diff --git a/Source/Cloning/CloneFactoryTest.cs b/Source/Cloning/CloneFactoryTest.cs index f768649..7d48bcc 100644 --- a/Source/Cloning/CloneFactoryTest.cs +++ b/Source/Cloning/CloneFactoryTest.cs @@ -100,6 +100,8 @@ namespace Nuclex.Support.Cloning { public TestReferenceType GetOnlyProperty { get { return null; } } /// A property that only has a setter public TestReferenceType SetOnlyProperty { set { } } + /// A read-only field + public readonly TestValueType ReadOnlyField; /// Field typed as base class holding a derived instance public TestReferenceType DerivedField; /// Field typed as base class holding a derived instance @@ -136,8 +138,10 @@ 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 { } } + /// A read-only field + public readonly TestValueType ReadOnlyField; /// Field typed as base class holding a derived instance public TestReferenceType DerivedField; /// Field typed as base class holding a derived instance diff --git a/Source/Cloning/ExpressionTreeCloner.FieldBased.cs b/Source/Cloning/ExpressionTreeCloner.FieldBased.cs index 98a2d26..1fa5686 100644 --- a/Source/Cloning/ExpressionTreeCloner.FieldBased.cs +++ b/Source/Cloning/ExpressionTreeCloner.FieldBased.cs @@ -191,12 +191,47 @@ namespace Nuclex.Support.Cloning { for(int index = 0; index < fieldInfos.Length; ++index) { FieldInfo fieldInfo = fieldInfos[index]; - transferExpressions.Add( - Expression.Assign( - Expression.Field(clone, fieldInfo), - Expression.Field(typedOriginal, fieldInfo) - ) - ); + if(fieldInfo.IsInitOnly) { + Expression source = Expression.Field(typedOriginal, fieldInfo); + if(fieldInfo.FieldType.IsValueType) { + source = Expression.Convert(source, typeof(object)); + } + + if(clone.Type.IsValueType) { + MethodInfo assignInitOnlyField = typeof(ExpressionTreeCloner).GetMethod( + "assignInitOnlyField", BindingFlags.Static | BindingFlags.NonPublic + ).MakeGenericMethod(clone.Type); + + transferExpressions.Add( + Expression.Call( + assignInitOnlyField, + clone, + Expression.Constant(fieldInfo), + source + ) + ); + } else { + MethodInfo setValueMethodInfo = typeof(FieldInfo).GetMethod( + "SetValue", new Type[] { typeof(object), typeof(object) } + ); + + transferExpressions.Add( + Expression.Call( + Expression.Constant(fieldInfo), + setValueMethodInfo, + clone, + source + ) + ); + } + } else { + transferExpressions.Add( + Expression.Assign( + Expression.Field(clone, fieldInfo), + Expression.Field(typedOriginal, fieldInfo) + ) + ); + } } // Make sure the clone is the last thing in the block to set the return value @@ -555,28 +590,62 @@ namespace Nuclex.Support.Cloning { MethodInfo getTypeMethodInfo = typeof(object).GetMethod("GetType"); MethodInfo invokeMethodInfo = typeof(Func).GetMethod("Invoke"); - // Generate expressions to do this: - // clone.SomeField = getOrCreateDeepFieldBasedCloner( + // Equivalent to + // (TField)getOrCreateDeepFieldBasedCloner( // original.SomeField.GetType() // ).Invoke(original.SomeField); - fieldTransferExpressions.Add( - Expression.Assign( - Expression.Field(clone, fieldInfo), - Expression.Convert( - Expression.Call( - Expression.Call( - getOrCreateClonerMethodInfo, - Expression.Call( - Expression.Field(original, fieldInfo), getTypeMethodInfo - ) - ), - invokeMethodInfo, - Expression.Field(original, fieldInfo) - ), - fieldType + Expression result = Expression.Call( + Expression.Call( + getOrCreateClonerMethodInfo, + Expression.Call( + Expression.Field(original, fieldInfo), getTypeMethodInfo ) - ) + ), + invokeMethodInfo, + Expression.Field(original, fieldInfo) ); + + // If the field is a readonly field, set the value via reflection because + // Expression Trees do not support assigning .initonly fields directly yet + if(fieldInfo.IsInitOnly) { + if(fieldInfo.FieldType.IsValueType) { + result = Expression.Convert(result, typeof(object)); + } + + if(clone.Type.IsValueType) { + MethodInfo assignInitOnlyField = typeof(ExpressionTreeCloner).GetMethod( + "assignInitOnlyField", BindingFlags.Static | BindingFlags.NonPublic + ).MakeGenericMethod(clone.Type); + fieldTransferExpressions.Add( + Expression.Call( + assignInitOnlyField, + clone, + Expression.Constant(fieldInfo), + result + ) + ); + } else { + MethodInfo setValueMethodInfo = typeof(FieldInfo).GetMethod( + "SetValue", new Type[] { typeof(object), typeof(object) } + ); + + fieldTransferExpressions.Add( + Expression.Call( + Expression.Constant(fieldInfo), + setValueMethodInfo, + clone, + result + ) + ); + } + } else { + fieldTransferExpressions.Add( + Expression.Assign( + Expression.Field(clone, fieldInfo), + Expression.Convert(result, fieldType) + ) + ); + } } // Wrap up the generated array or complex reference type transfer expressions @@ -591,6 +660,19 @@ namespace Nuclex.Support.Cloning { ); } + /// Assigns the value of an .initonly field + /// Type of structure that contains the field + /// + /// Reference to the structure on which the field will be assigned + /// + /// Field that will be assigned + /// Value that will be assigned to the field + private static void assignInitOnlyField( + ref TValueType instance, FieldInfo fieldInfo, object value + ) where TValueType : struct { + fieldInfo.SetValueDirect(__makeref(instance), value); + } + } } // namespace Nuclex.Support.Cloning diff --git a/Source/Collections/ObservableSet.Test.cs b/Source/Collections/ObservableSet.Test.cs index aae951f..fa29bec 100644 --- a/Source/Collections/ObservableSet.Test.cs +++ b/Source/Collections/ObservableSet.Test.cs @@ -190,7 +190,7 @@ namespace Nuclex.Support.Collections { [Test] public void CanDetermineProperSubsetAndSuperset() { var set1 = new ObservableSet() { 1, 2, 3 }; - var set2 = new ObservableSet() { 1, 3 }; + var set2 = new HashSet() { 1, 3 }; Assert.IsTrue(set1.IsProperSupersetOf(set2)); Assert.IsTrue(set2.IsProperSubsetOf(set1)); @@ -208,7 +208,7 @@ namespace Nuclex.Support.Collections { [Test] public void CanDetermineSubsetAndSuperset() { var set1 = new ObservableSet() { 1, 2, 3 }; - var set2 = new ObservableSet() { 1, 2, 3 }; + var set2 = new HashSet() { 1, 2, 3 }; Assert.IsTrue(set1.IsSupersetOf(set2)); Assert.IsTrue(set2.IsSubsetOf(set1)); @@ -225,7 +225,7 @@ namespace Nuclex.Support.Collections { [Test] public void CanDetermineOverlap() { var set1 = new ObservableSet() { 1, 3, 5 }; - var set2 = new ObservableSet() { 3 }; + var set2 = new HashSet() { 3 }; Assert.IsTrue(set1.Overlaps(set2)); Assert.IsTrue(set2.Overlaps(set1)); @@ -237,7 +237,7 @@ namespace Nuclex.Support.Collections { [Test] public void CanDetermineSetEquality() { var set1 = new ObservableSet() { 1, 3, 5 }; - var set2 = new ObservableSet() { 3, 1, 5 }; + var set2 = new HashSet() { 3, 1, 5 }; Assert.IsTrue(set1.SetEquals(set2)); Assert.IsTrue(set2.SetEquals(set1)); @@ -254,7 +254,7 @@ namespace Nuclex.Support.Collections { [Test] public void CanBeSymmetricallyExcepted() { var set1 = new ObservableSet() { 1, 2, 3 }; - var set2 = new ObservableSet() { 3, 4, 5 }; + var set2 = new HashSet() { 3, 4, 5 }; set1.SymmetricExceptWith(set2);