protected Expression SimpleCollectionLoop(CollectionMapperContext context, ParameterExpression sourceCollection, ParameterExpression targetCollection) { var targetCollectionInsertionMethod = GetTargetCollectionInsertionMethod(context); if (targetCollectionInsertionMethod == null) { string msg = $@"'{nameof( context.TargetInstance.Type )}' does not provide an insertion method. " + $"Please override '{nameof( GetTargetCollectionInsertionMethod )}' to provide the item insertion method."; throw new Exception(msg); } var itemMapping = MapperConfiguration[context.SourceCollectionElementType, context.TargetCollectionElementType].MappingExpression; Expression loopBody = Expression.Call ( targetCollection, targetCollectionInsertionMethod, itemMapping.Body.ReplaceParameter( context.SourceCollectionLoopingVar, itemMapping.Parameters[0].Name) ); return(ExpressionLoops.ForEach(sourceCollection, context.SourceCollectionLoopingVar, loopBody)); }
public Expression CollectionLoopWithReferenceTracking(CollectionMapperContext context, ParameterExpression sourceCollection, ParameterExpression targetCollection) { var targetCollectionInsertionMethod = GetTargetCollectionInsertionMethod(context); if (targetCollectionInsertionMethod == null) { string msg = $@"'{nameof( context.TargetInstance.Type )}' does not provide an insertion method. " + $"Please override '{nameof( GetTargetCollectionInsertionMethod )}' to provide the item insertion method."; throw new Exception(msg); } var itemMapping = MapperConfiguration[context.SourceCollectionLoopingVar.Type, context.TargetCollectionElementType].MappingExpression; var newElement = Expression.Variable(context.TargetCollectionElementType, "newElement"); return(Expression.Block ( new[] { newElement }, ExpressionLoops.ForEach(sourceCollection, context.SourceCollectionLoopingVar, Expression.Block ( LookUpBlock(context, context.SourceCollectionLoopingVar, newElement), Expression.Call(targetCollection, targetCollectionInsertionMethod, newElement) ) ))); }
protected override MethodInfo GetTargetCollectionInsertionMethod(CollectionMapperContext context) { //It is forbidden to use nameof with unbound generic types. We use 'int' just to get around that. var methodName = nameof(LinkedList <int> .AddLast); var methodParams = new[] { context.TargetCollectionElementType }; return(context.TargetInstance.Type.GetMethod(methodName, methodParams)); }
/// <summary> /// Returns the method that allows to clear the target collection. /// </summary> protected virtual MethodInfo GetTargetCollectionClearMethod(CollectionMapperContext context) { if (context.TargetInstance.Type.IsArray) { return(typeof(Array).GetMethod(nameof(Array.Clear), BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy)); } //It is forbidden to use nameof with unbound generic types. We use 'int' just to get around that. return(context.TargetInstance.Type.GetMethod(nameof(ICollection <int> .Clear))); }
protected override Expression GetTargetCollectionClearExpression(CollectionMapperContext context) { bool isResetCollection = /*context.Options.ReferenceBehavior == ReferenceBehaviors.USE_TARGET_INSTANCE_IF_NOT_NULL && */ context.Options.CollectionBehavior == CollectionBehaviors.RESET; var clearMethod = GetTargetCollectionClearMethod(context); //var lengthProperty = context.TargetInstance.Type.GetProperty( nameof( Array.Length ) ); return(isResetCollection ? Expression.Call(null, clearMethod, context.TargetInstance, Expression.Constant(0, typeof(int)), Expression.ArrayLength(context.TargetInstance)) : (Expression)Expression.Empty()); }
protected override MethodInfo GetUpdateCollectionMethod(CollectionMapperContext context) { return(typeof(LinqExtensions).GetMethod ( nameof(LinqExtensions.ArrayUpdate), BindingFlags.Static | BindingFlags.Public ) .MakeGenericMethod ( context.SourceCollectionElementType, context.TargetCollectionElementType )); }
protected virtual Expression GetUpdateCollectionExpression(CollectionMapperContext context) { if (context.Options.CollectionItemEqualityComparer == null) { return(Expression.Empty()); } var updateCollectionMethodInfo = GetUpdateCollectionMethod(context); return(Expression.Call(null, updateCollectionMethodInfo, context.Mapper, context.ReferenceTracker, context.SourceInstance, context.TargetInstance, Expression.Convert(Expression.Constant(context.Options.CollectionItemEqualityComparer.Compile()), typeof(Func <, ,>).MakeGenericType(context.SourceCollectionElementType, context.TargetCollectionElementType, typeof(bool))))); }
public BlockExpression LookUpBlock(CollectionMapperContext context, ParameterExpression sourceParam, ParameterExpression targetParam) { Expression itemLookupCall = Expression.Call ( Expression.Constant(refTrackingLookup.Target), refTrackingLookup.Method, context.ReferenceTracker, sourceParam, Expression.Constant(targetParam.Type) ); Expression itemCacheCall = Expression.Call ( Expression.Constant(addToTracker.Target), addToTracker.Method, context.ReferenceTracker, sourceParam, Expression.Constant(targetParam.Type), targetParam ); var mapMethod = CollectionMapperContext.RecursiveMapMethodInfo .MakeGenericMethod(sourceParam.Type, targetParam.Type); var itemMapping = MapperConfiguration[sourceParam.Type, targetParam.Type]; return(Expression.Block ( Expression.Assign(targetParam, Expression.Convert(itemLookupCall, targetParam.Type)), Expression.IfThen ( Expression.Equal(targetParam, Expression.Constant(null, targetParam.Type)), Expression.Block ( Expression.Assign(targetParam, Expression.New(targetParam.Type)), itemCacheCall, Expression.Call(context.Mapper, mapMethod, sourceParam, targetParam, context.ReferenceTracker, Expression.Constant(itemMapping)) ) ) )); }
/// <summary> /// Returns the expression that clears the collection /// </summary> protected virtual Expression GetTargetCollectionClearExpression(CollectionMapperContext context) { bool isResetCollection = context.Options.ReferenceBehavior == ReferenceBehaviors.USE_TARGET_INSTANCE_IF_NOT_NULL && context.Options.CollectionBehavior == CollectionBehaviors.RESET; if (isResetCollection) { var clearMethod = GetTargetCollectionClearMethod(context); if (clearMethod == null && isResetCollection) { string msg = $@"Cannot reset the collection. Type '{context.TargetInstance.Type}' does not provide a Clear method"; throw new Exception(msg); } return(Expression.Call(context.TargetInstance, clearMethod)); } return(Expression.Empty()); }
/// <summary> /// Returns the method that allows to insert items in the target collection. /// </summary> protected virtual MethodInfo GetTargetCollectionInsertionMethod(CollectionMapperContext context) { //It is forbidden to use nameof with unbound generic types. We use 'int' just to get around that. return(context.TargetInstance.Type.GetMethod(nameof(ICollection <int> .Add))); }
/// <summary> /// Returns the method that allows to clear the target collection. /// </summary> private MethodInfo GetTargetCollectionClearMethod(CollectionMapperContext context) { //It is forbidden to use nameof with unbound generic types. We use 'int' just to get around that. return(context.TargetInstance.Type.GetMethod(nameof(ICollection <int> .Clear))); }
protected virtual Type GetTemporaryCollectionType(CollectionMapperContext context) { return(typeof(List <>).MakeGenericType(context.SourceCollectionElementType)); }
protected virtual MethodInfo GetTemporaryCollectionInsertionMethod(CollectionMapperContext context) { return(this.GetTemporaryCollectionType(context).GetMethod(nameof(List <int> .Add))); }
protected override MethodInfo GetTargetCollectionClearMethod(CollectionMapperContext context) { var paramTypes = new[] { typeof(Array), typeof(int), typeof(int) }; return(typeof(Array).GetMethod(nameof(Array.Clear), paramTypes)); }
public override Expression GetMemberNewInstance(MemberMappingContext context, out bool isMapCompleted) { isMapCompleted = false; if (context.Options.CustomTargetConstructor != null) { return(Expression.Invoke(context.Options.CustomTargetConstructor)); } var collectionContext = new CollectionMapperContext((Mapping)context.Options); if (context.TargetMember.Type.IsArray) { var sourceCountMethod = GetCountMethod(context.SourceMember.Type); Expression sourceCountMethodCallExp; if (sourceCountMethod.IsStatic) { sourceCountMethodCallExp = Expression.Call(null, sourceCountMethod, context.SourceMember); } else { sourceCountMethodCallExp = Expression.Call(context.SourceMember, sourceCountMethod); } return(Expression.NewArrayInit(context.TargetMember.Type, sourceCountMethodCallExp)); } //OPTIMIZATION: If the types involved are primitives of exactly the same type //we can use the constructor taking as input the collection and avoid recursion if ((collectionContext.IsSourceElementTypeBuiltIn || collectionContext.IsTargetElementTypeBuiltIn) && collectionContext.SourceCollectionElementType == collectionContext.TargetCollectionElementType && context.Options.ReferenceBehavior == ReferenceBehaviors.CREATE_NEW_INSTANCE) { var newInstance = GetNewInstanceFromSourceCollection(context, collectionContext); if (newInstance != null) { var typeMapping = MapperConfiguration[context.SourceMember.Type, context.TargetMember.Type]; //We do not want recursion on each collection's item //but Capacity and other collection members must be mapped. Expression memberMappings = Expression.Empty(); if (context.TargetMemberValueGetter != null) //we can only map subparam if a way to access subparam is provided/resolved. Edge case is: providing a member's setter method but not the getter's { memberMappings = this.GetMemberMappingsExpression(typeMapping) .ReplaceParameter(context.Mapper, context.Mapper.Name) .ReplaceParameter(context.ReferenceTracker, context.ReferenceTracker.Name) .ReplaceParameter(context.SourceMember, context.SourceInstance.Name) .ReplaceParameter(context.TargetMember, context.TargetInstance.Name); } isMapCompleted = true; //we created a new instance also passing source array of non-reference type that will be copied return(Expression.Block ( Expression.IfThenElse ( Expression.IsTrue(Expression.Equal(context.SourceMemberValueGetter, context.SourceMemberNullValue)), //Expression.Assign( context.TargetMember, context.TargetMemberNullValue ), //this only works for properties/fields context.TargetMemberValueSetter .ReplaceParameter(context.TargetMemberNullValue, "targetValue"), //works on setter methods too Expression.Block ( //in order to assign inner members we need to assign TargetMember //(we also replaced TargetInstance with TargetMember) context.TargetMemberValueSetter .ReplaceParameter(newInstance, "targetValue"), //works on setter methods too //Expression.Assign( context.TargetMember, newInstance ), //this only works for properties/fields memberMappings ?? Expression.Empty() ) ) )); } } //OPTIMIZATION: if we need to create a new instance of a collection //we can try to reserve just the right capacity thus avoiding reallocations. //If the source collection implements ICollection we can read 'Count' property without any iteration. if (context.Options.ReferenceBehavior == ReferenceBehaviors.CREATE_NEW_INSTANCE && context.SourceMember.Type.ImplementsInterface(typeof(ICollection <>))) { var newInstanceWithReservedCapacity = this.GetNewInstanceWithReservedCapacity(context); if (newInstanceWithReservedCapacity != null) { return(newInstanceWithReservedCapacity); } } //DEALING WITH INTERFACES Type sourceType = context.SourceMember.Type.IsGenericType ? context.SourceMember.Type.GetGenericTypeDefinition() : context.SourceMember.Type; Type targetType = context.TargetMember.Type.IsGenericType ? context.TargetMember.Type.GetGenericTypeDefinition() : context.TargetMember.Type; //If we are just cloning (ie: mapping on the same type) we prefer to use exactly the //same runtime-type used in the source (in order to manage abstract classes, interfaces and inheritance). if (context.TargetMember.Type.IsInterface && (context.TargetMember.Type.IsAssignableFrom(context.SourceMember.Type) || targetType.IsAssignableFrom(sourceType) || sourceType.ImplementsInterface(targetType))) { ////RUNTIME INSPECTION (in order to use on the target the same type of the source, if possible) ////MethodInfo getTypeMethodInfo = typeof( object ).GetMethod( nameof( object.GetType ) ); ////var getSourceType = Expression.Call( context.SourceMemberValueGetter, getTypeMethodInfo ); ////return Expression.Convert( Expression.Call( null, typeof( InstanceFactory ).GetMethods()[ 1 ], //// getSourceType, Expression.Constant( null, typeof( object[] ) ) ), context.TargetMember.Type ); //Runtime inspection did not work well between array and collection backed by ICollection or IEnumerable; //just provide a list if the target is backed by an interface... return(Expression.New(typeof(List <>).MakeGenericType(collectionContext.TargetCollectionElementType))); } var defaultCtor = targetType.GetConstructor(Type.EmptyTypes); if (defaultCtor != null) { return(Expression.New(context.TargetMember.Type)); } if (targetType.IsInterface) { //use List<> as default collection type return(Expression.New(typeof(List <>).MakeGenericType(collectionContext.TargetCollectionElementType))); } throw new Exception($"Type {targetType} does not have a default constructor. " + $"Please provide a way to construct the type like this: cfg.MapTypes<A, B>( () => new B(param1,param2,...) ) "); }
protected override MethodInfo GetTargetCollectionInsertionMethod(CollectionMapperContext context) { return(context.TargetInstance.Type.GetMethod("Push")); }
/// <summary> /// Returns an expression calling Expression.New. /// Expression.New will call a constructor taking as input a collection /// </summary> protected virtual Expression GetNewInstanceFromSourceCollection(MemberMappingContext context, CollectionMapperContext collectionContext) { var targetConstructor = context.TargetMember.Type.GetConstructor( new[] { typeof(IEnumerable <>).MakeGenericType(collectionContext.TargetCollectionElementType) }); if (targetConstructor == null) { return(null); } return(Expression.New(targetConstructor, context.SourceMember)); }
protected override Expression GetMemberNewInstance(MemberMappingContext context) { if (context.Options.CustomTargetConstructor != null) { return(Expression.Invoke(context.Options.CustomTargetConstructor)); } var collectionContext = new CollectionMapperContext(context.SourceMember.Type, context.TargetMember.Type, context.Options); //OPTIMIZATION: If the types involved are primitives of exactly the same type //we can use the constructor taking as input the collection and avoid recursion if ((collectionContext.IsSourceElementTypeBuiltIn || collectionContext.IsTargetElementTypeBuiltIn) && collectionContext.SourceCollectionElementType == collectionContext.TargetCollectionElementType && context.Options.ReferenceBehavior == ReferenceBehaviors.CREATE_NEW_INSTANCE) { var newInstance = GetNewInstanceFromSourceCollection(context, collectionContext); if (newInstance != null) { var typeMapping = MapperConfiguration[context.SourceMember.Type, context.TargetMember.Type]; //We do not want recursion on each collection's item //but Capacity and other collection members must be mapped. var memberMappings = this.GetMemberMappings(typeMapping) .ReplaceParameter(context.Mapper, context.Mapper.Name) .ReplaceParameter(context.ReferenceTracker, context.ReferenceTracker.Name) .ReplaceParameter(context.SourceMember, context.SourceInstance.Name) .ReplaceParameter(context.TargetMember, context.TargetInstance.Name); context.InitializationComplete = true; return(Expression.Block ( //in order to assign inner members we need to assign TargetMember //(we also replaced TargetInstance with TargetMember) Expression.Assign(context.TargetMember, newInstance), memberMappings, context.TargetMember )); } } //OPTIMIZATION: if we need to create a new instance of a collection //we can try to reserve just the right capacity thus avoiding reallocations. //If the source collection implements ICollection we can read 'Count' property without any iteration. if (context.Options.ReferenceBehavior == ReferenceBehaviors.CREATE_NEW_INSTANCE && context.SourceMember.Type.ImplementsInterface(typeof(ICollection <>))) { var newInstanceWithReservedCapacity = this.GetNewInstanceWithReservedCapacity(context); if (newInstanceWithReservedCapacity != null) { return(newInstanceWithReservedCapacity); } } //DEALING WITH INTERFACES Type sourceType = context.SourceMember.Type.IsGenericType ? context.SourceMember.Type.GetGenericTypeDefinition() : context.SourceMember.Type; Type targetType = context.TargetMember.Type.IsGenericType ? context.TargetMember.Type.GetGenericTypeDefinition() : context.TargetMember.Type; //If we are just cloning (ie: mapping on the same type) we prefer to use exactly the //same runtime-type used in the source (in order to manage abstract classes, interfaces and inheritance). if (context.TargetMember.Type.IsInterface && (context.TargetMember.Type.IsAssignableFrom(context.SourceMember.Type) || targetType.IsAssignableFrom(sourceType) || sourceType.ImplementsInterface(targetType))) { ////RUNTIME INSPECTION (in order to use on the target the same type of the source, if possible) ////MethodInfo getTypeMethodInfo = typeof( object ).GetMethod( nameof( object.GetType ) ); ////var getSourceType = Expression.Call( context.SourceMemberValueGetter, getTypeMethodInfo ); ////return Expression.Convert( Expression.Call( null, typeof( InstanceFactory ).GetMethods()[ 1 ], //// getSourceType, Expression.Constant( null, typeof( object[] ) ) ), context.TargetMember.Type ); //Runtime inspection did not work well between array and collection backed by ICollection or IEnumerable; //just provide a list if the target is backed by an interface... return(Expression.New(typeof(List <>).MakeGenericType(collectionContext.TargetCollectionElementType))); } return(Expression.New(context.TargetMember.Type)); }
protected override Type GetTemporaryCollectionType(CollectionMapperContext context) { //by copying data in a temp stack and then in the target collection //the correct order of the items is preserved return(typeof(Stack <>).MakeGenericType(context.SourceCollectionElementType)); }
protected override Type GetTemporaryCollectionType(CollectionMapperContext context) { return(typeof(Stack <>).MakeGenericType(context.SourceCollectionElementType)); }
protected override MethodInfo GetTargetCollectionClearMethod(CollectionMapperContext context) { return(typeof(Array).GetMethod(nameof(Array.Clear), BindingFlags.Public | BindingFlags.Static)); }
protected override Expression ComplexCollectionLoop(ParameterExpression sourceCollection, Type sourceCollectionElementType, ParameterExpression targetCollection, Type targetCollectionElementType, MethodInfo targetCollectionInsertionMethod, ParameterExpression sourceCollectionLoopingVar, ParameterExpression referenceTracker, ParameterExpression mapper, CollectionMapperContext context = null) { var newElement = Expression.Variable(targetCollectionElementType, "newElement"); var itemIndex = Expression.Parameter(typeof(int), "itemIndex"); return(Expression.Block ( new[] { newElement, itemIndex }, ExpressionLoops.ForEach(sourceCollection, sourceCollectionLoopingVar, Expression.Block ( LookUpBlock(sourceCollectionLoopingVar, newElement, referenceTracker, mapper), Expression.Assign(Expression.ArrayAccess(targetCollection, itemIndex), newElement), Expression.AddAssign(itemIndex, Expression.Constant(1)) ) ))); }
protected virtual Expression ComplexCollectionLoop(ParameterExpression sourceCollection, Type sourceCollectionElementType, ParameterExpression targetCollection, Type targetCollectionElementType, MethodInfo targetCollectionInsertionMethod, ParameterExpression sourceCollectionLoopingVar, ParameterExpression referenceTracker, ParameterExpression mapper, CollectionMapperContext context = null) { if (targetCollectionInsertionMethod == null) { string msg = $@"'{targetCollection.Type}' does not provide an insertion method. " + $"Please override '{nameof( GetTargetCollectionInsertionMethod )}' to provide the item insertion method."; throw new Exception(msg); } var newElement = Expression.Variable(targetCollectionElementType, "newElement"); var mapping = ((Mapping)context.Options).GlobalConfig[sourceCollectionElementType, targetCollectionElementType]; if (mapping.Source.EntryType != mapping.Source.ReturnType) { mapping = ((Mapping)context.Options).GlobalConfig[mapping.Source.ReturnType, targetCollectionElementType]; } var valueGetter = mapping.Source.ValueGetter; /*member extraction support*/ Expression valueExtraction = Expression.Invoke(valueGetter, sourceCollectionLoopingVar); if (((Mapping)context.Options).Source.MemberAccessPath.Count <= 1) { valueExtraction = sourceCollectionLoopingVar; } return(Expression.Block ( new[] { newElement }, ExpressionLoops.ForEach(sourceCollection, sourceCollectionLoopingVar, Expression.Block ( Expression.IfThenElse ( Expression.Equal(valueExtraction, Expression.Constant(null, sourceCollectionElementType)), Expression.Call(targetCollection, targetCollectionInsertionMethod, Expression.Default(targetCollectionElementType)), Expression.Block ( LookUpBlock(sourceCollectionLoopingVar, newElement, referenceTracker, mapper), Expression.Call(targetCollection, targetCollectionInsertionMethod, newElement) ) ) ) ))); }