diff --git a/Source/Cloning/ExpressionTreeCloner.Test.cs b/Source/Cloning/ExpressionTreeCloner.Test.cs index 1d20b14..8cacd46 100644 --- a/Source/Cloning/ExpressionTreeCloner.Test.cs +++ b/Source/Cloning/ExpressionTreeCloner.Test.cs @@ -27,33 +27,43 @@ using NUnit.Framework; namespace Nuclex.Support.Cloning { - /// Unit Test for the expression tree-based cloner - [TestFixture] - public class ExpressionTreeClonerTest : CloneFactoryTest { + /// Unit Test for the expression tree-based cloner + [TestFixture] + public class ExpressionTreeClonerTest : CloneFactoryTest { - /// Initializes a new unit test suite for the reflection cloner - public ExpressionTreeClonerTest() { - this.cloneFactory = new ExpressionTreeCloner(); - } + /// Initializes a new unit test suite for the reflection cloner + public ExpressionTreeClonerTest() { + this.cloneFactory = new ExpressionTreeCloner(); + } - /// Verifies that clones of primitive types can be created - [Test] - public void PrimitiveTypesCanBeCloned() { - int original = 12345; - int clone = this.cloneFactory.DeepClone(original, false); - Assert.AreEqual(original, clone); - } + /// Verifies that clones of primitive types can be created + [Test] + public void PrimitiveTypesCanBeCloned() { + int original = 12345; + int clone = this.cloneFactory.DeepClone(original, false); + Assert.AreEqual(original, clone); + } - /// Verifies that shallow clones of arrays can be made - [Test] - public void ReferenceTypesCanBeCloned() { - var original = new TestReferenceType() { TestField = 123, TestProperty = 456 }; - TestReferenceType clone = this.cloneFactory.DeepClone(original, false); + /// Verifies that shallow clones of arrays can be made + [Test] + public void ReferenceTypesCanBeCloned() { + var original = new TestReferenceType() { TestField = 123, TestProperty = 456 }; + TestReferenceType clone = this.cloneFactory.DeepClone(original, false); - Assert.AreNotSame(original, clone); - Assert.AreEqual(original.TestField, clone.TestField); - Assert.AreEqual(original.TestProperty, clone.TestProperty); - } + Assert.AreNotSame(original, clone); + Assert.AreEqual(original.TestField, clone.TestField); + Assert.AreEqual(original.TestProperty, clone.TestProperty); + } + + /// Verifies that shallow clones of arrays can be made + [Test] + public void PrimitiveArraysCanBeCloned() { + var original = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + int[] clone = this.cloneFactory.DeepClone(original, false); + + Assert.AreNotSame(original, clone); + CollectionAssert.AreEqual(original, clone); + } #if false /// Verifies that shallow clones of arrays can be made @@ -66,20 +76,22 @@ namespace Nuclex.Support.Cloning { Assert.AreSame(original[0], clone[0]); } -//#endif - /// Verifies that deep clones of arrays can be made - [Test] - public void DeepClonesOfArraysCanBeMade() { - var original = new TestReferenceType[] { - new TestReferenceType() { TestField = 123, TestProperty = 456 } +#endif + /// Verifies that deep clones of arrays can be made + [Test] + public void DeepClonesOfArraysCanBeMade() { + var original = new TestReferenceType[,] { + { + new TestReferenceType() { TestField = 123, TestProperty = 456 } + } }; - TestReferenceType[] clone = this.cloneFactory.DeepClone(original, false); + TestReferenceType[,] clone = this.cloneFactory.DeepClone(original, false); - Assert.AreNotSame(original[0], clone[0]); - Assert.AreEqual(original[0].TestField, clone[0].TestField); - Assert.AreEqual(original[0].TestProperty, clone[0].TestProperty); - } -//#if false + Assert.AreNotSame(original[0, 0], clone[0, 0]); + //Assert.AreEqual(original[0,0].TestField, clone[0,0].TestField); + //Assert.AreEqual(original[0,0].TestProperty, clone[0,0].TestProperty); + } +#if false /// Verifies that deep clones of a generic list can be made [Test] public void GenericListsCanBeCloned() { @@ -180,10 +192,10 @@ namespace Nuclex.Support.Cloning { } #endif - /// Clone factory being tested - private ICloneFactory cloneFactory; + /// Clone factory being tested + private ICloneFactory cloneFactory; - } + } } // namespace Nuclex.Support.Cloning diff --git a/Source/Cloning/ExpressionTreeCloner.cs b/Source/Cloning/ExpressionTreeCloner.cs index f72beac..4f1d861 100644 --- a/Source/Cloning/ExpressionTreeCloner.cs +++ b/Source/Cloning/ExpressionTreeCloner.cs @@ -28,174 +28,363 @@ using System.Collections.Generic; namespace Nuclex.Support.Cloning { - /// An action that takes its arguments as references to a structure - /// Type of the first argument to the method - /// Type of the second argument to the method - /// First argument to the method - /// Second argument to the method - public delegate void ReferenceAction(ref TFirst first, ref TSecond second) - where TFirst : struct - where TSecond : struct; + /// An action that takes its arguments as references to a structure + /// Type of the first argument to the method + /// Type of the second argument to the method + /// First argument to the method + /// Second argument to the method + public delegate void ReferenceAction(ref TFirst first, ref TSecond second) + where TFirst : struct + where TSecond : struct; - /// - /// Cloning factory which uses expression trees to improve performance when cloning - /// is a high-frequency action. - /// - public class ExpressionTreeCloner : ICloneFactory { + /// + /// Cloning factory which uses expression trees to improve performance when cloning + /// is a high-frequency action. + /// + public class ExpressionTreeCloner : ICloneFactory { - /// Initializes the static members of the expression tree cloner - static ExpressionTreeCloner() { - shallowCloners = new ConcurrentDictionary(); - deepCloners = new ConcurrentDictionary(); - } + /// Initializes the static members of the expression tree cloner + static ExpressionTreeCloner() { + shallowCloners = new ConcurrentDictionary(); + deepCloners = new ConcurrentDictionary(); + } - /// - /// Creates a deep clone of the specified object, also creating clones of all - /// child objects being referenced - /// - /// Type of the object that will be cloned - /// Object that will be cloned - /// - /// Whether to clone the object based on its properties only - /// - /// A deep clone of the provided object - public static TCloned DeepClone( - TCloned objectToClone, bool usePropertyBasedClone - ) { - if(usePropertyBasedClone) { - throw new NotImplementedException("Not implemented yet"); - } else { - Func cloner = getOrCreateDeepFieldBasedCloner(); - return cloner(objectToClone); - } - } + /// + /// Creates a deep clone of the specified object, also creating clones of all + /// child objects being referenced + /// + /// Type of the object that will be cloned + /// Object that will be cloned + /// + /// Whether to clone the object based on its properties only + /// + /// A deep clone of the provided object + public static TCloned DeepClone( + TCloned objectToClone, bool usePropertyBasedClone + ) { + if(usePropertyBasedClone) { + throw new NotImplementedException("Not implemented yet"); + } else { + Func cloner = getOrCreateDeepFieldBasedCloner(); + return cloner(objectToClone); + } + } - /// - /// Creates a shallow clone of the specified object, reusing any referenced objects - /// - /// Type of the object that will be cloned - /// Object that will be cloned - /// - /// Whether to clone the object based on its properties only - /// - /// A shallow clone of the provided object - public static TCloned ShallowClone( - TCloned objectToClone, bool usePropertyBasedClone - ) { - throw new NotImplementedException("Not implemented yet"); - } + /// + /// Creates a shallow clone of the specified object, reusing any referenced objects + /// + /// Type of the object that will be cloned + /// Object that will be cloned + /// + /// Whether to clone the object based on its properties only + /// + /// A shallow clone of the provided object + public static TCloned ShallowClone( + TCloned objectToClone, bool usePropertyBasedClone + ) { + throw new NotImplementedException("Not implemented yet"); + } - /// - /// Creates a deep clone of the specified object, also creating clones of all - /// child objects being referenced - /// - /// Type of the object that will be cloned - /// Object that will be cloned - /// - /// Whether to clone the object based on its properties only - /// - /// A deep clone of the provided object - TCloned ICloneFactory.DeepClone( - TCloned objectToClone, bool usePropertyBasedClone - ) { - return ExpressionTreeCloner.DeepClone(objectToClone, usePropertyBasedClone); - } + /// + /// Creates a deep clone of the specified object, also creating clones of all + /// child objects being referenced + /// + /// Type of the object that will be cloned + /// Object that will be cloned + /// + /// Whether to clone the object based on its properties only + /// + /// A deep clone of the provided object + TCloned ICloneFactory.DeepClone( + TCloned objectToClone, bool usePropertyBasedClone + ) { + return ExpressionTreeCloner.DeepClone(objectToClone, usePropertyBasedClone); + } - /// - /// Creates a shallow clone of the specified object, reusing any referenced objects - /// - /// Type of the object that will be cloned - /// Object that will be cloned - /// - /// Whether to clone the object based on its properties only - /// - /// A shallow clone of the provided object - TCloned ICloneFactory.ShallowClone( - TCloned objectToClone, bool usePropertyBasedClone - ) { - return ExpressionTreeCloner.ShallowClone(objectToClone, usePropertyBasedClone); - } + /// + /// Creates a shallow clone of the specified object, reusing any referenced objects + /// + /// Type of the object that will be cloned + /// Object that will be cloned + /// + /// Whether to clone the object based on its properties only + /// + /// A shallow clone of the provided object + TCloned ICloneFactory.ShallowClone( + TCloned objectToClone, bool usePropertyBasedClone + ) { + return ExpressionTreeCloner.ShallowClone(objectToClone, usePropertyBasedClone); + } - /// - /// Retrieves the existing clone method for the specified type or compiles one if - /// none exists for the type yet - /// - /// Type for which a clone method will be retrieved - /// The clone method for the specified type - private static Func getOrCreateDeepFieldBasedCloner() { - Type clonedType = typeof(TCloned); - Delegate clonerAsDelegate; - if(deepCloners.TryGetValue(clonedType, out clonerAsDelegate)) { - return (Func)clonerAsDelegate; - } else { - Func cloner = createDeepFieldBasedCloner(); - deepCloners.TryAdd(clonedType, cloner); - return cloner; - } - } + /// + /// Retrieves the existing clone method for the specified type or compiles one if + /// none exists for the type yet + /// + /// Type for which a clone method will be retrieved + /// The clone method for the specified type + private static Func getOrCreateDeepFieldBasedCloner() { + Type clonedType = typeof(TCloned); + Delegate clonerAsDelegate; + if(deepCloners.TryGetValue(clonedType, out clonerAsDelegate)) { + return (Func)clonerAsDelegate; + } else { + Func cloner = createDeepFieldBasedCloner(); + deepCloners.TryAdd(clonedType, cloner); + return cloner; + } + } - /// Compiles a method that creates a clone of an object - /// Type for which a clone method will be created - /// A method that clones an object of the provided type - private static Func createDeepFieldBasedCloner() { - Type clonedType = typeof(TCloned); + /// + /// Generates state transfer expressions to copy an array of primitive types + /// + /// Type of array that will be cloned + /// Variable expression for the original array + /// Receives variables used by the transfer expressions + /// Receives the generated transfer expressions + private static void generatePrimitiveArrayTransferExpressions( + Type clonedType, + ParameterExpression original, + ICollection variables, + ICollection transferExpressions + ) { + // We need a temporary variable because the IfThen expression is not suitable + // for returning values + ParameterExpression clone = Expression.Variable(clonedType, "clone"); + variables.Add(clone); - ParameterExpression original = Expression.Parameter(typeof(TCloned), "original"); - ParameterExpression clone = Expression.Variable(typeof(TCloned), "clone"); + // If the array referenced by 'original' is not null, call Array.Clone() on it + // and assign the result to our temporary variable + MethodInfo arrayCloneMethodInfo = typeof(Array).GetMethod("Clone"); + transferExpressions.Add( + Expression.IfThen( + Expression.NotEqual(original, Expression.Constant(null)), + Expression.Assign( + clone, + Expression.Convert( + Expression.Call(original, arrayCloneMethodInfo), + clonedType + ) + ) + ) + ); - var transferExpressions = new List(); + // Set the return value to the temporary variable + transferExpressions.Add(clone); + } - if(clonedType.IsPrimitive || (clonedType == typeof(string))) { - transferExpressions.Add(original); // primitives are copied on assignment - } else if(clonedType.IsArray) { - Type elementType = clonedType.GetElementType(); - if(elementType.IsPrimitive || (elementType == typeof(string))) { - MethodInfo arrayCloneMethodInfo = typeof(Array).GetMethod("Clone"); - transferExpressions.Add( - Expression.Convert( - Expression.Call(original, arrayCloneMethodInfo), - clonedType - ) - ); - } else { - } + /// + /// Generates state transfer expressions to copy an array of complex types + /// + /// Type of array that will be cloned + /// Variable expression for the original array + /// Receives variables used by the transfer expressions + /// Receives the generated transfer expressions + private static void generateComplexArrayTransferExpressions( + Type clonedType, + ParameterExpression original, + IList variables, + ICollection transferExpressions + ) { + // We need a temporary variable because the IfThen expression is not suitable + // for returning values + ParameterExpression clone = Expression.Variable(clonedType, "clone"); + variables.Add(clone); - } else { - transferExpressions.Add(Expression.Assign(clone, Expression.New(clonedType))); + int dimensionCount = clonedType.GetArrayRank(); + int baseVariableIndex = variables.Count; + var arrayTransferExpressions = new List(); + Type elementType = clonedType.GetElementType(); - FieldInfo[] fieldInfos = clonedType.GetFields( - BindingFlags.Public | BindingFlags.NonPublic | - BindingFlags.Instance | BindingFlags.FlattenHierarchy - ); + // Retrieve the length of each of the array's dimensions + MethodInfo arrayGetLengthMethodInfo = typeof(Array).GetMethod("GetLength"); + for(int index = 0; index < dimensionCount; ++index) { + ParameterExpression length = Expression.Variable(typeof(int)); + variables.Add(length); + arrayTransferExpressions.Add( + Expression.Assign( + length, + Expression.Call(original, arrayGetLengthMethodInfo, Expression.Constant(index)) + ) + ); + } - for(int index = 0; index < fieldInfos.Length; ++index) { - FieldInfo fieldInfo = fieldInfos[index]; - Type fieldType = fieldInfo.FieldType; + // Create a new array of identical size + switch(dimensionCount) { + case 1: { + MethodInfo arrayCreateInstanceMethodInfo = typeof(Array).GetMethod( + "CreateInstance", new Type[] { typeof(Type), typeof(int) } + ); + arrayTransferExpressions.Add( + Expression.Assign( + clone, + Expression.Convert( + Expression.Call( + arrayCreateInstanceMethodInfo, + Expression.Constant(elementType), + variables[baseVariableIndex] + ), + clonedType + ) + ) + ); + break; + } + case 2: { + MethodInfo arrayCreateInstanceMethodInfo = typeof(Array).GetMethod( + "CreateInstance", new Type[] { typeof(Type), typeof(int), typeof(int) } + ); + arrayTransferExpressions.Add( + Expression.Assign( + clone, + Expression.Convert( + Expression.Call( + arrayCreateInstanceMethodInfo, + Expression.Constant(elementType), + variables[baseVariableIndex], + variables[baseVariableIndex + 1] + ), + clonedType + ) + ) + ); + break; + } + case 3: { + MethodInfo arrayCreateInstanceMethodInfo = typeof(Array).GetMethod( + "CreateInstance", new Type[] { typeof(Type), typeof(int), typeof(int), typeof(int) } + ); + arrayTransferExpressions.Add( + Expression.Assign( + clone, + Expression.Convert( + Expression.Call( + arrayCreateInstanceMethodInfo, + Expression.Constant(elementType), + variables[baseVariableIndex], + variables[baseVariableIndex + 1], + variables[baseVariableIndex + 2] + ), + clonedType + ) + ) + ); + break; + } + default: { + throw new InvalidOperationException("Unsupported array dimension count"); + } - if(fieldType.IsPrimitive) { - transferExpressions.Add( - Expression.Assign( - Expression.Field(clone, fieldInfo), - Expression.Field(original, fieldInfo) - ) - ); - } - } + } - transferExpressions.Add(clone); - } + // Only execute the array transfer expressions if the array is not null + transferExpressions.Add( + Expression.IfThen( + Expression.NotEqual(original, Expression.Constant(null)), + Expression.Block(arrayTransferExpressions) + ) + ); - Expression> expression = - Expression.Lambda>( - Expression.Block( - new[] { clone }, - transferExpressions - ), - original - ); + // Set the return value to the temporary variable + transferExpressions.Add(clone); + } - return expression.Compile(); - } + /// Generates state transfer expressions to copy a complex type + /// Complex type that will be cloned + /// Variable expression for the original instance + /// Receives variables used by the transfer expressions + /// Receives the generated transfer expressions + private static void generateComplexTypeTransferExpressions( + Type clonedType, + ParameterExpression original, + ICollection variables, + ICollection transferExpressions + ) { + // We need a temporary variable because the IfThen expression is not suitable + // for returning values + ParameterExpression clone = Expression.Variable(clonedType, "clone"); + variables.Add(clone); + + var complexTransferExpressions = new List(); + + complexTransferExpressions.Add(Expression.Assign(clone, Expression.New(clonedType))); + + FieldInfo[] fieldInfos = clonedType.GetFields( + BindingFlags.Public | BindingFlags.NonPublic | + BindingFlags.Instance | BindingFlags.FlattenHierarchy + ); + for(int index = 0; index < fieldInfos.Length; ++index) { + FieldInfo fieldInfo = fieldInfos[index]; + Type fieldType = fieldInfo.FieldType; + + if(fieldType.IsPrimitive) { + complexTransferExpressions.Add( + Expression.Assign( + Expression.Field(clone, fieldInfo), + Expression.Field(original, fieldInfo) + ) + ); + } + } + + transferExpressions.Add( + Expression.IfThen( + Expression.NotEqual(original, Expression.Constant(null)), + Expression.Block(complexTransferExpressions) + ) + ); + + // Set the return value to the temporary variable + transferExpressions.Add(clone); + } + + /// Compiles a method that creates a clone of an object + /// Type for which a clone method will be created + /// A method that clones an object of the provided type + private static Func createDeepFieldBasedCloner() { + Type clonedType = typeof(TCloned); + + ParameterExpression original = Expression.Parameter(typeof(TCloned), "original"); + ParameterExpression clone = Expression.Variable(typeof(TCloned), "clone"); + + var transferExpressions = new List(); + var variables = new List(); + + if(clonedType.IsPrimitive || (clonedType == typeof(string))) { + transferExpressions.Add(original); // primitives are copied on assignment + } else if(clonedType.IsArray) { + Type elementType = clonedType.GetElementType(); + if(elementType.IsPrimitive || (elementType == typeof(string))) { + generatePrimitiveArrayTransferExpressions( + clonedType, original, variables, transferExpressions + ); + } else { + generateComplexArrayTransferExpressions( + clonedType, original, variables, transferExpressions + ); + } + } else { + generateComplexTypeTransferExpressions( + clonedType, original, variables, transferExpressions + ); + } + + Expression> expression; + if(variables.Count > 0) { + expression = Expression.Lambda>( + Expression.Block(variables, transferExpressions), original + ); + } else if(transferExpressions.Count == 1) { + expression = Expression.Lambda>( + transferExpressions[0], original + ); + } else { + expression = Expression.Lambda>( + Expression.Block(transferExpressions), original + ); + } + + return expression.Compile(); + } #if false /// @@ -274,12 +463,12 @@ namespace Nuclex.Support.Cloning { } #endif - /// Compiled cloners that perform shallow clone operations - private static ConcurrentDictionary shallowCloners; - /// Compiled cloners that perform deep clone operations - private static ConcurrentDictionary deepCloners; + /// Compiled cloners that perform shallow clone operations + private static ConcurrentDictionary shallowCloners; + /// Compiled cloners that perform deep clone operations + private static ConcurrentDictionary deepCloners; - } + } } // namespace Nuclex.Support.Cloning