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:
parent
4c408a56ad
commit
21754b8e87
|
@ -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>
|
||||||
|
|
|
@ -191,12 +191,47 @@ 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];
|
||||||
|
|
||||||
transferExpressions.Add(
|
if(fieldInfo.IsInitOnly) {
|
||||||
Expression.Assign(
|
Expression source = Expression.Field(typedOriginal, fieldInfo);
|
||||||
Expression.Field(clone, fieldInfo),
|
if(fieldInfo.FieldType.IsValueType) {
|
||||||
Expression.Field(typedOriginal, fieldInfo)
|
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
|
// 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 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.Call(
|
||||||
Expression.Field(clone, fieldInfo),
|
getOrCreateClonerMethodInfo,
|
||||||
Expression.Convert(
|
Expression.Call(
|
||||||
Expression.Call(
|
Expression.Field(original, fieldInfo), getTypeMethodInfo
|
||||||
Expression.Call(
|
|
||||||
getOrCreateClonerMethodInfo,
|
|
||||||
Expression.Call(
|
|
||||||
Expression.Field(original, fieldInfo), getTypeMethodInfo
|
|
||||||
)
|
|
||||||
),
|
|
||||||
invokeMethodInfo,
|
|
||||||
Expression.Field(original, fieldInfo)
|
|
||||||
),
|
|
||||||
fieldType
|
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
|
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
|
// 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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user