From db8c93eabd2a5d688f9b0658c94a9016a7ae7829 Mon Sep 17 00:00:00 2001 From: Markus Ewald Date: Wed, 8 Feb 2012 15:20:39 +0000 Subject: [PATCH] All cloners are now able to clone objects without default constructors; began work on a property-based expression tree cloner git-svn-id: file:///srv/devel/repo-conversion/nusu@243 d2e56fa2-650e-0410-a79f-9358c0239efd --- Documents/Nuclex.Support.txt | 6 + Source/Cloning/CloneFactoryTest.cs | 2 +- .../ExpressionTreeCloner.FieldBased.cs | 46 +- .../ExpressionTreeCloner.PropertyBased.cs | 472 +++++++++++++++++- Source/Cloning/ExpressionTreeCloner.Test.cs | 202 ++++---- Source/Cloning/ReflectionCloner.Test.cs | 2 +- Source/Cloning/ReflectionCloner.cs | 5 +- Source/Cloning/SerializationCloner.Test.cs | 2 +- 8 files changed, 613 insertions(+), 124 deletions(-) diff --git a/Documents/Nuclex.Support.txt b/Documents/Nuclex.Support.txt index 4e29d84..8dbb765 100644 --- a/Documents/Nuclex.Support.txt +++ b/Documents/Nuclex.Support.txt @@ -26,3 +26,9 @@ #endif + +Benchmark of cloners: + + SerializationCloner took 13424 ms + ReflectionCloner took 2126 ms + ExpressionTreeCloner took 171 ms diff --git a/Source/Cloning/CloneFactoryTest.cs b/Source/Cloning/CloneFactoryTest.cs index 02d6ee9..1dd5d9c 100644 --- a/Source/Cloning/CloneFactoryTest.cs +++ b/Source/Cloning/CloneFactoryTest.cs @@ -27,7 +27,7 @@ using NUnit.Framework; namespace Nuclex.Support.Cloning { /// Base class for unit tests verifying the clone factory - public abstract class CloneFactoryTest { + internal abstract class CloneFactoryTest { #region class TestReferenceType diff --git a/Source/Cloning/ExpressionTreeCloner.FieldBased.cs b/Source/Cloning/ExpressionTreeCloner.FieldBased.cs index bd010a2..c280d28 100644 --- a/Source/Cloning/ExpressionTreeCloner.FieldBased.cs +++ b/Source/Cloning/ExpressionTreeCloner.FieldBased.cs @@ -24,6 +24,7 @@ using System; using System.Collections.Generic; using System.Linq.Expressions; using System.Reflection; +using System.Runtime.Serialization; namespace Nuclex.Support.Cloning { @@ -60,7 +61,7 @@ namespace Nuclex.Support.Cloning { if(elementType.IsPrimitive || (elementType == typeof(string))) { // For primitive arrays, the Array.Clone() method is sufficient transferExpressions.Add( - generatePrimitiveArrayTransferExpressions( + generateFieldBasedPrimitiveArrayTransferExpressions( clonedType, original, variables, transferExpressions ) ); @@ -75,7 +76,7 @@ namespace Nuclex.Support.Cloning { // Arrays of complex types require manual cloning transferExpressions.Add( - generateComplexArrayTransferExpressions( + generateFieldBasedComplexArrayTransferExpressions( clonedType, typedOriginal, variables, transferExpressions ) ); @@ -87,7 +88,20 @@ namespace Nuclex.Support.Cloning { variables.Add(clone); // Give it a new instance of the type being cloned - transferExpressions.Add(Expression.Assign(clone, Expression.New(clonedType))); + MethodInfo getUninitializedObjectMethodInfo = typeof(FormatterServices).GetMethod( + "GetUninitializedObject", BindingFlags.Static | BindingFlags.Public + ); + transferExpressions.Add( + Expression.Assign( + clone, + Expression.Convert( + Expression.Call( + getUninitializedObjectMethodInfo, Expression.Constant(clonedType) + ), + 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 @@ -98,7 +112,7 @@ namespace Nuclex.Support.Cloning { ); // Generate the expressions required to transfer the type field by field - generateComplexTypeTransferExpressions( + generateFieldBasedComplexTypeTransferExpressions( clonedType, typedOriginal, clone, variables, transferExpressions ); @@ -136,7 +150,7 @@ namespace Nuclex.Support.Cloning { transferExpressions.Add(original); } else if(clonedType.IsArray) { transferExpressions.Add( - generatePrimitiveArrayTransferExpressions( + generateFieldBasedPrimitiveArrayTransferExpressions( clonedType, original, variables, transferExpressions ) ); @@ -201,7 +215,7 @@ namespace Nuclex.Support.Cloning { /// Receives variables used by the transfer expressions /// Receives the generated transfer expressions /// The variable holding the cloned array - private static Expression generatePrimitiveArrayTransferExpressions( + private static Expression generateFieldBasedPrimitiveArrayTransferExpressions( Type clonedType, Expression original, ICollection variables, @@ -224,7 +238,7 @@ namespace Nuclex.Support.Cloning { /// Receives variables used by the transfer expressions /// Receives the generated transfer expressions /// The variable holding the cloned array - private static ParameterExpression generateComplexArrayTransferExpressions( + private static ParameterExpression generateFieldBasedComplexArrayTransferExpressions( Type clonedType, Expression original, IList variables, @@ -310,7 +324,7 @@ namespace Nuclex.Support.Cloning { // 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( + generateFieldBasedComplexTypeTransferExpressions( elementType, Expression.ArrayAccess(original, indexes), Expression.ArrayAccess(clone, indexes), @@ -338,11 +352,11 @@ namespace Nuclex.Support.Cloning { Type nestedElementType = elementType.GetElementType(); if(nestedElementType.IsPrimitive || (nestedElementType == typeof(string))) { - clonedElement = generatePrimitiveArrayTransferExpressions( + clonedElement = generateFieldBasedPrimitiveArrayTransferExpressions( elementType, originalElement, nestedVariables, nestedTransferExpressions ); } else { - clonedElement = generateComplexArrayTransferExpressions( + clonedElement = generateFieldBasedComplexArrayTransferExpressions( elementType, originalElement, nestedVariables, nestedTransferExpressions ); } @@ -427,7 +441,7 @@ namespace Nuclex.Support.Cloning { /// Variable expression for the cloned instance /// Receives variables used by the transfer expressions /// Receives the generated transfer expressions - private static void generateComplexTypeTransferExpressions( + private static void generateFieldBasedComplexTypeTransferExpressions( Type clonedType, // Actual, concrete type (not declared type) Expression original, // Expected to be an object Expression clone, // As actual, concrete type @@ -454,7 +468,7 @@ namespace Nuclex.Support.Cloning { } 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( + generateFieldBasedComplexTypeTransferExpressions( fieldType, Expression.Field(original, fieldInfo), Expression.Field(clone, fieldInfo), @@ -462,7 +476,7 @@ namespace Nuclex.Support.Cloning { transferExpressions ); } else { - generateReferenceTypeTransferExpressions( + generateFieldBasedReferenceTypeTransferExpressions( original, clone, transferExpressions, fieldInfo, fieldType ); } @@ -479,7 +493,7 @@ namespace Nuclex.Support.Cloning { /// /// Reflection informations about the field being cloned /// Type of the field being cloned - private static void generateReferenceTypeTransferExpressions( + private static void generateFieldBasedReferenceTypeTransferExpressions( Expression original, Expression clone, ICollection transferExpressions, @@ -498,7 +512,7 @@ namespace Nuclex.Support.Cloning { Type elementType = fieldType.GetElementType(); if(elementType.IsPrimitive || (elementType == typeof(string))) { // For primitive arrays, the Array.Clone() method is sufficient - fieldClone = generatePrimitiveArrayTransferExpressions( + fieldClone = generateFieldBasedPrimitiveArrayTransferExpressions( fieldType, Expression.Field(original, fieldInfo), fieldVariables, @@ -506,7 +520,7 @@ namespace Nuclex.Support.Cloning { ); } else { // Arrays of complex types require manual cloning - fieldClone = generateComplexArrayTransferExpressions( + fieldClone = generateFieldBasedComplexArrayTransferExpressions( fieldType, Expression.Field(original, fieldInfo), fieldVariables, diff --git a/Source/Cloning/ExpressionTreeCloner.PropertyBased.cs b/Source/Cloning/ExpressionTreeCloner.PropertyBased.cs index fbef4bd..876ce47 100644 --- a/Source/Cloning/ExpressionTreeCloner.PropertyBased.cs +++ b/Source/Cloning/ExpressionTreeCloner.PropertyBased.cs @@ -21,7 +21,6 @@ License along with this library #if !(XBOX360 || WINDOWS_PHONE) using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq.Expressions; using System.Reflection; @@ -30,7 +29,476 @@ namespace Nuclex.Support.Cloning { partial class ExpressionTreeCloner : ICloneFactory { - // Not implement yet... +#if false + + /// 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 property to be cloned and second when runner the matching + /// cloner for the property. + /// + /// + /// This design also enables the cloning of nested value types (which can never + /// be null) without any null check whatsoever. + /// + /// + private static Func createDeepPropertyBasedCloner(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( + generatePropertyBasedPrimitiveArrayTransferExpressions( + clonedType, original, variables, transferExpressions + ) + ); + } else { + // To access the propertys 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( + generatePropertyBasedComplexArrayTransferExpressions( + 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 propertys 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 property by property + generatePropertyBasedComplexTypeTransferExpressions( + 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(); + } + + /// + /// 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 generatePropertyBasedPrimitiveArrayTransferExpressions( + 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 generatePropertyBasedComplexArrayTransferExpressions( + 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)) + ); + + // 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 propertys + // of the source array element to the destination array element (cloning + // any nested reference types appropriately) + generatePropertyBasedComplexTypeTransferExpressions( + 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 propertys 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 = generatePropertyBasedPrimitiveArrayTransferExpressions( + elementType, originalElement, nestedVariables, nestedTransferExpressions + ); + } else { + clonedElement = generatePropertyBasedComplexArrayTransferExpressions( + elementType, originalElement, nestedVariables, nestedTransferExpressions + ); + } + nestedTransferExpressions.Add( + Expression.Assign(Expression.ArrayAccess(clone, indexes), clonedElement) + ); + } else { + // Complex types are cloned by checking their actual, concrete type (propertys + // may be typed to an interface or base class) and requesting a cloner for that + // type during runtime + MethodInfo getOrCreateClonerMethodInfo = typeof(ExpressionTreeCloner).GetMethod( + "getOrCreateDeepPropertyBasedCloner", + BindingFlags.NonPublic | BindingFlags.Static + ); + MethodInfo getTypeMethodInfo = typeof(object).GetMethod("GetType"); + MethodInfo invokeMethodInfo = typeof(Func).GetMethod("Invoke"); + + // Generate expressions to do this: + // clone.SomeProperty = getOrCreateDeepPropertyBasedCloner( + // original.SomeProperty.GetType() + // ).Invoke(original.SomeProperty); + 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 generatePropertyBasedComplexTypeTransferExpressions( + 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 propertys and generate transfer expressions for each + PropertyInfo[] propertyInfos = clonedType.GetProperties( + BindingFlags.Public | BindingFlags.NonPublic | + BindingFlags.Instance | BindingFlags.FlattenHierarchy + ); + for(int index = 0; index < propertyInfos.Length; ++index) { + PropertyInfo propertyInfo = propertyInfos[index]; + Type propertyType = propertyInfo.PropertyType; + + if(propertyType.IsPrimitive || (propertyType == typeof(string))) { + // Primitive types and strings can be transferred by simple assignment + transferExpressions.Add( + Expression.Assign( + Expression.Property(clone, propertyInfo), + Expression.Property(original, propertyInfo) + ) + ); + } else if(propertyType.IsValueType) { + // A nested value type is part of the parent and will have its propertys directly + // assigned without boxing, new instance creation or anything like that. + generatePropertyBasedComplexTypeTransferExpressions( + propertyType, + Expression.Property(original, propertyInfo), + Expression.Property(clone, propertyInfo), + variables, + transferExpressions + ); + } else { + generatePropertyBasedReferenceTypeTransferExpressions( + original, clone, transferExpressions, propertyInfo, propertyType + ); + } + } + } + + /// + /// 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 property being cloned + /// Type of the property being cloned + private static void generatePropertyBasedReferenceTypeTransferExpressions( + Expression original, + Expression clone, + ICollection transferExpressions, + PropertyInfo propertyInfo, + Type propertyType + ) { + // 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 propertyTransferExpressions = new List(); + var propertyVariables = new List(); + + if(propertyType.IsArray) { + // Arrays need to be cloned element-by-element + Expression propertyClone; + + Type elementType = propertyType.GetElementType(); + if(elementType.IsPrimitive || (elementType == typeof(string))) { + // For primitive arrays, the Array.Clone() method is sufficient + propertyClone = generatePropertyBasedPrimitiveArrayTransferExpressions( + propertyType, + Expression.Property(original, propertyInfo), + propertyVariables, + propertyTransferExpressions + ); + } else { + // Arrays of complex types require manual cloning + propertyClone = generatePropertyBasedComplexArrayTransferExpressions( + propertyType, + Expression.Property(original, propertyInfo), + propertyVariables, + propertyTransferExpressions + ); + } + + // 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 + propertyTransferExpressions.Add( + Expression.Assign(Expression.Property(clone, propertyInfo), propertyClone) + ); + } else { + // Complex types are cloned by checking their actual, concrete type (propertys + // may be typed to an interface or base class) and requesting a cloner for that + // type during runtime + MethodInfo getOrCreateClonerMethodInfo = typeof(ExpressionTreeCloner).GetMethod( + "getOrCreateDeepPropertyBasedCloner", + BindingFlags.NonPublic | BindingFlags.Static + ); + MethodInfo getTypeMethodInfo = typeof(object).GetMethod("GetType"); + MethodInfo invokeMethodInfo = typeof(Func).GetMethod("Invoke"); + + // Generate expressions to do this: + // clone.SomeProperty = getOrCreateDeepPropertyBasedCloner( + // original.SomeProperty.GetType() + // ).Invoke(original.SomeProperty); + propertyTransferExpressions.Add( + Expression.Assign( + Expression.Property(clone, propertyInfo), + Expression.Convert( + Expression.Call( + Expression.Call( + getOrCreateClonerMethodInfo, + Expression.Call( + Expression.Property(original, propertyInfo), getTypeMethodInfo + ) + ), + invokeMethodInfo, + Expression.Property(original, propertyInfo) + ), + propertyType + ) + ) + ); + } + + // Wrap up the generated array or complex reference type transfer expressions + // in a null check so the property is skipped if it is not holding an instance. + transferExpressions.Add( + Expression.IfThen( + Expression.NotEqual( + Expression.Property(original, propertyInfo), Expression.Constant(null) + ), + Expression.Block(propertyVariables, propertyTransferExpressions) + ) + ); + } + +#endif } diff --git a/Source/Cloning/ExpressionTreeCloner.Test.cs b/Source/Cloning/ExpressionTreeCloner.Test.cs index 35d745a..e0f4024 100644 --- a/Source/Cloning/ExpressionTreeCloner.Test.cs +++ b/Source/Cloning/ExpressionTreeCloner.Test.cs @@ -27,128 +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] + internal 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); + } - /// 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]); - } + 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]); + } - /// - /// 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); - } + /// + /// 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 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 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); - } + /// + /// 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 /// @@ -194,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/ReflectionCloner.Test.cs b/Source/Cloning/ReflectionCloner.Test.cs index 28da7c1..1966a69 100644 --- a/Source/Cloning/ReflectionCloner.Test.cs +++ b/Source/Cloning/ReflectionCloner.Test.cs @@ -29,7 +29,7 @@ namespace Nuclex.Support.Cloning { /// Unit Test for the reflection-based cloner [TestFixture] - public class ReflectionClonerTest : CloneFactoryTest { + internal class ReflectionClonerTest : CloneFactoryTest { /// Initializes a new unit test suite for the reflection cloner public ReflectionClonerTest() { diff --git a/Source/Cloning/ReflectionCloner.cs b/Source/Cloning/ReflectionCloner.cs index df30ef0..c7e7466 100644 --- a/Source/Cloning/ReflectionCloner.cs +++ b/Source/Cloning/ReflectionCloner.cs @@ -20,6 +20,7 @@ License along with this library using System; using System.Reflection; +using System.Runtime.Serialization; namespace Nuclex.Support.Cloning { @@ -119,7 +120,7 @@ namespace Nuclex.Support.Cloning { /// A clone of the original instance private static object shallowCloneComplexFieldBased(object original) { Type originalType = original.GetType(); - object clone = Activator.CreateInstance(originalType); + object clone = FormatterServices.GetUninitializedObject(originalType); FieldInfo[] fieldInfos = originalType.GetFields( BindingFlags.Public | BindingFlags.NonPublic | @@ -196,7 +197,7 @@ namespace Nuclex.Support.Cloning { /// A clone of the original instance private static object deepCloneComplexFieldBased(object original) { Type originalType = original.GetType(); - object clone = Activator.CreateInstance(originalType); + object clone = FormatterServices.GetUninitializedObject(originalType); FieldInfo[] fieldInfos = originalType.GetFields( BindingFlags.Public | BindingFlags.NonPublic | diff --git a/Source/Cloning/SerializationCloner.Test.cs b/Source/Cloning/SerializationCloner.Test.cs index f4bcd01..aa24f56 100644 --- a/Source/Cloning/SerializationCloner.Test.cs +++ b/Source/Cloning/SerializationCloner.Test.cs @@ -29,7 +29,7 @@ namespace Nuclex.Support.Cloning { /// Unit Test for the binary serializer-based cloner [TestFixture] - public class SerializationClonerTest : CloneFactoryTest { + internal class SerializationClonerTest : CloneFactoryTest { /// Initializes a new unit test suite for the reflection cloner public SerializationClonerTest() {