From cd6cec42d3fc5cd8ddc2cf3bd7e4d4f21547cffe Mon Sep 17 00:00:00 2001 From: Markus Ewald Date: Wed, 8 Feb 2012 12:17:35 +0000 Subject: [PATCH] Expression tree cloner can now also create shallow field based clones git-svn-id: file:///srv/devel/repo-conversion/nusu@241 d2e56fa2-650e-0410-a79f-9358c0239efd --- Nuclex.Support (net-4.0).csproj | 6 + Nuclex.Support (xna-4.0-phone7).csproj | 6 + Nuclex.Support (xna-4.0-xbox360).csproj | 6 + Source/Cloning/CloneFactoryTest.cs | 2 + .../ExpressionTreeCloner.FieldBased.cs | 579 ++++++++++++++++++ .../ExpressionTreeCloner.PropertyBased.cs | 39 ++ Source/Cloning/ExpressionTreeCloner.Test.cs | 207 +++---- Source/Cloning/ExpressionTreeCloner.cs | 541 ++-------------- 8 files changed, 794 insertions(+), 592 deletions(-) create mode 100644 Source/Cloning/ExpressionTreeCloner.FieldBased.cs create mode 100644 Source/Cloning/ExpressionTreeCloner.PropertyBased.cs diff --git a/Nuclex.Support (net-4.0).csproj b/Nuclex.Support (net-4.0).csproj index 6519c0d..0874354 100644 --- a/Nuclex.Support (net-4.0).csproj +++ b/Nuclex.Support (net-4.0).csproj @@ -63,6 +63,12 @@ + + ExpressionTreeCloner.cs + + + ExpressionTreeCloner.cs + ExpressionTreeCloner.cs diff --git a/Nuclex.Support (xna-4.0-phone7).csproj b/Nuclex.Support (xna-4.0-phone7).csproj index b0e753a..661225d 100644 --- a/Nuclex.Support (xna-4.0-phone7).csproj +++ b/Nuclex.Support (xna-4.0-phone7).csproj @@ -94,6 +94,12 @@ + + ExpressionTreeCloner.cs + + + ExpressionTreeCloner.cs + ExpressionTreeCloner.cs diff --git a/Nuclex.Support (xna-4.0-xbox360).csproj b/Nuclex.Support (xna-4.0-xbox360).csproj index fe0c9e7..9ea1f89 100644 --- a/Nuclex.Support (xna-4.0-xbox360).csproj +++ b/Nuclex.Support (xna-4.0-xbox360).csproj @@ -105,6 +105,12 @@ + + ExpressionTreeCloner.cs + + + ExpressionTreeCloner.cs + ExpressionTreeCloner.cs diff --git a/Source/Cloning/CloneFactoryTest.cs b/Source/Cloning/CloneFactoryTest.cs index 4991008..02d6ee9 100644 --- a/Source/Cloning/CloneFactoryTest.cs +++ b/Source/Cloning/CloneFactoryTest.cs @@ -123,6 +123,8 @@ namespace Nuclex.Support.Cloning { HierarchicalReferenceType original, HierarchicalReferenceType clone, bool isDeepClone, bool isPropertyBasedClone ) { + Assert.AreNotSame(original, clone); + if(isPropertyBasedClone) { Assert.AreEqual(0, clone.TestField); Assert.AreEqual(0, clone.ValueTypeField.TestField); diff --git a/Source/Cloning/ExpressionTreeCloner.FieldBased.cs b/Source/Cloning/ExpressionTreeCloner.FieldBased.cs new file mode 100644 index 0000000..e7d773a --- /dev/null +++ b/Source/Cloning/ExpressionTreeCloner.FieldBased.cs @@ -0,0 +1,579 @@ +#region CPL License +/* +Nuclex Framework +Copyright (C) 2002-2010 Nuclex Development Labs + +This library is free software; you can redistribute it and/or +modify it under the terms of the IBM Common Public License as +published by the IBM Corporation; either version 1.0 of the +License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +IBM Common Public License for more details. + +You should have received a copy of the IBM Common Public +License along with this library +*/ +#endregion + +#if !(XBOX360 || WINDOWS_PHONE) + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Reflection; + +namespace Nuclex.Support.Cloning { + + partial class ExpressionTreeCloner : ICloneFactory { + + /// Compiles a method that creates a deep clone of an object + /// Type for which a clone method will be created + /// A method that clones an object of the provided type + /// + /// + /// The 'null' check is supposed to take place before running the cloner. This + /// avoids having redundant 'null' checks on nested types - first before calling + /// GetType() on the field to be cloned and second when runner the matching + /// cloner for the field. + /// + /// + /// This design also enables the cloning of nested value types (which can never + /// be null) without any null check whatsoever. + /// + /// + private static Func createDeepFieldBasedCloner(Type clonedType) { + ParameterExpression original = Expression.Parameter(typeof(object), "original"); + + var transferExpressions = new List(); + var variables = new List(); + + if(clonedType.IsPrimitive || (clonedType == typeof(string))) { + // Primitives and strings are copied on direct assignment + transferExpressions.Add(original); + } else if(clonedType.IsArray) { + // Arrays need to be cloned element-by-element + Type elementType = clonedType.GetElementType(); + + if(elementType.IsPrimitive || (elementType == typeof(string))) { + // For primitive arrays, the Array.Clone() method is sufficient + transferExpressions.Add( + generatePrimitiveArrayTransferExpressions( + clonedType, original, variables, transferExpressions + ) + ); + } else { + // To access the fields of the original type, we need it to be of the actual + // type instead of an object, so perform a downcast + ParameterExpression typedOriginal = Expression.Variable(clonedType); + variables.Add(typedOriginal); + transferExpressions.Add( + Expression.Assign(typedOriginal, Expression.Convert(original, clonedType)) + ); + + // Arrays of complex types require manual cloning + transferExpressions.Add( + generateComplexArrayTransferExpressions( + clonedType, typedOriginal, variables, transferExpressions + ) + ); + } + } else { + // We need a variable to hold the clone because due to the assignments it + // won't be last in the block when we're finished + ParameterExpression clone = Expression.Variable(clonedType); + variables.Add(clone); + + // Give it a new instance of the type being cloned + transferExpressions.Add(Expression.Assign(clone, Expression.New(clonedType))); + + // To access the fields of the original type, we need it to be of the actual + // type instead of an object, so perform a downcast + ParameterExpression typedOriginal = Expression.Variable(clonedType); + variables.Add(typedOriginal); + transferExpressions.Add( + Expression.Assign(typedOriginal, Expression.Convert(original, clonedType)) + ); + + // Generate the expressions required to transfer the type field by field + generateComplexTypeTransferExpressions( + clonedType, typedOriginal, clone, variables, transferExpressions + ); + + // Make sure the clone is the last thing in the block to set the return value + transferExpressions.Add(clone); + } + + // Turn all transfer expressions into a single block if necessary + Expression resultExpression; + if((transferExpressions.Count == 1) && (variables.Count == 0)) { + resultExpression = transferExpressions[0]; + } else { + resultExpression = Expression.Block(variables, transferExpressions); + } + + // Value types require manual boxing + if(clonedType.IsValueType) { + resultExpression = Expression.Convert(resultExpression, typeof(object)); + } + + return Expression.Lambda>(resultExpression, original).Compile(); + } + + /// Compiles a method that creates a shallow 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 createShallowFieldBasedCloner(Type clonedType) { + ParameterExpression original = Expression.Parameter(typeof(object), "original"); + + var transferExpressions = new List(); + var variables = new List(); + + if(clonedType.IsPrimitive || clonedType.IsValueType || (clonedType == typeof(string))) { + // Primitives and strings are copied on direct assignment + transferExpressions.Add(original); + } else if(clonedType.IsArray) { + transferExpressions.Add( + generatePrimitiveArrayTransferExpressions( + clonedType, original, variables, transferExpressions + ) + ); + } else { + // We need a variable to hold the clone because due to the assignments it + // won't be last in the block when we're finished + ParameterExpression clone = Expression.Variable(clonedType); + variables.Add(clone); + + // To access the fields of the original type, we need it to be of the actual + // type instead of an object, so perform a downcast + ParameterExpression typedOriginal = Expression.Variable(clonedType); + variables.Add(typedOriginal); + transferExpressions.Add( + Expression.Assign(typedOriginal, Expression.Convert(original, clonedType)) + ); + + // Give it a new instance of the type being cloned + transferExpressions.Add(Expression.Assign(clone, Expression.New(clonedType))); + + // Enumerate all of the type's fields and generate transfer expressions for each + FieldInfo[] fieldInfos = clonedType.GetFields( + BindingFlags.Public | BindingFlags.NonPublic | + BindingFlags.Instance | BindingFlags.FlattenHierarchy + ); + for(int index = 0; index < fieldInfos.Length; ++index) { + FieldInfo fieldInfo = fieldInfos[index]; + + transferExpressions.Add( + Expression.Assign( + Expression.Field(clone, fieldInfo), + Expression.Field(typedOriginal, fieldInfo) + ) + ); + } + + // Make sure the clone is the last thing in the block to set the return value + transferExpressions.Add(clone); + } + + // Turn all transfer expressions into a single block if necessary + Expression resultExpression; + if((transferExpressions.Count == 1) && (variables.Count == 0)) { + resultExpression = transferExpressions[0]; + } else { + resultExpression = Expression.Block(variables, transferExpressions); + } + + // Value types require manual boxing + if(clonedType.IsValueType) { + resultExpression = Expression.Convert(resultExpression, typeof(object)); + } + + return Expression.Lambda>(resultExpression, original).Compile(); + } + + /// + /// 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 + /// The variable holding the cloned array + private static Expression generatePrimitiveArrayTransferExpressions( + Type clonedType, + Expression original, + ICollection variables, + ICollection transferExpressions + ) { + MethodInfo arrayCloneMethodInfo = typeof(Array).GetMethod("Clone"); + return Expression.Convert( + Expression.Call( + Expression.Convert(original, typeof(Array)), arrayCloneMethodInfo + ), + clonedType + ); + } + + /// + /// 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 + /// The variable holding the cloned array + private static ParameterExpression generateComplexArrayTransferExpressions( + Type clonedType, + Expression original, + IList variables, + ICollection transferExpressions + ) { + // We need a temporary variable in order to transfer the elements of the array + ParameterExpression clone = Expression.Variable(clonedType); + variables.Add(clone); + + int dimensionCount = clonedType.GetArrayRank(); + int baseVariableIndex = variables.Count; + Type elementType = clonedType.GetElementType(); + + var lengths = new List(); + var indexes = new List(); + var labels = new List(); + + // Retrieve the length of each of the array's dimensions + MethodInfo arrayGetLengthMethodInfo = typeof(Array).GetMethod("GetLength"); + for(int index = 0; index < dimensionCount; ++index) { + + // Obtain the length of the array in the current dimension + lengths.Add(Expression.Variable(typeof(int))); + variables.Add(lengths[index]); + transferExpressions.Add( + Expression.Assign( + lengths[index], + Expression.Call( + original, arrayGetLengthMethodInfo, Expression.Constant(index) + ) + ) + ); + + // Set up a variable to index the array in this dimension + indexes.Add(Expression.Variable(typeof(int))); + variables.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 + transferExpressions.Add( + Expression.Assign(clone, 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. + transferExpressions.Add( + Expression.Assign(indexes[0], Expression.Constant(0)) + ); + + // We use a temporary variable to store the element + ParameterExpression element = Expression.Variable(elementType); + variables.Add(element); + + // Build the nested loops (one for each dimension) from the inside out + Expression innerLoop = null; + for(int index = dimensionCount - 1; index >= 0; --index) { + var loopVariables = new List(); + var loopExpressions = new List(); + + // If we reached the end of the current array dimension, break the loop + loopExpressions.Add( + Expression.IfThen( + Expression.GreaterThanOrEqual(indexes[index], lengths[index]), + Expression.Break(labels[index]) + ) + ); + + if(innerLoop == null) { + // The innermost loop clones an actual array element + + if(elementType.IsPrimitive || (elementType == typeof(string))) { + // Primitive array elements can be copied by simple assignment. This case + // should not occur since Array.Clone() should be used instead. + loopExpressions.Add( + Expression.Assign( + Expression.ArrayAccess(clone, indexes), + Expression.ArrayAccess(original, indexes) + ) + ); + } else if(elementType.IsValueType) { + // Arrays of complex value types can be transferred by assigning all fields + // of the source array element to the destination array element (cloning + // any nested reference types appropriately) + generateComplexTypeTransferExpressions( + elementType, + Expression.ArrayAccess(original, indexes), + Expression.ArrayAccess(clone, indexes), + variables, + loopExpressions + ); + + } else { + // Arrays of reference types need to be cloned by creating a new instance + // of the reference type and then transferring the fields over + ParameterExpression originalElement = Expression.Variable(elementType); + loopVariables.Add(originalElement); + + loopExpressions.Add( + Expression.Assign(originalElement, Expression.ArrayAccess(original, indexes)) + ); + + var nestedVariables = new List(); + var nestedTransferExpressions = new List(); + + // 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) { + Expression clonedElement; + + Type nestedElementType = elementType.GetElementType(); + if(nestedElementType.IsPrimitive || (nestedElementType == typeof(string))) { + clonedElement = generatePrimitiveArrayTransferExpressions( + elementType, originalElement, nestedVariables, nestedTransferExpressions + ); + } else { + clonedElement = generateComplexArrayTransferExpressions( + elementType, originalElement, nestedVariables, nestedTransferExpressions + ); + } + nestedTransferExpressions.Add( + Expression.Assign(Expression.ArrayAccess(clone, indexes), clonedElement) + ); + } else { + // Complex types are cloned by checking their actual, concrete type (fields + // may be typed to an interface or base class) and requesting a cloner for that + // type during runtime + MethodInfo getOrCreateClonerMethodInfo = typeof(ExpressionTreeCloner).GetMethod( + "getOrCreateDeepFieldBasedCloner", + BindingFlags.NonPublic | BindingFlags.Static + ); + MethodInfo getTypeMethodInfo = typeof(object).GetMethod("GetType"); + MethodInfo invokeMethodInfo = typeof(Func).GetMethod("Invoke"); + + // Generate expressions to do this: + // clone.SomeField = getOrCreateDeepFieldBasedCloner( + // original.SomeField.GetType() + // ).Invoke(original.SomeField); + nestedTransferExpressions.Add( + Expression.Assign( + Expression.ArrayAccess(clone, indexes), + Expression.Convert( + Expression.Call( + Expression.Call( + getOrCreateClonerMethodInfo, + Expression.Call(originalElement, getTypeMethodInfo) + ), + invokeMethodInfo, + originalElement + ), + elementType + ) + ) + ); + } + + // 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(originalElement, Expression.Constant(null)), + Expression.Block( + nestedVariables, + nestedTransferExpressions + ) + ) + ); + } + + } 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. + transferExpressions.Add(innerLoop); + + return clone; + } + + /// Generates state transfer expressions to copy a complex type + /// Complex type that will be cloned + /// Variable expression for the original instance + /// Variable expression for the cloned instance + /// Receives variables used by the transfer expressions + /// Receives the generated transfer expressions + private static void generateComplexTypeTransferExpressions( + Type clonedType, // Actual, concrete type (not declared type) + Expression original, // Expected to be an object + Expression clone, // As actual, concrete type + IList variables, + ICollection transferExpressions + ) { + // Enumerate all of the type's fields and generate transfer expressions for each + 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 || (fieldType == typeof(string))) { + // Primitive types and strings can be transferred by simple assignment + transferExpressions.Add( + Expression.Assign( + Expression.Field(clone, fieldInfo), + Expression.Field(original, 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. + generateComplexTypeTransferExpressions( + fieldType, + Expression.Field(original, fieldInfo), + Expression.Field(clone, fieldInfo), + variables, + transferExpressions + ); + } else { + generateReferenceTypeTransferExpressions( + original, clone, transferExpressions, fieldInfo, fieldType + ); + } + } + } + + /// + /// Generates the expressions to transfer a reference type (array or class) + /// + /// Original value that will be cloned + /// Variable that will receive the cloned value + /// + /// Receives the expression generated to transfer the values + /// + /// Reflection informations about the field being cloned + /// Type of the field being cloned + private static void generateReferenceTypeTransferExpressions( + Expression original, + Expression clone, + ICollection transferExpressions, + FieldInfo fieldInfo, + Type fieldType + ) { + // 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 fieldTransferExpressions = new List(); + var fieldVariables = new List(); + + if(fieldType.IsArray) { + // Arrays need to be cloned element-by-element + Expression fieldClone; + + Type elementType = fieldType.GetElementType(); + if(elementType.IsPrimitive || (elementType == typeof(string))) { + // For primitive arrays, the Array.Clone() method is sufficient + fieldClone = generatePrimitiveArrayTransferExpressions( + fieldType, + Expression.Field(original, fieldInfo), + fieldVariables, + fieldTransferExpressions + ); + } else { + // Arrays of complex types require manual cloning + fieldClone = generateComplexArrayTransferExpressions( + fieldType, + Expression.Field(original, fieldInfo), + fieldVariables, + fieldTransferExpressions + ); + } + + // Add the assignment to the transfer expressions. The array transfer expression + // generator will either have set up a temporary variable to hold the array or + // returned the conversion expression straight away + fieldTransferExpressions.Add( + Expression.Assign(Expression.Field(clone, fieldInfo), fieldClone) + ); + } else { + // Complex types are cloned by checking their actual, concrete type (fields + // may be typed to an interface or base class) and requesting a cloner for that + // type during runtime + MethodInfo getOrCreateClonerMethodInfo = typeof(ExpressionTreeCloner).GetMethod( + "getOrCreateDeepFieldBasedCloner", + BindingFlags.NonPublic | BindingFlags.Static + ); + MethodInfo getTypeMethodInfo = typeof(object).GetMethod("GetType"); + MethodInfo invokeMethodInfo = typeof(Func).GetMethod("Invoke"); + + // Generate expressions to do this: + // clone.SomeField = getOrCreateDeepFieldBasedCloner( + // original.SomeField.GetType() + // ).Invoke(original.SomeField); + fieldTransferExpressions.Add( + Expression.Assign( + Expression.Field(clone, fieldInfo), + Expression.Convert( + Expression.Call( + Expression.Call( + getOrCreateClonerMethodInfo, + Expression.Call( + Expression.Field(original, fieldInfo), getTypeMethodInfo + ) + ), + invokeMethodInfo, + Expression.Field(original, fieldInfo) + ), + fieldType + ) + ) + ); + } + + // Wrap up the generated array or complex reference type transfer expressions + // in a null check so the field is skipped if it is not holding an instance. + transferExpressions.Add( + Expression.IfThen( + Expression.NotEqual( + Expression.Field(original, fieldInfo), Expression.Constant(null) + ), + Expression.Block(fieldVariables, fieldTransferExpressions) + ) + ); + } + + } + +} // namespace Nuclex.Support.Cloning + +#endif // !(XBOX360 || WINDOWS_PHONE) diff --git a/Source/Cloning/ExpressionTreeCloner.PropertyBased.cs b/Source/Cloning/ExpressionTreeCloner.PropertyBased.cs new file mode 100644 index 0000000..fbef4bd --- /dev/null +++ b/Source/Cloning/ExpressionTreeCloner.PropertyBased.cs @@ -0,0 +1,39 @@ +#region CPL License +/* +Nuclex Framework +Copyright (C) 2002-2010 Nuclex Development Labs + +This library is free software; you can redistribute it and/or +modify it under the terms of the IBM Common Public License as +published by the IBM Corporation; either version 1.0 of the +License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +IBM Common Public License for more details. + +You should have received a copy of the IBM Common Public +License along with this library +*/ +#endregion + +#if !(XBOX360 || WINDOWS_PHONE) + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Reflection; + +namespace Nuclex.Support.Cloning { + + partial class ExpressionTreeCloner : ICloneFactory { + + // Not implement yet... + + } + +} // namespace Nuclex.Support.Cloning + +#endif // !(XBOX360 || WINDOWS_PHONE) diff --git a/Source/Cloning/ExpressionTreeCloner.Test.cs b/Source/Cloning/ExpressionTreeCloner.Test.cs index 9107a72..35d745a 100644 --- a/Source/Cloning/ExpressionTreeCloner.Test.cs +++ b/Source/Cloning/ExpressionTreeCloner.Test.cs @@ -27,131 +27,128 @@ 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); + /// 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); - } + Assert.AreNotSame(original, clone); + CollectionAssert.AreEqual(original, clone); + } -#if false - /// Verifies that shallow clones of arrays can be made - [Test] - public void ShallowClonesOfArraysCanBeMade() { - var original = new TestReferenceType[] { + /// Verifies that shallow clones of arrays can be made + [Test] + public void ShallowClonesOfArraysCanBeMade() { + var original = new TestReferenceType[] { new TestReferenceType() { TestField = 123, TestProperty = 456 } }; - TestReferenceType[] clone = this.cloneFactory.ShallowClone(original, false); + TestReferenceType[] clone = this.cloneFactory.ShallowClone(original, false); - Assert.AreSame(original[0], clone[0]); - } -#endif + Assert.AreSame(original[0], clone[0]); + } - /// Verifies that deep clones of arrays can be made - [Test] - public void DeepClonesOfArraysCanBeMade() { - var original = new TestReferenceType[,] { + /// 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, 0], clone[0, 0]); - Assert.AreEqual(original[0, 0].TestField, clone[0, 0].TestField); - Assert.AreEqual(original[0, 0].TestProperty, clone[0, 0].TestProperty); - } + 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); + } - /// Verifies that deep clones of a generic list can be made - [Test] - public void GenericListsCanBeCloned() { - var original = new List(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }); - List clone = this.cloneFactory.DeepClone(original, false); + /// Verifies that deep clones of a generic list can be made + [Test] + public void GenericListsCanBeCloned() { + var original = new List(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }); + List clone = this.cloneFactory.DeepClone(original, false); - CollectionAssert.AreEqual(original, clone); - } + CollectionAssert.AreEqual(original, clone); + } - /// Verifies that deep clones of a generic dictionary can be made - [Test] - public void GenericDictionariesCanBeCloned() { - var original = new Dictionary(); - original.Add(1, "one"); - Dictionary clone = this.cloneFactory.DeepClone(original, false); + /// Verifies that deep clones of a generic dictionary can be made + [Test] + public void GenericDictionariesCanBeCloned() { + var original = new Dictionary(); + original.Add(1, "one"); + Dictionary clone = this.cloneFactory.DeepClone(original, false); - Assert.AreEqual("one", clone[1]); - } + Assert.AreEqual("one", clone[1]); + } -#if false - /// - /// Verifies that a field-based shallow clone of a value type can be performed - /// - [Test] - public void ShallowFieldBasedClonesOfValueTypesCanBeMade() { - HierarchicalValueType original = CreateValueType(); - HierarchicalValueType clone = this.cloneFactory.ShallowClone(original, false); - VerifyClone(ref original, ref clone, isDeepClone: false, isPropertyBasedClone: false); - } + /// + /// Verifies that a field-based shallow clone of a value type can be performed + /// + [Test] + public void ShallowFieldBasedClonesOfValueTypesCanBeMade() { + HierarchicalValueType original = CreateValueType(); + HierarchicalValueType clone = this.cloneFactory.ShallowClone(original, false); + VerifyClone(ref original, ref clone, isDeepClone: false, isPropertyBasedClone: false); + } - /// - /// Verifies that a field-based shallow clone of a reference type can be performed - /// - [Test] - public void ShallowFieldBasedClonesOfReferenceTypesCanBeMade() { - HierarchicalReferenceType original = CreateReferenceType(); - HierarchicalReferenceType clone = this.cloneFactory.ShallowClone(original, false); - VerifyClone(original, clone, isDeepClone: false, isPropertyBasedClone: false); - } -#endif - /// - /// Verifies that a field-based deep clone of a value type can be performed - /// - [Test] - public void DeepFieldBasedClonesOfValueTypesCanBeMade() { - HierarchicalValueType original = CreateValueType(); - HierarchicalValueType clone = this.cloneFactory.DeepClone(original, false); - VerifyClone(ref original, ref clone, isDeepClone: true, isPropertyBasedClone: false); - } + /// + /// Verifies that a field-based shallow clone of a reference type can be performed + /// + [Test] + public void ShallowFieldBasedClonesOfReferenceTypesCanBeMade() { + HierarchicalReferenceType original = CreateReferenceType(); + HierarchicalReferenceType clone = this.cloneFactory.ShallowClone(original, false); + VerifyClone(original, clone, isDeepClone: false, isPropertyBasedClone: false); + } - /// - /// Verifies that a field-based deep clone of a reference type can be performed - /// - [Test] - public void DeepFieldBasedClonesOfReferenceTypesCanBeMade() { - HierarchicalReferenceType original = CreateReferenceType(); - HierarchicalReferenceType clone = this.cloneFactory.DeepClone(original, false); - VerifyClone(original, clone, isDeepClone: true, isPropertyBasedClone: false); - } + /// + /// Verifies that a field-based deep clone of a value type can be performed + /// + [Test] + public void DeepFieldBasedClonesOfValueTypesCanBeMade() { + HierarchicalValueType original = CreateValueType(); + HierarchicalValueType clone = this.cloneFactory.DeepClone(original, false); + VerifyClone(ref original, ref clone, isDeepClone: true, isPropertyBasedClone: false); + } + + /// + /// Verifies that a field-based deep clone of a reference type can be performed + /// + [Test] + public void DeepFieldBasedClonesOfReferenceTypesCanBeMade() { + HierarchicalReferenceType original = CreateReferenceType(); + HierarchicalReferenceType clone = this.cloneFactory.DeepClone(original, false); + VerifyClone(original, clone, isDeepClone: true, isPropertyBasedClone: false); + } #if false /// @@ -197,10 +194,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 7c960f4..e129395 100644 --- a/Source/Cloning/ExpressionTreeCloner.cs +++ b/Source/Cloning/ExpressionTreeCloner.cs @@ -28,25 +28,16 @@ using System.Reflection; 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; - /// /// Cloning factory which uses expression trees to improve performance when cloning /// is a high-frequency action. /// - public class ExpressionTreeCloner : ICloneFactory { + public partial class ExpressionTreeCloner : ICloneFactory { /// Initializes the static members of the expression tree cloner static ExpressionTreeCloner() { - shallowCloners = new ConcurrentDictionary>(); - deepCloners = new ConcurrentDictionary>(); + shallowFieldBasedCloners = new ConcurrentDictionary>(); + deepFieldBasedCloners = new ConcurrentDictionary>(); } /// @@ -87,7 +78,17 @@ namespace Nuclex.Support.Cloning { public static TCloned ShallowClone( TCloned objectToClone, bool usePropertyBasedClone ) { - throw new NotImplementedException("Not implemented yet"); + object objectToCloneAsObject = objectToClone; + if(objectToCloneAsObject == null) { + return default(TCloned); + } + + if(usePropertyBasedClone) { + throw new NotImplementedException("Not implemented yet"); + } else { + Func cloner = getOrCreateShallowFieldBasedCloner(typeof(TCloned)); + return (TCloned)cloner(objectToCloneAsObject); + } } /// @@ -121,478 +122,6 @@ namespace Nuclex.Support.Cloning { 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) { - Func cloner; - - if(!deepCloners.TryGetValue(clonedType, out cloner)) { - cloner = createDeepFieldBasedCloner(clonedType); - deepCloners.TryAdd(clonedType, cloner); - } - - return cloner; - } - - /// - /// 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 - /// The variable holding the cloned array - private static Expression generatePrimitiveArrayTransferExpressions( - Type clonedType, - Expression original, - ICollection variables, - ICollection transferExpressions - ) { - MethodInfo arrayCloneMethodInfo = typeof(Array).GetMethod("Clone"); - return Expression.Convert( - Expression.Call( - Expression.Convert(original, typeof(Array)), arrayCloneMethodInfo - ), - clonedType - ); - } - - /// - /// 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 - /// The variable holding the cloned array - private static ParameterExpression generateComplexArrayTransferExpressions( - Type clonedType, - Expression original, - IList variables, - ICollection transferExpressions - ) { - // We need a temporary variable in order to transfer the elements of the array - ParameterExpression clone = Expression.Variable(clonedType); - variables.Add(clone); - - int dimensionCount = clonedType.GetArrayRank(); - int baseVariableIndex = variables.Count; - Type elementType = clonedType.GetElementType(); - - var lengths = new List(); - var indexes = new List(); - var labels = new List(); - - // Retrieve the length of each of the array's dimensions - MethodInfo arrayGetLengthMethodInfo = typeof(Array).GetMethod("GetLength"); - for(int index = 0; index < dimensionCount; ++index) { - lengths.Add(Expression.Variable(typeof(int))); - variables.Add(lengths[index]); - - indexes.Add(Expression.Variable(typeof(int))); - variables.Add(indexes[index]); - - labels.Add(Expression.Label()); - - transferExpressions.Add( - Expression.Assign( - lengths[index], - Expression.Call( - original, arrayGetLengthMethodInfo, Expression.Constant(index) - ) - ) - ); - } - - // Create a new (empty) array with the same dimensions and lengths as the original - transferExpressions.Add( - Expression.Assign( - clone, 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. - transferExpressions.Add( - Expression.Assign(indexes[0], Expression.Constant(0)) - ); - - // We use a temporary variable to store the element - ParameterExpression element = Expression.Variable(elementType); - variables.Add(element); - - // Build the nested loops (one for each dimension) from the inside out - Expression innerLoop = null; - for(int index = dimensionCount - 1; index >= 0; --index) { - var loopVariables = new List(); - var loopExpressions = new List(); - - // If we reached the end of the current array dimension, break the loop - loopExpressions.Add( - Expression.IfThen( - Expression.GreaterThanOrEqual(indexes[index], lengths[index]), - Expression.Break(labels[index]) - ) - ); - - if(innerLoop == null) { - // The innermost loop clones an actual array element - if(elementType.IsPrimitive || (elementType == typeof(string))) { - loopExpressions.Add( - Expression.Assign( - Expression.ArrayAccess(clone, indexes), - Expression.ArrayAccess(original, indexes) - ) - ); - } else if(elementType.IsValueType) { - generateComplexTypeTransferExpressions( - elementType, - Expression.ArrayAccess(original, indexes), - Expression.ArrayAccess(clone, indexes), - variables, - loopExpressions - ); - } else { - ParameterExpression originalElement = Expression.Variable(elementType); - loopVariables.Add(originalElement); - - loopExpressions.Add( - Expression.Assign(originalElement, Expression.ArrayAccess(original, indexes)) - ); - - var nestedVariables = new List(); - var nestedTransferExpressions = new List(); - - if(elementType.IsArray) { - Expression clonedElement; - - Type nestedElementType = elementType.GetElementType(); - if(nestedElementType.IsPrimitive || (nestedElementType == typeof(string))) { - clonedElement = generatePrimitiveArrayTransferExpressions( - elementType, originalElement, nestedVariables, nestedTransferExpressions - ); - } else { - clonedElement = generateComplexArrayTransferExpressions( - elementType, originalElement, nestedVariables, nestedTransferExpressions - ); - } - nestedTransferExpressions.Add( - Expression.Assign(Expression.ArrayAccess(clone, indexes), clonedElement) - ); - } else { - // Complex types are cloned by checking their actual, concrete type (fields - // may be typed to an interface or base class) and requesting a cloner for that - // type during runtime - MethodInfo getOrCreateClonerMethodInfo = typeof(ExpressionTreeCloner).GetMethod( - "getOrCreateDeepFieldBasedCloner", - BindingFlags.NonPublic | BindingFlags.Static - ); - MethodInfo getTypeMethodInfo = typeof(object).GetMethod("GetType"); - MethodInfo invokeMethodInfo = typeof(Func).GetMethod("Invoke"); - - // Generate expressions to do this: - // clone.SomeField = getOrCreateDeepFieldBasedCloner( - // original.SomeField.GetType() - // ).Invoke(original.SomeField); - nestedTransferExpressions.Add( - Expression.Assign( - Expression.ArrayAccess(clone, indexes), - Expression.Convert( - Expression.Call( - Expression.Call( - getOrCreateClonerMethodInfo, - Expression.Call(originalElement, getTypeMethodInfo) - ), - invokeMethodInfo, - originalElement - ), - elementType - ) - ) - ); - } - - loopExpressions.Add( - Expression.IfThen( - Expression.NotEqual(originalElement, Expression.Constant(null)), - Expression.Block( - nestedVariables, - nestedTransferExpressions - ) - ) - ); - } - - } 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. - transferExpressions.Add(innerLoop); - - return clone; - } - - /// Generates state transfer expressions to copy a complex type - /// Complex type that will be cloned - /// Variable expression for the original instance - /// Variable expression for the cloned instance - /// Receives variables used by the transfer expressions - /// Receives the generated transfer expressions - private static void generateComplexTypeTransferExpressions( - Type clonedType, // Actual, concrete type (not declared type) - Expression original, // Expected to be an object - Expression clone, // As actual, concrete type - IList variables, - ICollection transferExpressions - ) { - // Enumerate all of the type's fields and generate transfer expressions for each - 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 || (fieldType == typeof(string))) { - // Primitive types and strings can be transferred by simple assignment - transferExpressions.Add( - Expression.Assign( - Expression.Field(clone, fieldInfo), - Expression.Field(original, 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. - generateComplexTypeTransferExpressions( - fieldType, - Expression.Field(original, fieldInfo), - Expression.Field(clone, fieldInfo), - variables, - transferExpressions - ); - } else { - generateReferenceTypeTransferExpressions( - original, clone, transferExpressions, fieldInfo, fieldType - ); - } - } - } - - /// - /// Generates the expressions to transfer a reference type (array or class) - /// - /// Original value that will be cloned - /// Variable that will receive the cloned value - /// - /// Receives the expression generated to transfer the values - /// - /// Reflection informations about the field being cloned - /// Type of the field being cloned - private static void generateReferenceTypeTransferExpressions( - Expression original, - Expression clone, - ICollection transferExpressions, - FieldInfo fieldInfo, - Type fieldType - ) { - // 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 fieldTransferExpressions = new List(); - var fieldVariables = new List(); - - if(fieldType.IsArray) { - // Arrays need to be cloned element-by-element - Expression fieldClone; - - Type elementType = fieldType.GetElementType(); - if(elementType.IsPrimitive || (elementType == typeof(string))) { - // For primitive arrays, the Array.Clone() method is sufficient - fieldClone = generatePrimitiveArrayTransferExpressions( - fieldType, - Expression.Field(original, fieldInfo), - fieldVariables, - fieldTransferExpressions - ); - } else { - // Arrays of complex types require manual cloning - fieldClone = generateComplexArrayTransferExpressions( - fieldType, - Expression.Field(original, fieldInfo), - fieldVariables, - fieldTransferExpressions - ); - } - - // Add the assignment to the transfer expressions. The array transfer expression - // generator will either have set up a temporary variable to hold the array or - // returned the conversion expression straight away - fieldTransferExpressions.Add( - Expression.Assign(Expression.Field(clone, fieldInfo), fieldClone) - ); - } else { - // Complex types are cloned by checking their actual, concrete type (fields - // may be typed to an interface or base class) and requesting a cloner for that - // type during runtime - MethodInfo getOrCreateClonerMethodInfo = typeof(ExpressionTreeCloner).GetMethod( - "getOrCreateDeepFieldBasedCloner", - BindingFlags.NonPublic | BindingFlags.Static - ); - MethodInfo getTypeMethodInfo = typeof(object).GetMethod("GetType"); - MethodInfo invokeMethodInfo = typeof(Func).GetMethod("Invoke"); - - // Generate expressions to do this: - // clone.SomeField = getOrCreateDeepFieldBasedCloner( - // original.SomeField.GetType() - // ).Invoke(original.SomeField); - fieldTransferExpressions.Add( - Expression.Assign( - Expression.Field(clone, fieldInfo), - Expression.Convert( - Expression.Call( - Expression.Call( - getOrCreateClonerMethodInfo, - Expression.Call( - Expression.Field(original, fieldInfo), getTypeMethodInfo - ) - ), - invokeMethodInfo, - Expression.Field(original, fieldInfo) - ), - fieldType - ) - ) - ); - } - - // Wrap up the generated array or complex reference type transfer expressions - // in a null check so the field is skipped if it is not holding an instance. - transferExpressions.Add( - Expression.IfThen( - Expression.NotEqual( - Expression.Field(original, fieldInfo), Expression.Constant(null) - ), - Expression.Block(fieldVariables, fieldTransferExpressions) - ) - ); - } - - /// 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 - /// - /// - /// The 'null' check is supposed to take place before running the cloner. This - /// avoids having redundant 'null' checks on nested types - first before calling - /// GetType() on the field to be cloned and second when runner the matching - /// cloner for the field. - /// - /// - /// This design also enables the cloning of nested value types (which can never - /// be null) without any null check whatsoever. - /// - /// - private static Func createDeepFieldBasedCloner(Type clonedType) { - ParameterExpression original = Expression.Parameter(typeof(object), "original"); - - var transferExpressions = new List(); - var variables = new List(); - - if(clonedType.IsPrimitive || (clonedType == typeof(string))) { - // Primitives and strings are copied on direct assignment - transferExpressions.Add(original); - } else if(clonedType.IsArray) { - // Arrays need to be cloned element-by-element - Type elementType = clonedType.GetElementType(); - - if(elementType.IsPrimitive || (elementType == typeof(string))) { - // For primitive arrays, the Array.Clone() method is sufficient - transferExpressions.Add( - generatePrimitiveArrayTransferExpressions( - clonedType, original, variables, transferExpressions - ) - ); - } else { - // To access the fields of the original type, we need it to be of the actual - // type instead of an object, so perform a downcast - ParameterExpression typedOriginal = Expression.Variable(clonedType); - variables.Add(typedOriginal); - transferExpressions.Add( - Expression.Assign(typedOriginal, Expression.Convert(original, clonedType)) - ); - - // Arrays of complex types require manual cloning - transferExpressions.Add( - generateComplexArrayTransferExpressions( - clonedType, typedOriginal, variables, transferExpressions - ) - ); - } - } else { - // We need a variable to hold the clone because due to the assignments it - // won't be last in the block when we're finished - ParameterExpression clone = Expression.Variable(clonedType); - variables.Add(clone); - - // Give it a new instance of the type being cloned - transferExpressions.Add(Expression.Assign(clone, Expression.New(clonedType))); - - // To access the fields of the original type, we need it to be of the actual - // type instead of an object, so perform a downcast - ParameterExpression typedOriginal = Expression.Variable(clonedType); - variables.Add(typedOriginal); - transferExpressions.Add( - Expression.Assign(typedOriginal, Expression.Convert(original, clonedType)) - ); - - // Generate the expressions required to transfer the type field by field - generateComplexTypeTransferExpressions( - clonedType, typedOriginal, clone, variables, transferExpressions - ); - - // Make sure the clone is the last thing in the block to set the return value - transferExpressions.Add(clone); - } - - // Turn all transfer expressions into a single block if necessary - Expression resultExpression; - if((transferExpressions.Count == 1) && (variables.Count == 0)) { - resultExpression = transferExpressions[0]; - } else { - resultExpression = Expression.Block(variables, transferExpressions); - } - - // Value types require manual boxing - if(clonedType.IsValueType) { - resultExpression = Expression.Convert(resultExpression, typeof(object)); - } - - return Expression.Lambda>(resultExpression, original).Compile(); - } - #if false /// /// Transfers the state of one object into another, creating clones of referenced objects @@ -651,10 +180,48 @@ namespace Nuclex.Support.Cloning { #endif + /// + /// 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 getOrCreateShallowFieldBasedCloner(Type clonedType) { + Func cloner; + + if(!shallowFieldBasedCloners.TryGetValue(clonedType, out cloner)) { + cloner = createShallowFieldBasedCloner(clonedType); + shallowFieldBasedCloners.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) { + Func cloner; + + if(!deepFieldBasedCloners.TryGetValue(clonedType, out cloner)) { + cloner = createDeepFieldBasedCloner(clonedType); + deepFieldBasedCloners.TryAdd(clonedType, cloner); + } + + return cloner; + } + /// Compiled cloners that perform shallow clone operations - private static ConcurrentDictionary> shallowCloners; + private static ConcurrentDictionary> shallowFieldBasedCloners; /// Compiled cloners that perform deep clone operations - private static ConcurrentDictionary> deepCloners; + private static ConcurrentDictionary> deepFieldBasedCloners; + /// Compiled cloners that perform shallow clone operations + private static ConcurrentDictionary> shallowPropertyBasedCloners; + /// Compiled cloners that perform deep clone operations + private static ConcurrentDictionary> deepPropertyBasedCloners; }