From 961f56157e23853933bb701aad023d48343f6ad9 Mon Sep 17 00:00:00 2001 From: Markus Ewald Date: Fri, 3 Feb 2012 12:18:37 +0000 Subject: [PATCH] Added a cloner that uses a binary serializer to clone objects; converted unit tests for reflection cloner into a shared base class so they can be applied to the serialization-based cloner as well git-svn-id: file:///srv/devel/repo-conversion/nusu@227 d2e56fa2-650e-0410-a79f-9358c0239efd --- Nuclex.Support (net-4.0).csproj | 5 + Nuclex.Support (xna-4.0-phone7).csproj | 5 + Nuclex.Support (xna-4.0-xbox360).csproj | 5 + Source/Cloning/CloneFactoryTest.cs | 446 +++++++++++++++++++ Source/Cloning/ReflectionCloner.Test.cs | 472 ++------------------- Source/Cloning/ReflectionCloner.cs | 7 + Source/Cloning/SerializationCloner.Test.cs | 126 ++++++ Source/Cloning/SerializationCloner.cs | 288 +++++++++++++ 8 files changed, 917 insertions(+), 437 deletions(-) create mode 100644 Source/Cloning/CloneFactoryTest.cs create mode 100644 Source/Cloning/SerializationCloner.Test.cs create mode 100644 Source/Cloning/SerializationCloner.cs diff --git a/Nuclex.Support (net-4.0).csproj b/Nuclex.Support (net-4.0).csproj index 5303e18..70420ef 100644 --- a/Nuclex.Support (net-4.0).csproj +++ b/Nuclex.Support (net-4.0).csproj @@ -60,6 +60,7 @@ AffineThreadPool.cs + @@ -68,6 +69,10 @@ ReflectionCloner.cs + + + SerializationCloner.cs + Deque.cs diff --git a/Nuclex.Support (xna-4.0-phone7).csproj b/Nuclex.Support (xna-4.0-phone7).csproj index 0dc521e..698f1b1 100644 --- a/Nuclex.Support (xna-4.0-phone7).csproj +++ b/Nuclex.Support (xna-4.0-phone7).csproj @@ -91,6 +91,7 @@ AffineThreadPool.cs + @@ -99,6 +100,10 @@ ReflectionCloner.cs + + + SerializationCloner.cs + Deque.cs diff --git a/Nuclex.Support (xna-4.0-xbox360).csproj b/Nuclex.Support (xna-4.0-xbox360).csproj index 63c0c4c..7b32470 100644 --- a/Nuclex.Support (xna-4.0-xbox360).csproj +++ b/Nuclex.Support (xna-4.0-xbox360).csproj @@ -102,6 +102,7 @@ AffineThreadPool.cs + @@ -110,6 +111,10 @@ ReflectionCloner.cs + + + SerializationCloner.cs + Deque.cs diff --git a/Source/Cloning/CloneFactoryTest.cs b/Source/Cloning/CloneFactoryTest.cs new file mode 100644 index 0000000..4991008 --- /dev/null +++ b/Source/Cloning/CloneFactoryTest.cs @@ -0,0 +1,446 @@ +#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 NUnit.Framework; + +namespace Nuclex.Support.Cloning { + + /// Base class for unit tests verifying the clone factory + public abstract class CloneFactoryTest { + + #region class TestReferenceType + + /// A reference type being used for testing + protected class TestReferenceType { + + /// Field holding an integer value for testing + public int TestField; + /// Property holding an integer value for testing + public int TestProperty { get; set; } + + } + + #endregion // class TestReferenceType + + #region struct TestValueType + + /// A value type being used for testing + protected struct TestValueType { + + /// Field holding an integer value for testing + public int TestField; + /// Property holding an integer value for testing + public int TestProperty { get; set; } + + } + + #endregion // struct TestValueType + + #region struct HierarchicalValueType + + /// A value type containiner other complex types used for testing + protected struct HierarchicalValueType { + + /// Field holding an integer value for testing + public int TestField; + /// Property holding an integer value for testing + public int TestProperty { get; set; } + /// Value type field for testing + public TestValueType ValueTypeField; + /// Value type property for testing + public TestValueType ValueTypeProperty { get; set; } + /// Reference type field for testing + public TestReferenceType ReferenceTypeField; + /// Reference type property for testing + public TestReferenceType ReferenceTypeProperty { get; set; } + /// An array field of reference types + public TestReferenceType[,][] ReferenceTypeArrayField; + /// An array property of reference types + public TestReferenceType[,][] ReferenceTypeArrayProperty { get; set; } + + } + + #endregion // struct HierarchicalValueType + + #region struct HierarchicalReferenceType + + /// A value type containiner other complex types used for testing + protected class HierarchicalReferenceType { + + /// Field holding an integer value for testing + public int TestField; + /// Property holding an integer value for testing + public int TestProperty { get; set; } + /// Value type field for testing + public TestValueType ValueTypeField; + /// Value type property for testing + public TestValueType ValueTypeProperty { get; set; } + /// Reference type field for testing + public TestReferenceType ReferenceTypeField; + /// Reference type property for testing + public TestReferenceType ReferenceTypeProperty { get; set; } + /// An array field of reference types + public TestReferenceType[,][] ReferenceTypeArrayField; + /// An array property of reference types + public TestReferenceType[,][] ReferenceTypeArrayProperty { get; set; } + + } + + #endregion // struct HierarchicalReferenceType + + /// + /// Verifies that a cloned object exhibits the expected state for the type of + /// clone that has been performed + /// + /// Original instance the clone was created from + /// Cloned instance that will be checked for correctness + /// Whether the cloned instance is a deep clone + /// + /// Whether a property-based clone was performed + /// + protected static void VerifyClone( + HierarchicalReferenceType original, HierarchicalReferenceType clone, + bool isDeepClone, bool isPropertyBasedClone + ) { + if(isPropertyBasedClone) { + Assert.AreEqual(0, clone.TestField); + Assert.AreEqual(0, clone.ValueTypeField.TestField); + Assert.AreEqual(0, clone.ValueTypeField.TestProperty); + Assert.AreEqual(0, clone.ValueTypeProperty.TestField); + Assert.IsNull(clone.ReferenceTypeField); + + if(isDeepClone) { + Assert.AreNotSame(original.ReferenceTypeProperty, clone.ReferenceTypeProperty); + Assert.AreNotSame( + original.ReferenceTypeArrayProperty, clone.ReferenceTypeArrayProperty + ); + Assert.AreEqual(0, clone.ReferenceTypeProperty.TestField); + Assert.AreNotSame( + original.ReferenceTypeArrayProperty[1, 3][0], + clone.ReferenceTypeArrayProperty[1, 3][0] + ); + Assert.AreNotSame( + original.ReferenceTypeArrayProperty[1, 3][2], + clone.ReferenceTypeArrayProperty[1, 3][2] + ); + Assert.AreEqual(0, clone.ReferenceTypeArrayProperty[1, 3][0].TestField); + Assert.AreEqual(0, clone.ReferenceTypeArrayProperty[1, 3][2].TestField); + } else { + Assert.AreSame(original.ReferenceTypeProperty, clone.ReferenceTypeProperty); + Assert.AreSame( + original.ReferenceTypeArrayProperty, clone.ReferenceTypeArrayProperty + ); + } + } else { + Assert.AreEqual(original.TestField, clone.TestField); + Assert.AreEqual(original.ValueTypeField.TestField, clone.ValueTypeField.TestField); + Assert.AreEqual(original.ValueTypeField.TestProperty, clone.ValueTypeField.TestProperty); + Assert.AreEqual( + original.ValueTypeProperty.TestField, clone.ValueTypeProperty.TestField + ); + Assert.AreEqual( + original.ReferenceTypeField.TestField, clone.ReferenceTypeField.TestField + ); + Assert.AreEqual( + original.ReferenceTypeField.TestProperty, clone.ReferenceTypeField.TestProperty + ); + Assert.AreEqual( + original.ReferenceTypeProperty.TestField, clone.ReferenceTypeProperty.TestField + ); + + if(isDeepClone) { + Assert.AreNotSame(original.ReferenceTypeField, clone.ReferenceTypeField); + Assert.AreNotSame(original.ReferenceTypeProperty, clone.ReferenceTypeProperty); + Assert.AreNotSame( + original.ReferenceTypeArrayField, clone.ReferenceTypeArrayField + ); + Assert.AreNotSame( + original.ReferenceTypeArrayProperty, clone.ReferenceTypeArrayProperty + ); + Assert.AreNotSame( + original.ReferenceTypeArrayProperty[1, 3][0], + clone.ReferenceTypeArrayProperty[1, 3][0] + ); + Assert.AreNotSame( + original.ReferenceTypeArrayProperty[1, 3][2], + clone.ReferenceTypeArrayProperty[1, 3][2] + ); + Assert.AreEqual( + original.ReferenceTypeArrayProperty[1, 3][0].TestField, + clone.ReferenceTypeArrayProperty[1, 3][0].TestField + ); + Assert.AreEqual( + original.ReferenceTypeArrayProperty[1, 3][2].TestField, + clone.ReferenceTypeArrayProperty[1, 3][2].TestField + ); + } else { + Assert.AreSame(original.ReferenceTypeField, clone.ReferenceTypeField); + Assert.AreSame(original.ReferenceTypeProperty, clone.ReferenceTypeProperty); + Assert.AreSame( + original.ReferenceTypeArrayField, clone.ReferenceTypeArrayField + ); + Assert.AreSame( + original.ReferenceTypeArrayProperty, clone.ReferenceTypeArrayProperty + ); + } + } + + Assert.AreEqual(original.TestProperty, clone.TestProperty); + Assert.AreEqual( + original.ValueTypeProperty.TestProperty, clone.ValueTypeProperty.TestProperty + ); + Assert.AreEqual( + original.ReferenceTypeProperty.TestProperty, clone.ReferenceTypeProperty.TestProperty + ); + Assert.AreEqual( + original.ReferenceTypeArrayProperty[1, 3][0].TestProperty, + clone.ReferenceTypeArrayProperty[1, 3][0].TestProperty + ); + Assert.AreEqual( + original.ReferenceTypeArrayProperty[1, 3][2].TestProperty, + clone.ReferenceTypeArrayProperty[1, 3][2].TestProperty + ); + } + + /// + /// Verifies that a cloned object exhibits the expected state for the type of + /// clone that has been performed + /// + /// Original instance the clone was created from + /// Cloned instance that will be checked for correctness + /// Whether the cloned instance is a deep clone + /// + /// Whether a property-based clone was performed + /// + protected static void VerifyClone( + ref HierarchicalValueType original, ref HierarchicalValueType clone, + bool isDeepClone, bool isPropertyBasedClone + ) { + if(isPropertyBasedClone) { + Assert.AreEqual(0, clone.TestField); + Assert.AreEqual(0, clone.ValueTypeField.TestField); + Assert.AreEqual(0, clone.ValueTypeField.TestProperty); + Assert.AreEqual(0, clone.ValueTypeProperty.TestField); + Assert.IsNull(clone.ReferenceTypeField); + + if(isDeepClone) { + Assert.AreNotSame(original.ReferenceTypeProperty, clone.ReferenceTypeProperty); + Assert.AreNotSame( + original.ReferenceTypeArrayProperty, clone.ReferenceTypeArrayProperty + ); + Assert.AreEqual(0, clone.ReferenceTypeProperty.TestField); + Assert.AreNotSame( + original.ReferenceTypeArrayProperty[1, 3][0], + clone.ReferenceTypeArrayProperty[1, 3][0] + ); + Assert.AreNotSame( + original.ReferenceTypeArrayProperty[1, 3][2], + clone.ReferenceTypeArrayProperty[1, 3][2] + ); + Assert.AreEqual(0, clone.ReferenceTypeArrayProperty[1, 3][0].TestField); + Assert.AreEqual(0, clone.ReferenceTypeArrayProperty[1, 3][2].TestField); + } else { + Assert.AreSame(original.ReferenceTypeProperty, clone.ReferenceTypeProperty); + Assert.AreSame( + original.ReferenceTypeArrayProperty, clone.ReferenceTypeArrayProperty + ); + } + } else { + Assert.AreEqual(original.TestField, clone.TestField); + Assert.AreEqual(original.ValueTypeField.TestField, clone.ValueTypeField.TestField); + Assert.AreEqual(original.ValueTypeField.TestProperty, clone.ValueTypeField.TestProperty); + Assert.AreEqual( + original.ValueTypeProperty.TestField, clone.ValueTypeProperty.TestField + ); + Assert.AreEqual( + original.ReferenceTypeField.TestField, clone.ReferenceTypeField.TestField + ); + Assert.AreEqual( + original.ReferenceTypeField.TestProperty, clone.ReferenceTypeField.TestProperty + ); + Assert.AreEqual( + original.ReferenceTypeProperty.TestField, clone.ReferenceTypeProperty.TestField + ); + + if(isDeepClone) { + Assert.AreNotSame(original.ReferenceTypeField, clone.ReferenceTypeField); + Assert.AreNotSame(original.ReferenceTypeProperty, clone.ReferenceTypeProperty); + Assert.AreNotSame( + original.ReferenceTypeArrayField, clone.ReferenceTypeArrayField + ); + Assert.AreNotSame( + original.ReferenceTypeArrayProperty, clone.ReferenceTypeArrayProperty + ); + Assert.AreNotSame( + original.ReferenceTypeArrayProperty[1, 3][0], + clone.ReferenceTypeArrayProperty[1, 3][0] + ); + Assert.AreNotSame( + original.ReferenceTypeArrayProperty[1, 3][2], + clone.ReferenceTypeArrayProperty[1, 3][2] + ); + Assert.AreEqual( + original.ReferenceTypeArrayProperty[1, 3][0].TestField, + clone.ReferenceTypeArrayProperty[1, 3][0].TestField + ); + Assert.AreEqual( + original.ReferenceTypeArrayProperty[1, 3][2].TestField, + clone.ReferenceTypeArrayProperty[1, 3][2].TestField + ); + } else { + Assert.AreSame(original.ReferenceTypeField, clone.ReferenceTypeField); + Assert.AreSame(original.ReferenceTypeProperty, clone.ReferenceTypeProperty); + Assert.AreSame( + original.ReferenceTypeArrayField, clone.ReferenceTypeArrayField + ); + Assert.AreSame( + original.ReferenceTypeArrayProperty, clone.ReferenceTypeArrayProperty + ); + } + } + + Assert.AreEqual(original.TestProperty, clone.TestProperty); + Assert.AreEqual( + original.ValueTypeProperty.TestProperty, clone.ValueTypeProperty.TestProperty + ); + Assert.AreEqual( + original.ReferenceTypeProperty.TestProperty, clone.ReferenceTypeProperty.TestProperty + ); + Assert.AreEqual( + original.ReferenceTypeArrayProperty[1, 3][0].TestProperty, + clone.ReferenceTypeArrayProperty[1, 3][0].TestProperty + ); + Assert.AreEqual( + original.ReferenceTypeArrayProperty[1, 3][2].TestProperty, + clone.ReferenceTypeArrayProperty[1, 3][2].TestProperty + ); + } + + /// Creates a value type with random data for testing + /// A new value type with random data + protected static HierarchicalValueType CreateValueType() { + return new HierarchicalValueType() { + TestField = 123, + TestProperty = 321, + ReferenceTypeArrayField = new TestReferenceType[2, 4][] { + { + null, null, null, null + }, + { + null, null, null, + new TestReferenceType[3] { + new TestReferenceType() { TestField = 101, TestProperty = 202 }, + null, + new TestReferenceType() { TestField = 909, TestProperty = 808 } + } + }, + }, + ReferenceTypeArrayProperty = new TestReferenceType[2, 4][] { + { + null, null, null, null + }, + { + null, null, null, + new TestReferenceType[3] { + new TestReferenceType() { TestField = 303, TestProperty = 404 }, + null, + new TestReferenceType() { TestField = 707, TestProperty = 606 } + } + }, + }, + ValueTypeField = new TestValueType() { + TestField = 456, + TestProperty = 654 + }, + ValueTypeProperty = new TestValueType() { + TestField = 789, + TestProperty = 987, + }, + ReferenceTypeField = new TestReferenceType() { + TestField = 135, + TestProperty = 531 + }, + ReferenceTypeProperty = new TestReferenceType() { + TestField = 246, + TestProperty = 642, + } + }; + } + + /// Creates a reference type with random data for testing + /// A new reference type with random data + protected static HierarchicalReferenceType CreateReferenceType() { + return new HierarchicalReferenceType() { + TestField = 123, + TestProperty = 321, + ReferenceTypeArrayField = new TestReferenceType[2, 4][] { + { + null, null, null, null + }, + { + null, null, null, + new TestReferenceType[3] { + new TestReferenceType() { TestField = 101, TestProperty = 202 }, + null, + new TestReferenceType() { TestField = 909, TestProperty = 808 } + } + }, + }, + ReferenceTypeArrayProperty = new TestReferenceType[2, 4][] { + { + null, null, null, null + }, + { + null, null, null, + new TestReferenceType[3] { + new TestReferenceType() { TestField = 303, TestProperty = 404 }, + null, + new TestReferenceType() { TestField = 707, TestProperty = 606 } + } + }, + }, + ValueTypeField = new TestValueType() { + TestField = 456, + TestProperty = 654 + }, + ValueTypeProperty = new TestValueType() { + TestField = 789, + TestProperty = 987, + }, + ReferenceTypeField = new TestReferenceType() { + TestField = 135, + TestProperty = 531 + }, + ReferenceTypeProperty = new TestReferenceType() { + TestField = 246, + TestProperty = 642, + } + }; + } + + } + +} // namespace Nuclex.Support.Cloning + +#endif // UNITTEST diff --git a/Source/Cloning/ReflectionCloner.Test.cs b/Source/Cloning/ReflectionCloner.Test.cs index 683171a..28da7c1 100644 --- a/Source/Cloning/ReflectionCloner.Test.cs +++ b/Source/Cloning/ReflectionCloner.Test.cs @@ -29,93 +29,18 @@ namespace Nuclex.Support.Cloning { /// Unit Test for the reflection-based cloner [TestFixture] - public class ReflectionClonerTest { - - #region class TestReferenceType - - /// A reference type being used for testing - private class TestReferenceType { - - /// Field holding an integer value for testing - public int TestField; - /// Property holding an integer value for testing - public int TestProperty { get; set; } + public class ReflectionClonerTest : CloneFactoryTest { + /// Initializes a new unit test suite for the reflection cloner + public ReflectionClonerTest() { + this.cloneFactory = new ReflectionCloner(); } - #endregion // class TestReferenceType - - #region struct TestValueType - - /// A value type being used for testing - private struct TestValueType { - - /// Field holding an integer value for testing - public int TestField; - /// Property holding an integer value for testing - public int TestProperty { get; set; } - - } - - #endregion // struct TestValueType - - #region struct HierarchicalValueType - - /// A value type containiner other complex types used for testing - private struct HierarchicalValueType { - - /// Field holding an integer value for testing - public int TestField; - /// Property holding an integer value for testing - public int TestProperty { get; set; } - /// Value type field for testing - public TestValueType ValueTypeField; - /// Value type property for testing - public TestValueType ValueTypeProperty { get; set; } - /// Reference type field for testing - public TestReferenceType ReferenceTypeField; - /// Reference type property for testing - public TestReferenceType ReferenceTypeProperty { get; set; } - /// An array field of reference types - public TestReferenceType[,][] ReferenceTypeArrayField; - /// An array property of reference types - public TestReferenceType[,][] ReferenceTypeArrayProperty { get; set; } - - } - - #endregion // struct HierarchicalValueType - - #region struct HierarchicalReferenceType - - /// A value type containiner other complex types used for testing - private class HierarchicalReferenceType { - - /// Field holding an integer value for testing - public int TestField; - /// Property holding an integer value for testing - public int TestProperty { get; set; } - /// Value type field for testing - public TestValueType ValueTypeField; - /// Value type property for testing - public TestValueType ValueTypeProperty { get; set; } - /// Reference type field for testing - public TestReferenceType ReferenceTypeField; - /// Reference type property for testing - public TestReferenceType ReferenceTypeProperty { get; set; } - /// An array field of reference types - public TestReferenceType[,][] ReferenceTypeArrayField; - /// An array property of reference types - public TestReferenceType[,][] ReferenceTypeArrayProperty { get; set; } - - } - - #endregion // struct HierarchicalReferenceType - /// Verifies that clones of primitive types can be created [Test] public void PrimitiveTypesCanBeCloned() { int original = 12345; - int clone = (new ReflectionCloner()).ShallowClone(original, false); + int clone = this.cloneFactory.ShallowClone(original, false); Assert.AreEqual(original, clone); } @@ -125,7 +50,7 @@ namespace Nuclex.Support.Cloning { var original = new TestReferenceType[] { new TestReferenceType() { TestField = 123, TestProperty = 456 } }; - TestReferenceType[] clone = (new ReflectionCloner()).ShallowClone(original, false); + TestReferenceType[] clone = this.cloneFactory.ShallowClone(original, false); Assert.AreSame(original[0], clone[0]); } @@ -136,7 +61,7 @@ namespace Nuclex.Support.Cloning { var original = new TestReferenceType[] { new TestReferenceType() { TestField = 123, TestProperty = 456 } }; - TestReferenceType[] clone = (new ReflectionCloner()).DeepClone(original, false); + TestReferenceType[] clone = this.cloneFactory.DeepClone(original, false); Assert.AreNotSame(original[0], clone[0]); Assert.AreEqual(original[0].TestField, clone[0].TestField); @@ -147,7 +72,7 @@ namespace Nuclex.Support.Cloning { [Test] public void GenericListsCanBeCloned() { var original = new List(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }); - List clone = (new ReflectionCloner()).DeepClone(original, false); + List clone = this.cloneFactory.DeepClone(original, false); CollectionAssert.AreEqual(original, clone); } @@ -157,7 +82,7 @@ namespace Nuclex.Support.Cloning { public void GenericDictionariesCanBeCloned() { var original = new Dictionary(); original.Add(1, "one"); - Dictionary clone = (new ReflectionCloner()).DeepClone(original, false); + Dictionary clone = this.cloneFactory.DeepClone(original, false); Assert.AreEqual("one", clone[1]); } @@ -167,9 +92,9 @@ namespace Nuclex.Support.Cloning { /// [Test] public void ShallowFieldBasedClonesOfValueTypesCanBeMade() { - HierarchicalValueType original = createValueType(); - HierarchicalValueType clone = (new ReflectionCloner()).ShallowClone(original, false); - verifyClone(ref original, ref clone, isDeepClone: false, isPropertyBasedClone: false); + HierarchicalValueType original = CreateValueType(); + HierarchicalValueType clone = this.cloneFactory.ShallowClone(original, false); + VerifyClone(ref original, ref clone, isDeepClone: false, isPropertyBasedClone: false); } /// @@ -177,9 +102,9 @@ namespace Nuclex.Support.Cloning { /// [Test] public void ShallowFieldBasedClonesOfReferenceTypesCanBeMade() { - HierarchicalReferenceType original = createReferenceType(); - HierarchicalReferenceType clone = (new ReflectionCloner()).ShallowClone(original, false); - verifyClone(original, clone, isDeepClone: false, isPropertyBasedClone: false); + HierarchicalReferenceType original = CreateReferenceType(); + HierarchicalReferenceType clone = this.cloneFactory.ShallowClone(original, false); + VerifyClone(original, clone, isDeepClone: false, isPropertyBasedClone: false); } /// @@ -187,9 +112,9 @@ namespace Nuclex.Support.Cloning { /// [Test] public void DeepFieldBasedClonesOfValueTypesCanBeMade() { - HierarchicalValueType original = createValueType(); - HierarchicalValueType clone = (new ReflectionCloner()).DeepClone(original, false); - verifyClone(ref original, ref clone, isDeepClone: true, isPropertyBasedClone: false); + HierarchicalValueType original = CreateValueType(); + HierarchicalValueType clone = this.cloneFactory.DeepClone(original, false); + VerifyClone(ref original, ref clone, isDeepClone: true, isPropertyBasedClone: false); } /// @@ -197,9 +122,9 @@ namespace Nuclex.Support.Cloning { /// [Test] public void DeepFieldBasedClonesOfReferenceTypesCanBeMade() { - HierarchicalReferenceType original = createReferenceType(); - HierarchicalReferenceType clone = (new ReflectionCloner()).DeepClone(original, false); - verifyClone(original, clone, isDeepClone: true, isPropertyBasedClone: false); + HierarchicalReferenceType original = CreateReferenceType(); + HierarchicalReferenceType clone = this.cloneFactory.DeepClone(original, false); + VerifyClone(original, clone, isDeepClone: true, isPropertyBasedClone: false); } /// @@ -207,9 +132,9 @@ namespace Nuclex.Support.Cloning { /// [Test] public void ShallowPropertyBasedClonesOfValueTypesCanBeMade() { - HierarchicalValueType original = createValueType(); - HierarchicalValueType clone = (new ReflectionCloner()).ShallowClone(original, true); - verifyClone(ref original, ref clone, isDeepClone: false, isPropertyBasedClone: true); + HierarchicalValueType original = CreateValueType(); + HierarchicalValueType clone = this.cloneFactory.ShallowClone(original, true); + VerifyClone(ref original, ref clone, isDeepClone: false, isPropertyBasedClone: true); } /// @@ -217,9 +142,9 @@ namespace Nuclex.Support.Cloning { /// [Test] public void ShallowPropertyBasedClonesOfReferenceTypesCanBeMade() { - HierarchicalReferenceType original = createReferenceType(); - HierarchicalReferenceType clone = (new ReflectionCloner()).ShallowClone(original, true); - verifyClone(original, clone, isDeepClone: false, isPropertyBasedClone: true); + HierarchicalReferenceType original = CreateReferenceType(); + HierarchicalReferenceType clone = this.cloneFactory.ShallowClone(original, true); + VerifyClone(original, clone, isDeepClone: false, isPropertyBasedClone: true); } /// @@ -227,9 +152,9 @@ namespace Nuclex.Support.Cloning { /// [Test] public void DeepPropertyBasedClonesOfValueTypesCanBeMade() { - HierarchicalValueType original = createValueType(); - HierarchicalValueType clone = (new ReflectionCloner()).DeepClone(original, true); - verifyClone(ref original, ref clone, isDeepClone: true, isPropertyBasedClone: true); + HierarchicalValueType original = CreateValueType(); + HierarchicalValueType clone = this.cloneFactory.DeepClone(original, true); + VerifyClone(ref original, ref clone, isDeepClone: true, isPropertyBasedClone: true); } /// @@ -237,340 +162,13 @@ namespace Nuclex.Support.Cloning { /// [Test] public void DeepPropertyBasedClonesOfReferenceTypesCanBeMade() { - HierarchicalReferenceType original = createReferenceType(); - HierarchicalReferenceType clone = (new ReflectionCloner()).DeepClone(original, true); - verifyClone(original, clone, isDeepClone: true, isPropertyBasedClone: true); + HierarchicalReferenceType original = CreateReferenceType(); + HierarchicalReferenceType clone = this.cloneFactory.DeepClone(original, true); + VerifyClone(original, clone, isDeepClone: true, isPropertyBasedClone: true); } - /// - /// Verifies that a cloned object exhibits the expected state for the type of - /// clone that has been performed - /// - /// Original instance the clone was created from - /// Cloned instance that will be checked for correctness - /// Whether the cloned instance is a deep clone - /// - /// Whether a property-based clone was performed - /// - private static void verifyClone( - HierarchicalReferenceType original, HierarchicalReferenceType clone, - bool isDeepClone, bool isPropertyBasedClone - ) { - if(isPropertyBasedClone) { - Assert.AreEqual(0, clone.TestField); - Assert.AreEqual(0, clone.ValueTypeField.TestField); - Assert.AreEqual(0, clone.ValueTypeField.TestProperty); - Assert.AreEqual(0, clone.ValueTypeProperty.TestField); - Assert.IsNull(clone.ReferenceTypeField); - - if(isDeepClone) { - Assert.AreNotSame(original.ReferenceTypeProperty, clone.ReferenceTypeProperty); - Assert.AreNotSame( - original.ReferenceTypeArrayProperty, clone.ReferenceTypeArrayProperty - ); - Assert.AreEqual(0, clone.ReferenceTypeProperty.TestField); - Assert.AreNotSame( - original.ReferenceTypeArrayProperty[1, 3][0], - clone.ReferenceTypeArrayProperty[1, 3][0] - ); - Assert.AreNotSame( - original.ReferenceTypeArrayProperty[1, 3][2], - clone.ReferenceTypeArrayProperty[1, 3][2] - ); - Assert.AreEqual(0, clone.ReferenceTypeArrayProperty[1, 3][0].TestField); - Assert.AreEqual(0, clone.ReferenceTypeArrayProperty[1, 3][2].TestField); - } else { - Assert.AreSame(original.ReferenceTypeProperty, clone.ReferenceTypeProperty); - Assert.AreSame( - original.ReferenceTypeArrayProperty, clone.ReferenceTypeArrayProperty - ); - } - } else { - Assert.AreEqual(original.TestField, clone.TestField); - Assert.AreEqual(original.ValueTypeField.TestField, clone.ValueTypeField.TestField); - Assert.AreEqual(original.ValueTypeField.TestProperty, clone.ValueTypeField.TestProperty); - Assert.AreEqual( - original.ValueTypeProperty.TestField, clone.ValueTypeProperty.TestField - ); - Assert.AreEqual( - original.ReferenceTypeField.TestField, clone.ReferenceTypeField.TestField - ); - Assert.AreEqual( - original.ReferenceTypeField.TestProperty, clone.ReferenceTypeField.TestProperty - ); - Assert.AreEqual( - original.ReferenceTypeProperty.TestField, clone.ReferenceTypeProperty.TestField - ); - - if(isDeepClone) { - Assert.AreNotSame(original.ReferenceTypeField, clone.ReferenceTypeField); - Assert.AreNotSame(original.ReferenceTypeProperty, clone.ReferenceTypeProperty); - Assert.AreNotSame( - original.ReferenceTypeArrayField, clone.ReferenceTypeArrayField - ); - Assert.AreNotSame( - original.ReferenceTypeArrayProperty, clone.ReferenceTypeArrayProperty - ); - Assert.AreNotSame( - original.ReferenceTypeArrayProperty[1, 3][0], - clone.ReferenceTypeArrayProperty[1, 3][0] - ); - Assert.AreNotSame( - original.ReferenceTypeArrayProperty[1, 3][2], - clone.ReferenceTypeArrayProperty[1, 3][2] - ); - Assert.AreEqual( - original.ReferenceTypeArrayProperty[1, 3][0].TestField, - clone.ReferenceTypeArrayProperty[1, 3][0].TestField - ); - Assert.AreEqual( - original.ReferenceTypeArrayProperty[1, 3][2].TestField, - clone.ReferenceTypeArrayProperty[1, 3][2].TestField - ); - } else { - Assert.AreSame(original.ReferenceTypeField, clone.ReferenceTypeField); - Assert.AreSame(original.ReferenceTypeProperty, clone.ReferenceTypeProperty); - Assert.AreSame( - original.ReferenceTypeArrayField, clone.ReferenceTypeArrayField - ); - Assert.AreSame( - original.ReferenceTypeArrayProperty, clone.ReferenceTypeArrayProperty - ); - } - } - - Assert.AreEqual(original.TestProperty, clone.TestProperty); - Assert.AreEqual( - original.ValueTypeProperty.TestProperty, clone.ValueTypeProperty.TestProperty - ); - Assert.AreEqual( - original.ReferenceTypeProperty.TestProperty, clone.ReferenceTypeProperty.TestProperty - ); - Assert.AreEqual( - original.ReferenceTypeArrayProperty[1, 3][0].TestProperty, - clone.ReferenceTypeArrayProperty[1, 3][0].TestProperty - ); - Assert.AreEqual( - original.ReferenceTypeArrayProperty[1, 3][2].TestProperty, - clone.ReferenceTypeArrayProperty[1, 3][2].TestProperty - ); - } - - /// - /// Verifies that a cloned object exhibits the expected state for the type of - /// clone that has been performed - /// - /// Original instance the clone was created from - /// Cloned instance that will be checked for correctness - /// Whether the cloned instance is a deep clone - /// - /// Whether a property-based clone was performed - /// - private static void verifyClone( - ref HierarchicalValueType original, ref HierarchicalValueType clone, - bool isDeepClone, bool isPropertyBasedClone - ) { - if(isPropertyBasedClone) { - Assert.AreEqual(0, clone.TestField); - Assert.AreEqual(0, clone.ValueTypeField.TestField); - Assert.AreEqual(0, clone.ValueTypeField.TestProperty); - Assert.AreEqual(0, clone.ValueTypeProperty.TestField); - Assert.IsNull(clone.ReferenceTypeField); - - if(isDeepClone) { - Assert.AreNotSame(original.ReferenceTypeProperty, clone.ReferenceTypeProperty); - Assert.AreNotSame( - original.ReferenceTypeArrayProperty, clone.ReferenceTypeArrayProperty - ); - Assert.AreEqual(0, clone.ReferenceTypeProperty.TestField); - Assert.AreNotSame( - original.ReferenceTypeArrayProperty[1, 3][0], - clone.ReferenceTypeArrayProperty[1, 3][0] - ); - Assert.AreNotSame( - original.ReferenceTypeArrayProperty[1, 3][2], - clone.ReferenceTypeArrayProperty[1, 3][2] - ); - Assert.AreEqual(0, clone.ReferenceTypeArrayProperty[1, 3][0].TestField); - Assert.AreEqual(0, clone.ReferenceTypeArrayProperty[1, 3][2].TestField); - } else { - Assert.AreSame(original.ReferenceTypeProperty, clone.ReferenceTypeProperty); - Assert.AreSame( - original.ReferenceTypeArrayProperty, clone.ReferenceTypeArrayProperty - ); - } - } else { - Assert.AreEqual(original.TestField, clone.TestField); - Assert.AreEqual(original.ValueTypeField.TestField, clone.ValueTypeField.TestField); - Assert.AreEqual(original.ValueTypeField.TestProperty, clone.ValueTypeField.TestProperty); - Assert.AreEqual( - original.ValueTypeProperty.TestField, clone.ValueTypeProperty.TestField - ); - Assert.AreEqual( - original.ReferenceTypeField.TestField, clone.ReferenceTypeField.TestField - ); - Assert.AreEqual( - original.ReferenceTypeField.TestProperty, clone.ReferenceTypeField.TestProperty - ); - Assert.AreEqual( - original.ReferenceTypeProperty.TestField, clone.ReferenceTypeProperty.TestField - ); - - if(isDeepClone) { - Assert.AreNotSame(original.ReferenceTypeField, clone.ReferenceTypeField); - Assert.AreNotSame(original.ReferenceTypeProperty, clone.ReferenceTypeProperty); - Assert.AreNotSame( - original.ReferenceTypeArrayField, clone.ReferenceTypeArrayField - ); - Assert.AreNotSame( - original.ReferenceTypeArrayProperty, clone.ReferenceTypeArrayProperty - ); - Assert.AreNotSame( - original.ReferenceTypeArrayProperty[1, 3][0], - clone.ReferenceTypeArrayProperty[1, 3][0] - ); - Assert.AreNotSame( - original.ReferenceTypeArrayProperty[1, 3][2], - clone.ReferenceTypeArrayProperty[1, 3][2] - ); - Assert.AreEqual( - original.ReferenceTypeArrayProperty[1, 3][0].TestField, - clone.ReferenceTypeArrayProperty[1, 3][0].TestField - ); - Assert.AreEqual( - original.ReferenceTypeArrayProperty[1, 3][2].TestField, - clone.ReferenceTypeArrayProperty[1, 3][2].TestField - ); - } else { - Assert.AreSame(original.ReferenceTypeField, clone.ReferenceTypeField); - Assert.AreSame(original.ReferenceTypeProperty, clone.ReferenceTypeProperty); - Assert.AreSame( - original.ReferenceTypeArrayField, clone.ReferenceTypeArrayField - ); - Assert.AreSame( - original.ReferenceTypeArrayProperty, clone.ReferenceTypeArrayProperty - ); - } - } - - Assert.AreEqual(original.TestProperty, clone.TestProperty); - Assert.AreEqual( - original.ValueTypeProperty.TestProperty, clone.ValueTypeProperty.TestProperty - ); - Assert.AreEqual( - original.ReferenceTypeProperty.TestProperty, clone.ReferenceTypeProperty.TestProperty - ); - Assert.AreEqual( - original.ReferenceTypeArrayProperty[1, 3][0].TestProperty, - clone.ReferenceTypeArrayProperty[1, 3][0].TestProperty - ); - Assert.AreEqual( - original.ReferenceTypeArrayProperty[1, 3][2].TestProperty, - clone.ReferenceTypeArrayProperty[1, 3][2].TestProperty - ); - } - - /// Creates a value type with random data for testing - /// A new value type with random data - private static HierarchicalValueType createValueType() { - return new HierarchicalValueType() { - TestField = 123, - TestProperty = 321, - ReferenceTypeArrayField = new TestReferenceType[2, 4][] { - { - null, null, null, null - }, - { - null, null, null, - new TestReferenceType[3] { - new TestReferenceType() { TestField = 101, TestProperty = 202 }, - null, - new TestReferenceType() { TestField = 909, TestProperty = 808 } - } - }, - }, - ReferenceTypeArrayProperty = new TestReferenceType[2, 4][] { - { - null, null, null, null - }, - { - null, null, null, - new TestReferenceType[3] { - new TestReferenceType() { TestField = 303, TestProperty = 404 }, - null, - new TestReferenceType() { TestField = 707, TestProperty = 606 } - } - }, - }, - ValueTypeField = new TestValueType() { - TestField = 456, - TestProperty = 654 - }, - ValueTypeProperty = new TestValueType() { - TestField = 789, - TestProperty = 987, - }, - ReferenceTypeField = new TestReferenceType() { - TestField = 135, - TestProperty = 531 - }, - ReferenceTypeProperty = new TestReferenceType() { - TestField = 246, - TestProperty = 642, - } - }; - } - - /// Creates a reference type with random data for testing - /// A new reference type with random data - private static HierarchicalReferenceType createReferenceType() { - return new HierarchicalReferenceType() { - TestField = 123, - TestProperty = 321, - ReferenceTypeArrayField = new TestReferenceType[2, 4][] { - { - null, null, null, null - }, - { - null, null, null, - new TestReferenceType[3] { - new TestReferenceType() { TestField = 101, TestProperty = 202 }, - null, - new TestReferenceType() { TestField = 909, TestProperty = 808 } - } - }, - }, - ReferenceTypeArrayProperty = new TestReferenceType[2, 4][] { - { - null, null, null, null - }, - { - null, null, null, - new TestReferenceType[3] { - new TestReferenceType() { TestField = 303, TestProperty = 404 }, - null, - new TestReferenceType() { TestField = 707, TestProperty = 606 } - } - }, - }, - ValueTypeField = new TestValueType() { - TestField = 456, - TestProperty = 654 - }, - ValueTypeProperty = new TestValueType() { - TestField = 789, - TestProperty = 987, - }, - ReferenceTypeField = new TestReferenceType() { - TestField = 135, - TestProperty = 531 - }, - ReferenceTypeProperty = new TestReferenceType() { - TestField = 246, - TestProperty = 642, - } - }; - } + /// Clone factory being tested + private ICloneFactory cloneFactory; } diff --git a/Source/Cloning/ReflectionCloner.cs b/Source/Cloning/ReflectionCloner.cs index 357d49c..a145255 100644 --- a/Source/Cloning/ReflectionCloner.cs +++ b/Source/Cloning/ReflectionCloner.cs @@ -24,6 +24,13 @@ using System.Reflection; namespace Nuclex.Support.Cloning { /// Clones objects using reflection + /// + /// + /// This type of cloning is a lot faster than cloning by serialization and + /// incurs no set-up cost, but requires cloned types to provide a default + /// constructor in order to work. + /// + /// public class ReflectionCloner : ICloneFactory { /// diff --git a/Source/Cloning/SerializationCloner.Test.cs b/Source/Cloning/SerializationCloner.Test.cs new file mode 100644 index 0000000..75cbe11 --- /dev/null +++ b/Source/Cloning/SerializationCloner.Test.cs @@ -0,0 +1,126 @@ +#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 reflection-based cloner + [TestFixture] + public class SerializationClonerTest : CloneFactoryTest { + + /// Initializes a new unit test suite for the reflection cloner + public SerializationClonerTest() { + this.cloneFactory = new SerializationCloner(); + } + + /// 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 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 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 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/SerializationCloner.cs b/Source/Cloning/SerializationCloner.cs new file mode 100644 index 0000000..8cf973a --- /dev/null +++ b/Source/Cloning/SerializationCloner.cs @@ -0,0 +1,288 @@ +#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 + +using System; +using System.IO; +using System.Reflection; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; + +namespace Nuclex.Support.Cloning { + + /// Clones objects via serialization + /// + /// + /// This type of cloning uses the binary formatter to persist the state of + /// an object and then restores it into a clone. It has the advantage of even + /// working with types that don't provide a default constructor, but is + /// terribly slow. + /// + /// + /// Inspired by the "A Generic Method for Deep Cloning in C# 3.0" article + /// on CodeProject: http://www.codeproject.com/KB/cs/generic_deep_cloning.aspx + /// + /// + public class SerializationCloner : ICloneFactory { + + #region class StaticSurrogateSelector + + /// Selects a static surrogate for any non-primitive types + private class StaticSurrogateSelector : ISurrogateSelector { + + /// Initializes a new static surrogate selector + /// Surrogate that will be selected + public StaticSurrogateSelector(ISerializationSurrogate staticSurrogate) { + this.staticSurrogate = staticSurrogate; + } + + /// + /// Sets the next selector to escalate to if this one can't provide a surrogate + /// + /// Selector to escalate to + public void ChainSelector(ISurrogateSelector selector) { + this.chainedSelector = selector; + } + + /// + /// Returns the selector this one will escalate to if it can't provide a surrogate + /// + /// The selector this one will escalate to + public ISurrogateSelector GetNextSelector() { + return this.chainedSelector; + } + + /// Attempts to provides a surrogate for the specified type + /// Type a surrogate will be provided for + /// Context + /// + /// + public ISerializationSurrogate GetSurrogate( + Type type, StreamingContext context, out ISurrogateSelector selector + ) { + if(type.IsPrimitive || type.IsArray || (type == typeof(string))) { + if(this.chainedSelector == null) { + selector = null; + return null; + } else { + return this.chainedSelector.GetSurrogate(type, context, out selector); + } + } else { + selector = this; + return this.staticSurrogate; + } + } + + /// Surrogate the that will be selected for any non-primitive types + private ISerializationSurrogate staticSurrogate; + /// Surrogate selector to escalate to if no surrogate can be provided + private ISurrogateSelector chainedSelector; + + } + + #endregion // class StaticSurrogateSelector + + #region class FieldSerializationSurrogate + + /// Serializes a type based on its fields + private class FieldSerializationSurrogate : ISerializationSurrogate { + + /// Extracts the data to be serialized from an object + /// Object that is being serialized + /// Stores the serialized informations + /// + /// Provides additional informations about the serialization process + /// + public void GetObjectData( + object objectToSerialize, + SerializationInfo info, + StreamingContext context + ) { + Type originalType = objectToSerialize.GetType(); + + FieldInfo[] fieldInfos = originalType.GetFields( + BindingFlags.Public | BindingFlags.NonPublic | + BindingFlags.Instance | BindingFlags.FlattenHierarchy + ); + for(int index = 0; index < fieldInfos.Length; ++index) { + FieldInfo fieldInfo = fieldInfos[index]; + info.AddValue(fieldInfo.Name, fieldInfo.GetValue(objectToSerialize)); + } + } + + /// Reinserts saved data into a deserializd object + /// Object the saved data will be inserted into + /// Contains the serialized informations + /// + /// Provides additional informations about the serialization process + /// + /// Surrogate selector that specified this surrogate + /// The deserialized object + public object SetObjectData( + object deserializedObject, + SerializationInfo info, + StreamingContext context, + ISurrogateSelector selector + ) { + Type originalType = deserializedObject.GetType(); + + FieldInfo[] fieldInfos = originalType.GetFields( + BindingFlags.Public | BindingFlags.NonPublic | + BindingFlags.Instance | BindingFlags.FlattenHierarchy + ); + for(int index = 0; index < fieldInfos.Length; ++index) { + FieldInfo fieldInfo = fieldInfos[index]; + fieldInfo.SetValue(deserializedObject, info.GetValue(fieldInfo.Name, fieldInfo.FieldType)); + } + + return deserializedObject; + } + + } + + #endregion // class FieldSerializationSurrogate + + #region class PropertySerializationSurrogate + + /// Serializes a type based on its properties + private class PropertySerializationSurrogate : ISerializationSurrogate { + + /// Extracts the data to be serialized from an object + /// Object that is being serialized + /// Stores the serialized informations + /// + /// Provides additional informations about the serialization process + /// + public void GetObjectData( + object objectToSerialize, + SerializationInfo info, + StreamingContext context + ) { + Type originalType = objectToSerialize.GetType(); + + PropertyInfo[] propertyInfos = originalType.GetProperties( + BindingFlags.Public | BindingFlags.NonPublic | + BindingFlags.Instance | BindingFlags.FlattenHierarchy + ); + for(int index = 0; index < propertyInfos.Length; ++index) { + PropertyInfo propertyInfo = propertyInfos[index]; + info.AddValue(propertyInfo.Name, propertyInfo.GetValue(objectToSerialize, null)); + } + } + + /// Reinserts saved data into a deserializd object + /// Object the saved data will be inserted into + /// Contains the serialized informations + /// + /// Provides additional informations about the serialization process + /// + /// Surrogate selector that specified this surrogate + /// The deserialized object + public object SetObjectData( + object deserializedObject, + SerializationInfo info, + StreamingContext context, + ISurrogateSelector selector + ) { + Type originalType = deserializedObject.GetType(); + + PropertyInfo[] propertyInfos = originalType.GetProperties( + BindingFlags.Public | BindingFlags.NonPublic | + BindingFlags.Instance | BindingFlags.FlattenHierarchy + ); + for(int index = 0; index < propertyInfos.Length; ++index) { + PropertyInfo propertyInfo = propertyInfos[index]; + propertyInfo.SetValue( + deserializedObject, + info.GetValue(propertyInfo.Name, propertyInfo.PropertyType), + null + ); + } + + return deserializedObject; + } + + } + + #endregion // class PropertySerializationSurrogate + + /// Initializes a new serialization-based cloner + public SerializationCloner() { + var fieldSurrogateSelector = new SurrogateSelector(); + fieldSurrogateSelector.ChainSelector( + new StaticSurrogateSelector(new FieldSerializationSurrogate()) + ); + 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) + ); + } + + /// + /// Creates a deep clone of the specified object, also creating clones of all + /// child objects being referenced + /// + /// Type of the object that will be cloned + /// Object that will be cloned + /// + /// Whether to clone the object based on its properties only + /// + /// A deep clone of the provided object + public TCloned DeepClone(TCloned objectToClone, bool usePropertyBasedClone) { + using(var memoryStream = new MemoryStream()) { + if(usePropertyBasedClone) { + this.propertyBasedFormatter.Serialize(memoryStream, objectToClone); + memoryStream.Position = 0; + return (TCloned)this.propertyBasedFormatter.Deserialize(memoryStream); + } else { + this.fieldBasedFormatter.Serialize(memoryStream, objectToClone); + memoryStream.Position = 0; + return (TCloned)this.fieldBasedFormatter.Deserialize(memoryStream); + } + } + } + + /// + /// Creates a shallow clone of the specified object, reusing any referenced objects + /// + /// Type of the object that will be cloned + /// Object that will be cloned + /// + /// Whether to clone the object based on its properties only + /// + /// A shallow clone of the provided object + public TCloned ShallowClone(TCloned objectToClone, bool usePropertyBasedClone) { + throw new NotSupportedException("The serialization cloner cannot create shallow clones"); + } + + /// Serializes objects by storing their fields + private BinaryFormatter fieldBasedFormatter; + /// Serializes objects by storing their properties + private BinaryFormatter propertyBasedFormatter; + + } + +} // namespace Nuclex.Support.Cloning