diff --git a/Nuclex.Support (net-4.0).csproj b/Nuclex.Support (net-4.0).csproj index 70420ef..6519c0d 100644 --- a/Nuclex.Support (net-4.0).csproj +++ b/Nuclex.Support (net-4.0).csproj @@ -63,6 +63,9 @@ + + ExpressionTreeCloner.cs + diff --git a/Nuclex.Support (xna-4.0-phone7).csproj b/Nuclex.Support (xna-4.0-phone7).csproj index 698f1b1..b0e753a 100644 --- a/Nuclex.Support (xna-4.0-phone7).csproj +++ b/Nuclex.Support (xna-4.0-phone7).csproj @@ -94,6 +94,9 @@ + + ExpressionTreeCloner.cs + diff --git a/Nuclex.Support (xna-4.0-xbox360).csproj b/Nuclex.Support (xna-4.0-xbox360).csproj index 7b32470..fe0c9e7 100644 --- a/Nuclex.Support (xna-4.0-xbox360).csproj +++ b/Nuclex.Support (xna-4.0-xbox360).csproj @@ -105,6 +105,9 @@ + + ExpressionTreeCloner.cs + diff --git a/Source/Cloning/ExpressionTreeCloner.Test.cs b/Source/Cloning/ExpressionTreeCloner.Test.cs new file mode 100644 index 0000000..618338a --- /dev/null +++ b/Source/Cloning/ExpressionTreeCloner.Test.cs @@ -0,0 +1,177 @@ +#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 UNITTEST + +using System; +using System.Collections.Generic; + +using NUnit.Framework; + +namespace Nuclex.Support.Cloning { + + /// 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(); + } + + /// Verifies that clones of primitive types can be created + [Test] + public void PrimitiveTypesCanBeCloned() { + int original = 12345; + int clone = this.cloneFactory.ShallowClone(original, false); + Assert.AreEqual(original, clone); + } + + /// 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); + + Assert.AreSame(original[0], clone[0]); + } + + /// 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); + + Assert.AreNotSame(original[0], clone[0]); + Assert.AreEqual(original[0].TestField, clone[0].TestField); + Assert.AreEqual(original[0].TestProperty, clone[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); + + 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); + + 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 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 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 property-based shallow clone of a value type can be performed + /// + [Test] + public void ShallowPropertyBasedClonesOfValueTypesCanBeMade() { + HierarchicalValueType original = CreateValueType(); + HierarchicalValueType clone = this.cloneFactory.ShallowClone(original, true); + VerifyClone(ref original, ref clone, isDeepClone: false, isPropertyBasedClone: true); + } + + /// + /// Verifies that a property-based shallow clone of a reference type can be performed + /// + [Test] + public void ShallowPropertyBasedClonesOfReferenceTypesCanBeMade() { + HierarchicalReferenceType original = CreateReferenceType(); + HierarchicalReferenceType clone = this.cloneFactory.ShallowClone(original, true); + VerifyClone(original, clone, isDeepClone: false, isPropertyBasedClone: true); + } + + /// + /// Verifies that a property-based deep clone of a value type can be performed + /// + [Test] + public void DeepPropertyBasedClonesOfValueTypesCanBeMade() { + HierarchicalValueType original = CreateValueType(); + HierarchicalValueType clone = this.cloneFactory.DeepClone(original, true); + VerifyClone(ref original, ref clone, isDeepClone: true, isPropertyBasedClone: true); + } + + /// + /// Verifies that a property-based deep clone of a reference type can be performed + /// + [Test] + public void DeepPropertyBasedClonesOfReferenceTypesCanBeMade() { + HierarchicalReferenceType original = CreateReferenceType(); + HierarchicalReferenceType clone = this.cloneFactory.DeepClone(original, true); + VerifyClone(original, clone, isDeepClone: true, isPropertyBasedClone: true); + } + + /// Clone factory being tested + private ICloneFactory cloneFactory; + + } + +} // namespace Nuclex.Support.Cloning + +#endif // UNITTEST diff --git a/Source/Cloning/ExpressionTreeCloner.cs b/Source/Cloning/ExpressionTreeCloner.cs index 3086634..cedc9af 100644 --- a/Source/Cloning/ExpressionTreeCloner.cs +++ b/Source/Cloning/ExpressionTreeCloner.cs @@ -18,15 +18,13 @@ License along with this library */ #endregion +#if !(XBOX360 || WINDOWS_PHONE) + using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.Collections.Concurrent; namespace Nuclex.Support.Cloning { -#if false - /// 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 @@ -40,7 +38,13 @@ namespace Nuclex.Support.Cloning { /// Cloning factory which uses expression trees to improve performance when cloning /// is a high-frequency action. /// - public class ExpressionTreeCloneFactory : ICloneFactory { + public class ExpressionTreeCloner : ICloneFactory { + + /// 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 @@ -52,9 +56,10 @@ namespace Nuclex.Support.Cloning { /// Whether to clone the object based on its properties only /// /// A deep clone of the provided object - public TCloned DeepClone(TCloned objectToClone, bool usePropertyBasedClone) - where TCloned : new() { - throw new NotImplementedException(); + public static TCloned DeepClone( + TCloned objectToClone, bool usePropertyBasedClone + ) { + throw new NotImplementedException("Not implemented yet"); } /// @@ -66,11 +71,44 @@ namespace Nuclex.Support.Cloning { /// Whether to clone the object based on its properties only /// /// A shallow clone of the provided object - public TCloned ShallowClone(TCloned objectToClone, bool usePropertyBasedClone) - where TCloned : new() { - throw new NotImplementedException(); + 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 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); + } + +#if false /// /// Transfers the state of one object into another, creating clones of referenced objects /// @@ -145,9 +183,15 @@ namespace Nuclex.Support.Cloning { where TCloned : class, new() { throw new NotImplementedException(); } +#endif + + /// Compiled cloners that perform shallow clone operations + private static ConcurrentDictionary> shallowCloners; + /// Compiled cloners that perform deep clone operations + private static ConcurrentDictionary> deepCloners; } -#endif - } // namespace Nuclex.Support.Cloning + +#endif // !(XBOX360 || WINDOWS_PHONE) diff --git a/Source/Cloning/ReflectionCloner.cs b/Source/Cloning/ReflectionCloner.cs index a145255..df30ef0 100644 --- a/Source/Cloning/ReflectionCloner.cs +++ b/Source/Cloning/ReflectionCloner.cs @@ -42,7 +42,9 @@ namespace Nuclex.Support.Cloning { /// Whether to clone the object based on its properties only /// /// A shallow clone of the provided object - public TCloned ShallowClone(TCloned objectToClone, bool usePropertyBasedClone) { + public static TCloned ShallowClone( + TCloned objectToClone, bool usePropertyBasedClone + ) { Type originalType = objectToClone.GetType(); if(originalType.IsPrimitive || (originalType == typeof(string))) { return objectToClone; // Being value types, primitives are copied by default @@ -73,7 +75,9 @@ namespace Nuclex.Support.Cloning { /// Whether to clone the object based on its properties only /// /// A deep clone of the provided object - public TCloned DeepClone(TCloned objectToClone, bool usePropertyBasedClone) { + public static TCloned DeepClone( + TCloned objectToClone, bool usePropertyBasedClone + ) { if(usePropertyBasedClone) { return (TCloned)deepCloneSinglePropertyBased(objectToClone); } else { @@ -81,10 +85,39 @@ namespace Nuclex.Support.Cloning { } } + /// + /// 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 ReflectionCloner.ShallowClone(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 ReflectionCloner.DeepClone(objectToClone, usePropertyBasedClone); + } + /// Clones a complex type using field-based value transfer /// Original instance that will be cloned /// A clone of the original instance - private object shallowCloneComplexFieldBased(object original) { + private static object shallowCloneComplexFieldBased(object original) { Type originalType = original.GetType(); object clone = Activator.CreateInstance(originalType); @@ -107,7 +140,7 @@ namespace Nuclex.Support.Cloning { /// Clones a complex type using property-based value transfer /// Original instance that will be cloned /// A clone of the original instance - private object shallowCloneComplexPropertyBased(object original) { + private static object shallowCloneComplexPropertyBased(object original) { Type originalType = original.GetType(); object clone = Activator.CreateInstance(originalType); @@ -140,7 +173,7 @@ namespace Nuclex.Support.Cloning { /// Clones an array using field-based value transfer /// Original array that will be cloned /// A clone of the original array - private object shallowCloneArray(object original) { + private static object shallowCloneArray(object original) { return ((Array)original).Clone(); } diff --git a/Source/Cloning/SerializationCloner.Test.cs b/Source/Cloning/SerializationCloner.Test.cs index 75cbe11..f4bcd01 100644 --- a/Source/Cloning/SerializationCloner.Test.cs +++ b/Source/Cloning/SerializationCloner.Test.cs @@ -27,7 +27,7 @@ using NUnit.Framework; namespace Nuclex.Support.Cloning { - /// Unit Test for the reflection-based cloner + /// Unit Test for the binary serializer-based cloner [TestFixture] public class SerializationClonerTest : CloneFactoryTest { diff --git a/Source/Cloning/SerializationCloner.cs b/Source/Cloning/SerializationCloner.cs index 8cf973a..b14e02e 100644 --- a/Source/Cloning/SerializationCloner.cs +++ b/Source/Cloning/SerializationCloner.cs @@ -18,6 +18,8 @@ License along with this library */ #endregion +#if !(XBOX360 || WINDOWS_PHONE) + using System; using System.IO; using System.Reflection; @@ -222,22 +224,15 @@ namespace Nuclex.Support.Cloning { #endregion // class PropertySerializationSurrogate - /// Initializes a new serialization-based cloner - public SerializationCloner() { - var fieldSurrogateSelector = new SurrogateSelector(); - fieldSurrogateSelector.ChainSelector( - new StaticSurrogateSelector(new FieldSerializationSurrogate()) + /// Initializes the static members of the serialization-based cloner + static SerializationCloner() { + fieldBasedFormatter = new BinaryFormatter( + new StaticSurrogateSelector(new FieldSerializationSurrogate()), + new StreamingContext(StreamingContextStates.All) ); - this.fieldBasedFormatter = new BinaryFormatter( - fieldSurrogateSelector, new StreamingContext(StreamingContextStates.All) - ); - - var propertySurrogateSelector = new SurrogateSelector(); - propertySurrogateSelector.ChainSelector( - new StaticSurrogateSelector(new PropertySerializationSurrogate()) - ); - this.propertyBasedFormatter = new BinaryFormatter( - propertySurrogateSelector, new StreamingContext(StreamingContextStates.All) + propertyBasedFormatter = new BinaryFormatter( + new StaticSurrogateSelector(new PropertySerializationSurrogate()), + new StreamingContext(StreamingContextStates.All) ); } @@ -251,20 +246,38 @@ namespace Nuclex.Support.Cloning { /// Whether to clone the object based on its properties only /// /// A deep clone of the provided object - public TCloned DeepClone(TCloned objectToClone, bool usePropertyBasedClone) { + public static TCloned DeepClone( + TCloned objectToClone, bool usePropertyBasedClone + ) { using(var memoryStream = new MemoryStream()) { if(usePropertyBasedClone) { - this.propertyBasedFormatter.Serialize(memoryStream, objectToClone); + propertyBasedFormatter.Serialize(memoryStream, objectToClone); memoryStream.Position = 0; - return (TCloned)this.propertyBasedFormatter.Deserialize(memoryStream); + return (TCloned)propertyBasedFormatter.Deserialize(memoryStream); } else { - this.fieldBasedFormatter.Serialize(memoryStream, objectToClone); + fieldBasedFormatter.Serialize(memoryStream, objectToClone); memoryStream.Position = 0; - return (TCloned)this.fieldBasedFormatter.Deserialize(memoryStream); + return (TCloned)fieldBasedFormatter.Deserialize(memoryStream); } } } + /// + /// 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 SerializationCloner.DeepClone(objectToClone, usePropertyBasedClone); + } + /// /// Creates a shallow clone of the specified object, reusing any referenced objects /// @@ -274,15 +287,19 @@ namespace Nuclex.Support.Cloning { /// Whether to clone the object based on its properties only /// /// A shallow clone of the provided object - public TCloned ShallowClone(TCloned objectToClone, bool usePropertyBasedClone) { + TCloned ICloneFactory.ShallowClone( + TCloned objectToClone, bool usePropertyBasedClone + ) { throw new NotSupportedException("The serialization cloner cannot create shallow clones"); } /// Serializes objects by storing their fields - private BinaryFormatter fieldBasedFormatter; + private static BinaryFormatter fieldBasedFormatter; /// Serializes objects by storing their properties - private BinaryFormatter propertyBasedFormatter; + private static BinaryFormatter propertyBasedFormatter; } } // namespace Nuclex.Support.Cloning + +#endif // !(XBOX360 || WINDOWS_PHONE)