/// <summary> /// Generates state transfer expressions to copy a complex type. /// </summary> /// <param name="complexType"> /// Complex type that will be cloned. /// </param> /// <param name="source"> /// Variable expression for the original instance. /// </param> /// <param name="target"> /// Variable expression for the cloned instance. /// </param> /// <param name="expression"> /// Receives the generated transfer expressions. /// </param> private void GenerateFieldBasedComplexTypeTransferExpressions(Type complexType, Expression source, Expression target, ICollection <Expression> expression) { // Enumerate all of the type's fields and generate transfer expressions for each ISet <FieldInfo> skipCloneFieldInfos; IEnumerable <FieldInfo> fieldInfos = GetFieldInfosIncludingBaseClasses(complexType, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, out skipCloneFieldInfos); // For those field which skip deep copying, do shallow copying by assigning the field value from the source to the target foreach (FieldInfo fieldInfo in skipCloneFieldInfos) { expression.Add(CloneExpressionHelper.CreateCopyFieldExpression(source, target, fieldInfo)); } foreach (FieldInfo fieldInfo in fieldInfos) { Type fieldType = fieldInfo.FieldType; if (IsTypePrimitiveOrString(fieldType)) { expression.Add(CloneExpressionHelper.CreateCopyFieldExpression(source, target, fieldInfo)); } else if (fieldType.IsValueType) { // A nested value type is part of the parent and will have its fields directly assigned without boxing, new instance creation or anything like that. this.GenerateFieldBasedComplexTypeTransferExpressions(fieldType, Expression.Field(source, fieldInfo), Expression.Field(target, fieldInfo), expression); } else { this.GenerateFieldBasedReferenceTypeTransferExpressions(source, target, expression, fieldInfo); } } }
/// <summary> /// Generates the expressions to transfer a reference type (array or class) /// </summary> /// <param name="original">Original value that will be cloned</param> /// <param name="clone">Variable that will receive the cloned value</param> /// <param name="expressions"> /// Receives the expression generated to transfer the values /// </param> /// <param name="fieldInfo">Reflection informations about the field being cloned</param> private void GenerateFieldBasedReferenceTypeTransferExpressions(Expression original, Expression clone, ICollection <Expression> expressions, FieldInfo fieldInfo) { // Reference types and arrays require special care because they can be null, so gather the transfer expressions in a separate block for the null check var fieldExpressions = new List <Expression>(); var fieldVariables = new List <ParameterExpression>(); var fieldType = fieldInfo.FieldType; if (fieldType.IsArray) { Expression fieldClone = GenerateFieldBasedComplexArrayTransferExpressions( fieldType, fieldType.GetElementType(), Expression.Field(original, fieldInfo), fieldVariables, fieldExpressions); fieldExpressions.Add(CloneExpressionHelper.CreateSetFieldExpression(clone, fieldClone, fieldInfo)); } else { fieldExpressions.Add(CloneExpressionHelper.CreateCopyComplexFieldExpression(original, clone, fieldInfo, _objectDictionary)); } expressions.Add( Expression.IfThen( Expression.NotEqual(Expression.Field(original, fieldInfo), Expression.Constant(null)), Expression.Block(fieldVariables, fieldExpressions))); }
/// <summary> /// Generates state transfer expressions to copy a complex type /// </summary> /// <param name="complexType">Complex type that will be cloned</param> /// <param name="source">Variable expression for the original instance</param> /// <param name="target">Variable expression for the cloned instance</param> /// <param name="expression">Receives the generated transfer expressions</param> private void GenerateFieldBasedComplexTypeTransferExpressions(Type complexType, Expression source, Expression target, ICollection <Expression> expression) { // Enumerate all of the type's fields and generate transfer expressions for each ISet <FieldInfo> skipCloneFieldInfos; var fieldInfos = GetFieldInfosIncludingBaseClasses( complexType, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, out skipCloneFieldInfos); // For those field which skip deep copying, do shallow copying by assigning the field value from the source to the target foreach (var fieldInfo in skipCloneFieldInfos) { expression.Add(CloneExpressionHelper.CreateCopyFieldExpression(source, target, fieldInfo)); } foreach (var fieldInfo in fieldInfos) { var fieldType = fieldInfo.FieldType; if (fieldType.Equals(typeof(DataTable))) { expression.Add(CloneExpressionHelper.CreateCopyFieldExpression(source, target, fieldInfo)); } else if (IsTypePrimitiveOrString(fieldType)) { expression.Add(CloneExpressionHelper.CreateCopyFieldExpression(source, target, fieldInfo)); } else if (fieldType.IsValueType) { GenerateFieldBasedComplexTypeTransferExpressions(fieldType, Expression.Field(source, fieldInfo), Expression.Field(target, fieldInfo), expression); } else { GenerateFieldBasedReferenceTypeTransferExpressions(source, target, expression, fieldInfo); } } }
/// <summary> /// Generates state transfer expressions to copy an array of complex types. /// </summary> /// <param name="arrayType"> /// Type of array that will be cloned. /// </param> /// <param name="elementType"> /// Type of the elements of the array. /// </param> /// <param name="originalArray"> /// Variable expression for the original array. /// </param> /// <param name="arrayVariables"> /// Receives variables used by the transfer expressions. /// </param> /// <param name="arrayExpressions"> /// Receives the generated transfer expressions. /// </param> /// <returns> /// The variable holding the cloned array. /// </returns> private ParameterExpression GenerateFieldBasedComplexArrayTransferExpressions(Type arrayType, Type elementType, Expression originalArray, ICollection <ParameterExpression> arrayVariables, ICollection <Expression> arrayExpressions) { // We need a temporary variable in order to transfer the elements of the array ParameterExpression arrayClone = Expression.Variable(arrayType); arrayVariables.Add(arrayClone); int dimensionCount = arrayType.GetArrayRank(); List <ParameterExpression> lengths = new List <ParameterExpression>(); List <ParameterExpression> indexes = new List <ParameterExpression>(); List <LabelTarget> labels = new List <LabelTarget>(); // Retrieve the length of each of the array's dimensions for (int index = 0; index < dimensionCount; ++index) { // Obtain the length of the array in the current dimension lengths.Add(Expression.Variable(typeof(int))); arrayVariables.Add(lengths[index]); arrayExpressions.Add(Expression.Assign(lengths[index], Expression.Call(originalArray, ArrayGetLengthMethodInfo, Expression.Constant(index)))); // Set up a variable to index the array in this dimension indexes.Add(Expression.Variable(typeof(int))); arrayVariables.Add(indexes[index]); // Also set up a label than can be used to break out of the dimension's transfer loop labels.Add(Expression.Label()); } // Create a new (empty) array with the same dimensions and lengths as the original arrayExpressions.Add(Expression.Assign(arrayClone, Expression.NewArrayBounds(elementType, lengths))); // Initialize the indexer of the outer loop (indexers are initialized one up // in the loops (ie. before the loop using it begins), so we have to set this // one outside of the loop building code. arrayExpressions.Add(Expression.Assign(indexes[0], Expression.Constant(0))); // Build the nested loops (one for each dimension) from the inside out Expression innerLoop = null; for (int index = dimensionCount - 1; index >= 0; --index) { List <ParameterExpression> loopVariables = new List <ParameterExpression>(); List <Expression> loopExpressions = new List <Expression> { Expression.IfThen(Expression.GreaterThanOrEqual(indexes[index], lengths[index]), Expression.Break(labels[index])) }; // If we reached the end of the current array dimension, break the loop if (innerLoop == null) { // The innermost loop clones an actual array element if (IsTypePrimitiveOrString(elementType)) { loopExpressions.Add(Expression.Assign(Expression.ArrayAccess(arrayClone, indexes), Expression.ArrayAccess(originalArray, indexes))); } else if (elementType.IsValueType) { this.GenerateFieldBasedComplexTypeTransferExpressions(elementType, Expression.ArrayAccess(originalArray, indexes), Expression.ArrayAccess(arrayClone, indexes), loopExpressions); } else { List <ParameterExpression> nestedVariables = new List <ParameterExpression>(); List <Expression> nestedExpressions = new List <Expression>(); // A nested array should be cloned by directly creating a new array (not invoking a cloner) since you cannot derive from an array if (elementType.IsArray) { Type nestedElementType = elementType.GetElementType(); Expression clonedElement = IsTypePrimitiveOrString(nestedElementType) ? GenerateFieldBasedPrimitiveArrayTransferExpressions(elementType, Expression.ArrayAccess(originalArray, indexes)) : this.GenerateFieldBasedComplexArrayTransferExpressions(elementType, nestedElementType, Expression.ArrayAccess(originalArray, indexes), nestedVariables, nestedExpressions); nestedExpressions.Add(Expression.Assign(Expression.ArrayAccess(arrayClone, indexes), clonedElement)); } else { nestedExpressions.Add(CloneExpressionHelper.CreateCopyComplexArrayTypeFieldExpression(Expression.ArrayAccess(originalArray, indexes), Expression.ArrayAccess(arrayClone, indexes), elementType, this._objectDictionary)); } // Whether array-in-array of reference-type-in-array, we need a null check before // doing anything to avoid NullReferenceExceptions for unset members loopExpressions.Add(Expression.IfThen(Expression.NotEqual(Expression.ArrayAccess(originalArray, indexes), Expression.Constant(null)), Expression.Block(nestedVariables, nestedExpressions))); } } else { // Outer loops of any level just reset the inner loop's indexer and execute the inner loop loopExpressions.Add(Expression.Assign(indexes[index + 1], Expression.Constant(0))); loopExpressions.Add(innerLoop); } // Each time we executed the loop instructions, increment the indexer loopExpressions.Add(Expression.PreIncrementAssign(indexes[index])); // Build the loop using the expressions recorded above innerLoop = Expression.Loop(Expression.Block(loopVariables, loopExpressions), labels[index]); } // After the loop builder has finished, the innerLoop variable contains the entire hierarchy of nested loops, so add this to the clone expressions. arrayExpressions.Add(innerLoop); return(arrayClone); }