diff --git a/Nuclex.Support (net-4.0).csproj b/Nuclex.Support (net-4.0).csproj index a7f5e16..397c3b6 100644 --- a/Nuclex.Support (net-4.0).csproj +++ b/Nuclex.Support (net-4.0).csproj @@ -61,6 +61,10 @@ AffineThreadPool.cs + + + ClonerHelpers.cs + ExpressionTreeCloner.cs diff --git a/Nuclex.Support (xna-4.0-phone7).csproj b/Nuclex.Support (xna-4.0-phone7).csproj index 4c0d5a2..9ef932e 100644 --- a/Nuclex.Support (xna-4.0-phone7).csproj +++ b/Nuclex.Support (xna-4.0-phone7).csproj @@ -92,6 +92,10 @@ AffineThreadPool.cs + + + ClonerHelpers.cs + ExpressionTreeCloner.cs diff --git a/Nuclex.Support (xna-4.0-xbox360).csproj b/Nuclex.Support (xna-4.0-xbox360).csproj index cce3ddc..ff71d7a 100644 --- a/Nuclex.Support (xna-4.0-xbox360).csproj +++ b/Nuclex.Support (xna-4.0-xbox360).csproj @@ -103,6 +103,10 @@ AffineThreadPool.cs + + + ClonerHelpers.cs + ExpressionTreeCloner.cs diff --git a/Source/Cloning/CloneFactoryTest.cs b/Source/Cloning/CloneFactoryTest.cs index 7c99fbf..1562041 100644 --- a/Source/Cloning/CloneFactoryTest.cs +++ b/Source/Cloning/CloneFactoryTest.cs @@ -27,7 +27,21 @@ using NUnit.Framework; namespace Nuclex.Support.Cloning { /// Base class for unit tests verifying the clone factory - internal abstract class CloneFactoryTest { + public abstract class CloneFactoryTest { + + #region class DerivedReferenceType + + /// A derived reference type being used for testing + protected class DerivedReferenceType : TestReferenceType { + + /// Field holding an integer value for testing + public int DerivedField; + /// Property holding an integer value for testing + public int DerivedProperty { get; set; } + + } + + #endregion // class DerivedReferenceType #region class TestReferenceType @@ -84,8 +98,12 @@ namespace Nuclex.Support.Cloning { public TestReferenceType AlwaysNullProperty { get; set; } /// A property that only has a getter public TestReferenceType GetOnlyProperty { get { return null; } } - /// A property that only has a s + /// A property that only has a setter public TestReferenceType SetOnlyProperty { set { } } + /// Field typed as base class holding a derived instance + public TestReferenceType DerivedField; + /// Field typed as base class holding a derived instance + public TestReferenceType DerivedProperty { get; set; } } @@ -120,6 +138,10 @@ namespace Nuclex.Support.Cloning { public TestReferenceType GetOnlyProperty { get { return null; } } /// A property that only has a s public TestReferenceType SetOnlyProperty { set { } } + /// Field typed as base class holding a derived instance + public TestReferenceType DerivedField; + /// Field typed as base class holding a derived instance + public TestReferenceType DerivedProperty { get; set; } } @@ -172,12 +194,21 @@ namespace Nuclex.Support.Cloning { Assert.AreEqual(0, clone.ValueTypeField.TestProperty); Assert.AreEqual(0, clone.ValueTypeProperty.TestField); Assert.IsNull(clone.ReferenceTypeField); + Assert.IsNull(clone.DerivedField); if(isDeepClone) { Assert.AreNotSame(original.ReferenceTypeProperty, clone.ReferenceTypeProperty); Assert.AreNotSame( original.ReferenceTypeArrayProperty, clone.ReferenceTypeArrayProperty ); + Assert.AreNotSame(original.DerivedProperty, clone.DerivedProperty); + Assert.IsInstanceOf(clone.DerivedProperty); + + var originalDerived = (DerivedReferenceType)original.DerivedProperty; + var clonedDerived = (DerivedReferenceType)clone.DerivedProperty; + Assert.AreEqual(originalDerived.TestProperty, clonedDerived.TestProperty); + Assert.AreEqual(originalDerived.DerivedProperty, clonedDerived.DerivedProperty); + Assert.AreEqual(0, clone.ReferenceTypeProperty.TestField); Assert.AreNotSame( original.ReferenceTypeArrayProperty[1, 3][0], @@ -190,6 +221,7 @@ namespace Nuclex.Support.Cloning { 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.ReferenceTypeProperty, clone.ReferenceTypeProperty); Assert.AreSame( original.ReferenceTypeArrayProperty, clone.ReferenceTypeArrayProperty @@ -215,6 +247,25 @@ namespace Nuclex.Support.Cloning { if(isDeepClone) { Assert.AreNotSame(original.ReferenceTypeField, clone.ReferenceTypeField); Assert.AreNotSame(original.ReferenceTypeProperty, clone.ReferenceTypeProperty); + Assert.AreNotSame(original.DerivedField, clone.DerivedField); + Assert.AreNotSame(original.DerivedProperty, clone.DerivedProperty); + Assert.IsInstanceOf(clone.DerivedField); + Assert.IsInstanceOf(clone.DerivedProperty); + + var originalDerived = (DerivedReferenceType)original.DerivedField; + var clonedDerived = (DerivedReferenceType)clone.DerivedField; + Assert.AreEqual(originalDerived.TestField, clonedDerived.TestField); + Assert.AreEqual(originalDerived.TestProperty, clonedDerived.TestProperty); + Assert.AreEqual(originalDerived.DerivedField, clonedDerived.DerivedField); + Assert.AreEqual(originalDerived.DerivedProperty, clonedDerived.DerivedProperty); + + originalDerived = (DerivedReferenceType)original.DerivedProperty; + clonedDerived = (DerivedReferenceType)clone.DerivedProperty; + Assert.AreEqual(originalDerived.TestField, clonedDerived.TestField); + Assert.AreEqual(originalDerived.TestProperty, clonedDerived.TestProperty); + Assert.AreEqual(originalDerived.DerivedField, clonedDerived.DerivedField); + Assert.AreEqual(originalDerived.DerivedProperty, clonedDerived.DerivedProperty); + Assert.AreNotSame( original.ReferenceTypeArrayField, clone.ReferenceTypeArrayField ); @@ -238,6 +289,8 @@ namespace Nuclex.Support.Cloning { clone.ReferenceTypeArrayProperty[1, 3][2].TestField ); } else { + Assert.AreSame(original.DerivedField, clone.DerivedField); + Assert.AreSame(original.DerivedProperty, clone.DerivedProperty); Assert.AreSame(original.ReferenceTypeField, clone.ReferenceTypeField); Assert.AreSame(original.ReferenceTypeProperty, clone.ReferenceTypeProperty); Assert.AreSame( @@ -248,22 +301,6 @@ namespace Nuclex.Support.Cloning { ); } } - - 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 - ); } /// @@ -286,12 +323,21 @@ namespace Nuclex.Support.Cloning { Assert.AreEqual(0, clone.ValueTypeField.TestProperty); Assert.AreEqual(0, clone.ValueTypeProperty.TestField); Assert.IsNull(clone.ReferenceTypeField); + Assert.IsNull(clone.DerivedField); if(isDeepClone) { Assert.AreNotSame(original.ReferenceTypeProperty, clone.ReferenceTypeProperty); Assert.AreNotSame( original.ReferenceTypeArrayProperty, clone.ReferenceTypeArrayProperty ); + Assert.AreNotSame(original.DerivedProperty, clone.DerivedProperty); + Assert.IsInstanceOf(clone.DerivedProperty); + + var originalDerived = (DerivedReferenceType)original.DerivedProperty; + var clonedDerived = (DerivedReferenceType)clone.DerivedProperty; + Assert.AreEqual(originalDerived.TestProperty, clonedDerived.TestProperty); + Assert.AreEqual(originalDerived.DerivedProperty, clonedDerived.DerivedProperty); + Assert.AreEqual(0, clone.ReferenceTypeProperty.TestField); Assert.AreNotSame( original.ReferenceTypeArrayProperty[1, 3][0], @@ -304,6 +350,7 @@ namespace Nuclex.Support.Cloning { 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.ReferenceTypeProperty, clone.ReferenceTypeProperty); Assert.AreSame( original.ReferenceTypeArrayProperty, clone.ReferenceTypeArrayProperty @@ -329,6 +376,25 @@ namespace Nuclex.Support.Cloning { if(isDeepClone) { Assert.AreNotSame(original.ReferenceTypeField, clone.ReferenceTypeField); Assert.AreNotSame(original.ReferenceTypeProperty, clone.ReferenceTypeProperty); + Assert.AreNotSame(original.DerivedField, clone.DerivedField); + Assert.AreNotSame(original.DerivedProperty, clone.DerivedProperty); + Assert.IsInstanceOf(clone.DerivedField); + Assert.IsInstanceOf(clone.DerivedProperty); + + var originalDerived = (DerivedReferenceType)original.DerivedField; + var clonedDerived = (DerivedReferenceType)clone.DerivedField; + Assert.AreEqual(originalDerived.TestField, clonedDerived.TestField); + Assert.AreEqual(originalDerived.TestProperty, clonedDerived.TestProperty); + Assert.AreEqual(originalDerived.DerivedField, clonedDerived.DerivedField); + Assert.AreEqual(originalDerived.DerivedProperty, clonedDerived.DerivedProperty); + + originalDerived = (DerivedReferenceType)original.DerivedProperty; + clonedDerived = (DerivedReferenceType)clone.DerivedProperty; + Assert.AreEqual(originalDerived.TestField, clonedDerived.TestField); + Assert.AreEqual(originalDerived.TestProperty, clonedDerived.TestProperty); + Assert.AreEqual(originalDerived.DerivedField, clonedDerived.DerivedField); + Assert.AreEqual(originalDerived.DerivedProperty, clonedDerived.DerivedProperty); + Assert.AreNotSame( original.ReferenceTypeArrayField, clone.ReferenceTypeArrayField ); @@ -352,6 +418,8 @@ namespace Nuclex.Support.Cloning { clone.ReferenceTypeArrayProperty[1, 3][2].TestField ); } else { + Assert.AreSame(original.DerivedField, clone.DerivedField); + Assert.AreSame(original.DerivedProperty, clone.DerivedProperty); Assert.AreSame(original.ReferenceTypeField, clone.ReferenceTypeField); Assert.AreSame(original.ReferenceTypeProperty, clone.ReferenceTypeProperty); Assert.AreSame( @@ -427,6 +495,18 @@ namespace Nuclex.Support.Cloning { ReferenceTypeProperty = new TestReferenceType() { TestField = 246, TestProperty = 642, + }, + DerivedField = new DerivedReferenceType() { + DerivedField = 100, + DerivedProperty = 200, + TestField = 300, + TestProperty = 400 + }, + DerivedProperty = new DerivedReferenceType() { + DerivedField = 500, + DerivedProperty = 600, + TestField = 700, + TestProperty = 800 } }; } @@ -478,6 +558,18 @@ namespace Nuclex.Support.Cloning { ReferenceTypeProperty = new TestReferenceType() { TestField = 246, TestProperty = 642, + }, + DerivedField = new DerivedReferenceType() { + DerivedField = 100, + DerivedProperty = 200, + TestField = 300, + TestProperty = 400 + }, + DerivedProperty = new DerivedReferenceType() { + DerivedField = 500, + DerivedProperty = 600, + TestField = 700, + TestProperty = 800 } }; } diff --git a/Source/Cloning/ClonerHelpers.Test.cs b/Source/Cloning/ClonerHelpers.Test.cs new file mode 100644 index 0000000..f6beb0f --- /dev/null +++ b/Source/Cloning/ClonerHelpers.Test.cs @@ -0,0 +1,74 @@ +#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.Reflection; + +using NUnit.Framework; + +namespace Nuclex.Support.Cloning { + + /// Unit Test for the cloner helpers + [TestFixture] + internal class ClonerHelpersTest { + + #region class Base + + /// Base class used to test the helper methods + private class Base { + /// A simple public field + public int PublicBaseField; + /// An automatic property with a hidden backing field + public int PublicBaseProperty { get; set; } + } + + #endregion // class Base + + #region class Derived + + /// Derived class used to test the helper methods + private class Derived : Base { + /// A simple public field + public int PublicDerivedField; + /// An automatic property with a hidden backing field + public int PublicDerivedProperty { get; set; } + } + + #endregion // class Derived + + /// + /// Verifies that the GetFieldInfosIncludingBaseClasses() will include the backing + /// fields of automatically implemented properties in base classes + /// + [Test] + public void CanGetBackingFieldsForPropertiesInBaseClasses() { + FieldInfo[] fieldInfos = ClonerHelpers.GetFieldInfosIncludingBaseClasses( + typeof(Derived), BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance + ); + Assert.AreEqual(4, fieldInfos.Length); + } + + } + +} // namespace Nuclex.Support.Cloning + +#endif // UNITTEST diff --git a/Source/Cloning/ClonerHelpers.cs b/Source/Cloning/ClonerHelpers.cs new file mode 100644 index 0000000..466b8ad --- /dev/null +++ b/Source/Cloning/ClonerHelpers.cs @@ -0,0 +1,81 @@ +#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.Collections.Generic; +using System.Linq; +using System.Text; +using System.Reflection; + +namespace Nuclex.Support.Cloning { + + /// Contains helper methods for the cloners + internal static class ClonerHelpers { + + /// + /// Returns all the fields of a type, working around a weird reflection issue + /// where explicitly declared fields in base classes are returned, but not + /// automatic property backing fields. + /// + /// Type whose fields will be returned + /// Binding flags to use when querying the fields + /// All of the type's fields, including its base types + public static FieldInfo[] GetFieldInfosIncludingBaseClasses( + Type type, BindingFlags bindingFlags + ) { + FieldInfo[] fieldInfos = type.GetFields(bindingFlags); + + // If this class doesn't have a base, don't waste any time + if(type.BaseType == typeof(object)) { + return fieldInfos; + } else { // Otherwise, collect all types up to the furthest base class + var fieldInfoList = new List(fieldInfos); + while(type.BaseType != typeof(object)) { + type = type.BaseType; + fieldInfos = type.GetFields(bindingFlags); + + // Look for fields we do not have listed yet and merge them into the main list + for(int index = 0; index < fieldInfos.Length; ++index) { + bool found = false; + + for(int searchIndex = 0; searchIndex < fieldInfoList.Count; ++searchIndex) { + bool match = + (fieldInfoList[searchIndex].DeclaringType == fieldInfos[index].DeclaringType) && + (fieldInfoList[searchIndex].Name == fieldInfos[index].Name); + + if(match) { + found = true; + break; + } + } + + if(!found) { + fieldInfoList.Add(fieldInfos[index]); + } + } + } + + return fieldInfoList.ToArray(); + } + } + + } + +} // namespace Nuclex.Support.Cloning diff --git a/Source/Cloning/ExpressionTreeCloner.FieldBased.cs b/Source/Cloning/ExpressionTreeCloner.FieldBased.cs index 0bf9b35..c77a2ea 100644 --- a/Source/Cloning/ExpressionTreeCloner.FieldBased.cs +++ b/Source/Cloning/ExpressionTreeCloner.FieldBased.cs @@ -185,9 +185,8 @@ namespace Nuclex.Support.Cloning { ); // Enumerate all of the type's fields and generate transfer expressions for each - FieldInfo[] fieldInfos = clonedType.GetFields( - BindingFlags.Public | BindingFlags.NonPublic | - BindingFlags.Instance | BindingFlags.FlattenHierarchy + FieldInfo[] fieldInfos = ClonerHelpers.GetFieldInfosIncludingBaseClasses( + clonedType, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ); for(int index = 0; index < fieldInfos.Length; ++index) { FieldInfo fieldInfo = fieldInfos[index]; @@ -462,9 +461,9 @@ namespace Nuclex.Support.Cloning { ICollection transferExpressions ) { // Enumerate all of the type's fields and generate transfer expressions for each - FieldInfo[] fieldInfos = clonedType.GetFields( - BindingFlags.Public | BindingFlags.NonPublic | - BindingFlags.Instance | BindingFlags.FlattenHierarchy + FieldInfo[] fieldInfos = ClonerHelpers.GetFieldInfosIncludingBaseClasses( + clonedType, + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ); for(int index = 0; index < fieldInfos.Length; ++index) { FieldInfo fieldInfo = fieldInfos[index]; diff --git a/Source/Cloning/ExpressionTreeCloner.Test.cs b/Source/Cloning/ExpressionTreeCloner.Test.cs index 42c3a6e..c0b1357 100644 --- a/Source/Cloning/ExpressionTreeCloner.Test.cs +++ b/Source/Cloning/ExpressionTreeCloner.Test.cs @@ -29,7 +29,7 @@ namespace Nuclex.Support.Cloning { /// Unit Test for the expression tree-based cloner [TestFixture] - internal class ExpressionTreeClonerTest : CloneFactoryTest { + public class ExpressionTreeClonerTest : CloneFactoryTest { /// Initializes a new unit test suite for the reflection cloner public ExpressionTreeClonerTest() { diff --git a/Source/Cloning/ReflectionCloner.cs b/Source/Cloning/ReflectionCloner.cs index 2f778cd..a4fd617 100644 --- a/Source/Cloning/ReflectionCloner.cs +++ b/Source/Cloning/ReflectionCloner.cs @@ -167,9 +167,8 @@ namespace Nuclex.Support.Cloning { object clone = FormatterServices.GetUninitializedObject(originalType); #endif - FieldInfo[] fieldInfos = originalType.GetFields( - BindingFlags.Public | BindingFlags.NonPublic | - BindingFlags.Instance | BindingFlags.FlattenHierarchy + FieldInfo[] fieldInfos = ClonerHelpers.GetFieldInfosIncludingBaseClasses( + originalType, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ); for(int index = 0; index < fieldInfos.Length; ++index) { FieldInfo fieldInfo = fieldInfos[index]; @@ -250,9 +249,8 @@ namespace Nuclex.Support.Cloning { object clone = FormatterServices.GetUninitializedObject(originalType); #endif - FieldInfo[] fieldInfos = originalType.GetFields( - BindingFlags.Public | BindingFlags.NonPublic | - BindingFlags.Instance | BindingFlags.FlattenHierarchy + FieldInfo[] fieldInfos = ClonerHelpers.GetFieldInfosIncludingBaseClasses( + originalType, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ); for(int index = 0; index < fieldInfos.Length; ++index) { FieldInfo fieldInfo = fieldInfos[index]; diff --git a/Source/Cloning/SerializationCloner.cs b/Source/Cloning/SerializationCloner.cs index 5c0c2d6..7bff60d 100644 --- a/Source/Cloning/SerializationCloner.cs +++ b/Source/Cloning/SerializationCloner.cs @@ -118,9 +118,8 @@ namespace Nuclex.Support.Cloning { ) { Type originalType = objectToSerialize.GetType(); - FieldInfo[] fieldInfos = originalType.GetFields( - BindingFlags.Public | BindingFlags.NonPublic | - BindingFlags.Instance | BindingFlags.FlattenHierarchy + FieldInfo[] fieldInfos = ClonerHelpers.GetFieldInfosIncludingBaseClasses( + originalType, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ); for(int index = 0; index < fieldInfos.Length; ++index) { FieldInfo fieldInfo = fieldInfos[index]; @@ -144,9 +143,8 @@ namespace Nuclex.Support.Cloning { ) { Type originalType = deserializedObject.GetType(); - FieldInfo[] fieldInfos = originalType.GetFields( - BindingFlags.Public | BindingFlags.NonPublic | - BindingFlags.Instance | BindingFlags.FlattenHierarchy + FieldInfo[] fieldInfos = ClonerHelpers.GetFieldInfosIncludingBaseClasses( + originalType, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ); for(int index = 0; index < fieldInfos.Length; ++index) { FieldInfo fieldInfo = fieldInfos[index];