The expression tree cloner was not able to assign read-only field -- fixed; expanded tests to safeguard against regressions

git-svn-id: file:///srv/devel/repo-conversion/nusu@265 d2e56fa2-650e-0410-a79f-9358c0239efd
This commit is contained in:
Markus Ewald 2012-03-06 13:39:02 +00:00
parent 4c408a56ad
commit 21754b8e87
3 changed files with 116 additions and 30 deletions

View File

@ -100,6 +100,8 @@ namespace Nuclex.Support.Cloning {
public TestReferenceType GetOnlyProperty { get { return null; } } public TestReferenceType GetOnlyProperty { get { return null; } }
/// <summary>A property that only has a setter</summary> /// <summary>A property that only has a setter</summary>
public TestReferenceType SetOnlyProperty { set { } } public TestReferenceType SetOnlyProperty { set { } }
/// <summary>A read-only field</summary>
public readonly TestValueType ReadOnlyField;
/// <summary>Field typed as base class holding a derived instance</summary> /// <summary>Field typed as base class holding a derived instance</summary>
public TestReferenceType DerivedField; public TestReferenceType DerivedField;
/// <summary>Field typed as base class holding a derived instance</summary> /// <summary>Field typed as base class holding a derived instance</summary>
@ -136,8 +138,10 @@ namespace Nuclex.Support.Cloning {
public TestReferenceType AlwaysNullProperty { get; set; } public TestReferenceType AlwaysNullProperty { get; set; }
/// <summary>A property that only has a getter</summary> /// <summary>A property that only has a getter</summary>
public TestReferenceType GetOnlyProperty { get { return null; } } public TestReferenceType GetOnlyProperty { get { return null; } }
/// <summary>A property that only has a s</summary> /// <summary>A property that only has a setter</summary>
public TestReferenceType SetOnlyProperty { set { } } public TestReferenceType SetOnlyProperty { set { } }
/// <summary>A read-only field</summary>
public readonly TestValueType ReadOnlyField;
/// <summary>Field typed as base class holding a derived instance</summary> /// <summary>Field typed as base class holding a derived instance</summary>
public TestReferenceType DerivedField; public TestReferenceType DerivedField;
/// <summary>Field typed as base class holding a derived instance</summary> /// <summary>Field typed as base class holding a derived instance</summary>

View File

@ -191,6 +191,40 @@ namespace Nuclex.Support.Cloning {
for(int index = 0; index < fieldInfos.Length; ++index) { for(int index = 0; index < fieldInfos.Length; ++index) {
FieldInfo fieldInfo = fieldInfos[index]; FieldInfo fieldInfo = fieldInfos[index];
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( transferExpressions.Add(
Expression.Assign( Expression.Assign(
Expression.Field(clone, fieldInfo), Expression.Field(clone, fieldInfo),
@ -198,6 +232,7 @@ namespace Nuclex.Support.Cloning {
) )
); );
} }
}
// Make sure the clone is the last thing in the block to set the return value // Make sure the clone is the last thing in the block to set the return value
transferExpressions.Add(clone); transferExpressions.Add(clone);
@ -555,15 +590,11 @@ namespace Nuclex.Support.Cloning {
MethodInfo getTypeMethodInfo = typeof(object).GetMethod("GetType"); MethodInfo getTypeMethodInfo = typeof(object).GetMethod("GetType");
MethodInfo invokeMethodInfo = typeof(Func<object, object>).GetMethod("Invoke"); MethodInfo invokeMethodInfo = typeof(Func<object, object>).GetMethod("Invoke");
// Generate expressions to do this: // Equivalent to
// clone.SomeField = getOrCreateDeepFieldBasedCloner( // (TField)getOrCreateDeepFieldBasedCloner(
// original.SomeField.GetType() // original.SomeField.GetType()
// ).Invoke(original.SomeField); // ).Invoke(original.SomeField);
fieldTransferExpressions.Add( Expression result = Expression.Call(
Expression.Assign(
Expression.Field(clone, fieldInfo),
Expression.Convert(
Expression.Call(
Expression.Call( Expression.Call(
getOrCreateClonerMethodInfo, getOrCreateClonerMethodInfo,
Expression.Call( Expression.Call(
@ -572,11 +603,49 @@ namespace Nuclex.Support.Cloning {
), ),
invokeMethodInfo, invokeMethodInfo,
Expression.Field(original, fieldInfo) Expression.Field(original, fieldInfo)
), );
fieldType
) // 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 // Wrap up the generated array or complex reference type transfer expressions
@ -591,6 +660,19 @@ namespace Nuclex.Support.Cloning {
); );
} }
/// <summary>Assigns the value of an .initonly field</summary>
/// <typeparam name="TValueType">Type of structure that contains the field</typeparam>
/// <param name="instance">
/// Reference to the structure on which the field will be assigned
/// </param>
/// <param name="fieldInfo">Field that will be assigned</param>
/// <param name="value">Value that will be assigned to the field</param>
private static void assignInitOnlyField<TValueType>(
ref TValueType instance, FieldInfo fieldInfo, object value
) where TValueType : struct {
fieldInfo.SetValueDirect(__makeref(instance), value);
}
} }
} // namespace Nuclex.Support.Cloning } // namespace Nuclex.Support.Cloning

View File

@ -190,7 +190,7 @@ namespace Nuclex.Support.Collections {
[Test] [Test]
public void CanDetermineProperSubsetAndSuperset() { public void CanDetermineProperSubsetAndSuperset() {
var set1 = new ObservableSet<int>() { 1, 2, 3 }; var set1 = new ObservableSet<int>() { 1, 2, 3 };
var set2 = new ObservableSet<int>() { 1, 3 }; var set2 = new HashSet<int>() { 1, 3 };
Assert.IsTrue(set1.IsProperSupersetOf(set2)); Assert.IsTrue(set1.IsProperSupersetOf(set2));
Assert.IsTrue(set2.IsProperSubsetOf(set1)); Assert.IsTrue(set2.IsProperSubsetOf(set1));
@ -208,7 +208,7 @@ namespace Nuclex.Support.Collections {
[Test] [Test]
public void CanDetermineSubsetAndSuperset() { public void CanDetermineSubsetAndSuperset() {
var set1 = new ObservableSet<int>() { 1, 2, 3 }; var set1 = new ObservableSet<int>() { 1, 2, 3 };
var set2 = new ObservableSet<int>() { 1, 2, 3 }; var set2 = new HashSet<int>() { 1, 2, 3 };
Assert.IsTrue(set1.IsSupersetOf(set2)); Assert.IsTrue(set1.IsSupersetOf(set2));
Assert.IsTrue(set2.IsSubsetOf(set1)); Assert.IsTrue(set2.IsSubsetOf(set1));
@ -225,7 +225,7 @@ namespace Nuclex.Support.Collections {
[Test] [Test]
public void CanDetermineOverlap() { public void CanDetermineOverlap() {
var set1 = new ObservableSet<int>() { 1, 3, 5 }; var set1 = new ObservableSet<int>() { 1, 3, 5 };
var set2 = new ObservableSet<int>() { 3 }; var set2 = new HashSet<int>() { 3 };
Assert.IsTrue(set1.Overlaps(set2)); Assert.IsTrue(set1.Overlaps(set2));
Assert.IsTrue(set2.Overlaps(set1)); Assert.IsTrue(set2.Overlaps(set1));
@ -237,7 +237,7 @@ namespace Nuclex.Support.Collections {
[Test] [Test]
public void CanDetermineSetEquality() { public void CanDetermineSetEquality() {
var set1 = new ObservableSet<int>() { 1, 3, 5 }; var set1 = new ObservableSet<int>() { 1, 3, 5 };
var set2 = new ObservableSet<int>() { 3, 1, 5 }; var set2 = new HashSet<int>() { 3, 1, 5 };
Assert.IsTrue(set1.SetEquals(set2)); Assert.IsTrue(set1.SetEquals(set2));
Assert.IsTrue(set2.SetEquals(set1)); Assert.IsTrue(set2.SetEquals(set1));
@ -254,7 +254,7 @@ namespace Nuclex.Support.Collections {
[Test] [Test]
public void CanBeSymmetricallyExcepted() { public void CanBeSymmetricallyExcepted() {
var set1 = new ObservableSet<int>() { 1, 2, 3 }; var set1 = new ObservableSet<int>() { 1, 2, 3 };
var set2 = new ObservableSet<int>() { 3, 4, 5 }; var set2 = new HashSet<int>() { 3, 4, 5 };
set1.SymmetricExceptWith(set2); set1.SymmetricExceptWith(set2);