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];