Fixed a bug in the cloners: if a class has properties with only a setter or only a getter, the property-based clones now ignore that property; added a unit test that verifies types without a default constructor can be cloned as well
git-svn-id: file:///srv/devel/repo-conversion/nusu@248 d2e56fa2-650e-0410-a79f-9358c0239efd
This commit is contained in:
parent
48e1674912
commit
0290444140
|
@ -78,6 +78,14 @@ namespace Nuclex.Support.Cloning {
|
||||||
public TestReferenceType[,][] ReferenceTypeArrayField;
|
public TestReferenceType[,][] ReferenceTypeArrayField;
|
||||||
/// <summary>An array property of reference types</summary>
|
/// <summary>An array property of reference types</summary>
|
||||||
public TestReferenceType[,][] ReferenceTypeArrayProperty { get; set; }
|
public TestReferenceType[,][] ReferenceTypeArrayProperty { get; set; }
|
||||||
|
/// <summary>A reference type field that's always null</summary>
|
||||||
|
public TestReferenceType AlwaysNullField;
|
||||||
|
/// <summary>A reference type property that's always null</summary>
|
||||||
|
public TestReferenceType AlwaysNullProperty { get; set; }
|
||||||
|
/// <summary>A property that only has a getter</summary>
|
||||||
|
public TestReferenceType GetOnlyProperty { get { return null; } }
|
||||||
|
/// <summary>A property that only has a s</summary>
|
||||||
|
public TestReferenceType SetOnlyProperty { set { } }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,11 +112,44 @@ namespace Nuclex.Support.Cloning {
|
||||||
public TestReferenceType[,][] ReferenceTypeArrayField;
|
public TestReferenceType[,][] ReferenceTypeArrayField;
|
||||||
/// <summary>An array property of reference types</summary>
|
/// <summary>An array property of reference types</summary>
|
||||||
public TestReferenceType[,][] ReferenceTypeArrayProperty { get; set; }
|
public TestReferenceType[,][] ReferenceTypeArrayProperty { get; set; }
|
||||||
|
/// <summary>A reference type field that's always null</summary>
|
||||||
|
public TestReferenceType AlwaysNullField;
|
||||||
|
/// <summary>A reference type property that's always null</summary>
|
||||||
|
public TestReferenceType AlwaysNullProperty { get; set; }
|
||||||
|
/// <summary>A property that only has a getter</summary>
|
||||||
|
public TestReferenceType GetOnlyProperty { get { return null; } }
|
||||||
|
/// <summary>A property that only has a s</summary>
|
||||||
|
public TestReferenceType SetOnlyProperty { set { } }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion // struct HierarchicalReferenceType
|
#endregion // struct HierarchicalReferenceType
|
||||||
|
|
||||||
|
#region class ClassWithoutDefaultConstructor
|
||||||
|
|
||||||
|
/// <summary>A class that does not have a default constructor</summary>
|
||||||
|
public class ClassWithoutDefaultConstructor {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the class without default constructor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dummy">Dummy value that will be saved by the instance</param>
|
||||||
|
public ClassWithoutDefaultConstructor(int dummy) {
|
||||||
|
this.dummy = dummy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Dummy value that has been saved by the instance</summary>
|
||||||
|
public int Dummy {
|
||||||
|
get { return this.dummy; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Dummy value that has been saved by the instance</summary>
|
||||||
|
private int dummy;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion // class ClassWithoutDefaultConstructor
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Verifies that a cloned object exhibits the expected state for the type of
|
/// Verifies that a cloned object exhibits the expected state for the type of
|
||||||
/// clone that has been performed
|
/// clone that has been performed
|
||||||
|
|
|
@ -213,44 +213,46 @@ namespace Nuclex.Support.Cloning {
|
||||||
);
|
);
|
||||||
for(int index = 0; index < propertyInfos.Length; ++index) {
|
for(int index = 0; index < propertyInfos.Length; ++index) {
|
||||||
PropertyInfo propertyInfo = propertyInfos[index];
|
PropertyInfo propertyInfo = propertyInfos[index];
|
||||||
Type propertyType = propertyInfo.PropertyType;
|
if(propertyInfo.CanRead && propertyInfo.CanWrite) {
|
||||||
|
Type propertyType = propertyInfo.PropertyType;
|
||||||
|
|
||||||
if(propertyType.IsPrimitive || (propertyType == typeof(string))) {
|
if(propertyType.IsPrimitive || (propertyType == typeof(string))) {
|
||||||
transferExpressions.Add(
|
transferExpressions.Add(
|
||||||
Expression.Assign(
|
Expression.Assign(
|
||||||
Expression.Property(clone, propertyInfo),
|
Expression.Property(clone, propertyInfo),
|
||||||
Expression.Property(original, propertyInfo)
|
Expression.Property(original, propertyInfo)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
} else if(propertyType.IsValueType) {
|
} else if(propertyType.IsValueType) {
|
||||||
ParameterExpression originalProperty = Expression.Variable(propertyType);
|
ParameterExpression originalProperty = Expression.Variable(propertyType);
|
||||||
variables.Add(originalProperty);
|
variables.Add(originalProperty);
|
||||||
transferExpressions.Add(
|
transferExpressions.Add(
|
||||||
Expression.Assign(
|
Expression.Assign(
|
||||||
originalProperty, Expression.Property(original, propertyInfo)
|
originalProperty, Expression.Property(original, propertyInfo)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
ParameterExpression clonedProperty = Expression.Variable(propertyType);
|
ParameterExpression clonedProperty = Expression.Variable(propertyType);
|
||||||
variables.Add(clonedProperty);
|
variables.Add(clonedProperty);
|
||||||
transferExpressions.Add(
|
transferExpressions.Add(
|
||||||
Expression.Assign(clonedProperty, Expression.New(propertyType))
|
Expression.Assign(clonedProperty, Expression.New(propertyType))
|
||||||
);
|
);
|
||||||
|
|
||||||
generateShallowPropertyBasedComplexCloneExpressions(propertyType, originalProperty, clonedProperty, transferExpressions, variables);
|
generateShallowPropertyBasedComplexCloneExpressions(propertyType, originalProperty, clonedProperty, transferExpressions, variables);
|
||||||
|
|
||||||
transferExpressions.Add(
|
transferExpressions.Add(
|
||||||
Expression.Assign(
|
Expression.Assign(
|
||||||
Expression.Property(clone, propertyInfo), clonedProperty
|
Expression.Property(clone, propertyInfo), clonedProperty
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
transferExpressions.Add(
|
transferExpressions.Add(
|
||||||
Expression.Assign(
|
Expression.Assign(
|
||||||
Expression.Property(clone, propertyInfo),
|
Expression.Property(clone, propertyInfo),
|
||||||
Expression.Property(original, propertyInfo)
|
Expression.Property(original, propertyInfo)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -503,52 +505,54 @@ namespace Nuclex.Support.Cloning {
|
||||||
);
|
);
|
||||||
for(int index = 0; index < propertyInfos.Length; ++index) {
|
for(int index = 0; index < propertyInfos.Length; ++index) {
|
||||||
PropertyInfo propertyInfo = propertyInfos[index];
|
PropertyInfo propertyInfo = propertyInfos[index];
|
||||||
Type propertyType = propertyInfo.PropertyType;
|
if(propertyInfo.CanRead && propertyInfo.CanWrite) {
|
||||||
|
Type propertyType = propertyInfo.PropertyType;
|
||||||
|
|
||||||
if(propertyType.IsPrimitive || (propertyType == typeof(string))) {
|
if(propertyType.IsPrimitive || (propertyType == typeof(string))) {
|
||||||
// Primitive types and strings can be transferred by simple assignment
|
// Primitive types and strings can be transferred by simple assignment
|
||||||
transferExpressions.Add(
|
transferExpressions.Add(
|
||||||
Expression.Assign(
|
Expression.Assign(
|
||||||
Expression.Property(clone, propertyInfo),
|
Expression.Property(clone, propertyInfo),
|
||||||
Expression.Property(original, propertyInfo)
|
Expression.Property(original, propertyInfo)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
} else if(propertyType.IsValueType) {
|
} else if(propertyType.IsValueType) {
|
||||||
ParameterExpression originalProperty = Expression.Variable(propertyType);
|
ParameterExpression originalProperty = Expression.Variable(propertyType);
|
||||||
variables.Add(originalProperty);
|
variables.Add(originalProperty);
|
||||||
ParameterExpression clonedProperty = Expression.Variable(propertyType);
|
ParameterExpression clonedProperty = Expression.Variable(propertyType);
|
||||||
variables.Add(clonedProperty);
|
variables.Add(clonedProperty);
|
||||||
|
|
||||||
transferExpressions.Add(
|
transferExpressions.Add(
|
||||||
Expression.Assign(
|
Expression.Assign(
|
||||||
originalProperty, Expression.Property(original, propertyInfo)
|
originalProperty, Expression.Property(original, propertyInfo)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
transferExpressions.Add(
|
transferExpressions.Add(
|
||||||
Expression.Assign(clonedProperty, Expression.New(propertyType))
|
Expression.Assign(clonedProperty, Expression.New(propertyType))
|
||||||
);
|
);
|
||||||
|
|
||||||
// A nested value type is part of the parent and will have its properties directly
|
// A nested value type is part of the parent and will have its properties directly
|
||||||
// assigned without boxing, new instance creation or anything like that.
|
// assigned without boxing, new instance creation or anything like that.
|
||||||
generatePropertyBasedComplexTypeTransferExpressions(
|
generatePropertyBasedComplexTypeTransferExpressions(
|
||||||
propertyType,
|
propertyType,
|
||||||
originalProperty,
|
originalProperty,
|
||||||
clonedProperty,
|
clonedProperty,
|
||||||
variables,
|
variables,
|
||||||
transferExpressions
|
transferExpressions
|
||||||
);
|
);
|
||||||
|
|
||||||
transferExpressions.Add(
|
transferExpressions.Add(
|
||||||
Expression.Assign(
|
Expression.Assign(
|
||||||
Expression.Property(clone, propertyInfo),
|
Expression.Property(clone, propertyInfo),
|
||||||
clonedProperty
|
clonedProperty
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
generatePropertyBasedReferenceTypeTransferExpressions(
|
generatePropertyBasedReferenceTypeTransferExpressions(
|
||||||
original, clone, transferExpressions, variables, propertyInfo, propertyType
|
original, clone, transferExpressions, variables, propertyInfo, propertyType
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,19 @@ namespace Nuclex.Support.Cloning {
|
||||||
Assert.IsNull(this.cloneFactory.ShallowPropertyClone<object>(null));
|
Assert.IsNull(this.cloneFactory.ShallowPropertyClone<object>(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies that clones of objects whose class doesn't possess a default constructor
|
||||||
|
/// can be made
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void ClassWithoutDefaultConstructorCanBeCloned() {
|
||||||
|
var original = new ClassWithoutDefaultConstructor(1234);
|
||||||
|
ClassWithoutDefaultConstructor clone = this.cloneFactory.DeepFieldClone(original);
|
||||||
|
|
||||||
|
Assert.AreNotSame(original, clone);
|
||||||
|
Assert.AreEqual(original.Dummy, clone.Dummy);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Verifies that clones of primitive types can be created</summary>
|
/// <summary>Verifies that clones of primitive types can be created</summary>
|
||||||
[Test]
|
[Test]
|
||||||
public void PrimitiveTypesCanBeCloned() {
|
public void PrimitiveTypesCanBeCloned() {
|
||||||
|
|
|
@ -45,6 +45,19 @@ namespace Nuclex.Support.Cloning {
|
||||||
Assert.IsNull(this.cloneFactory.ShallowPropertyClone<object>(null));
|
Assert.IsNull(this.cloneFactory.ShallowPropertyClone<object>(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies that clones of objects whose class doesn't possess a default constructor
|
||||||
|
/// can be made
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void ClassWithoutDefaultConstructorCanBeCloned() {
|
||||||
|
var original = new ClassWithoutDefaultConstructor(1234);
|
||||||
|
ClassWithoutDefaultConstructor clone = this.cloneFactory.DeepFieldClone(original);
|
||||||
|
|
||||||
|
Assert.AreNotSame(original, clone);
|
||||||
|
Assert.AreEqual(original.Dummy, clone.Dummy);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Verifies that clones of primitive types can be created</summary>
|
/// <summary>Verifies that clones of primitive types can be created</summary>
|
||||||
[Test]
|
[Test]
|
||||||
public void PrimitiveTypesCanBeCloned() {
|
public void PrimitiveTypesCanBeCloned() {
|
||||||
|
|
|
@ -196,19 +196,21 @@ namespace Nuclex.Support.Cloning {
|
||||||
);
|
);
|
||||||
for(int index = 0; index < propertyInfos.Length; ++index) {
|
for(int index = 0; index < propertyInfos.Length; ++index) {
|
||||||
PropertyInfo propertyInfo = propertyInfos[index];
|
PropertyInfo propertyInfo = propertyInfos[index];
|
||||||
Type propertyType = propertyInfo.PropertyType;
|
if(propertyInfo.CanRead && propertyInfo.CanWrite) {
|
||||||
object originalValue = propertyInfo.GetValue(original, null);
|
Type propertyType = propertyInfo.PropertyType;
|
||||||
if(originalValue != null) {
|
object originalValue = propertyInfo.GetValue(original, null);
|
||||||
if(propertyType.IsPrimitive || (propertyType == typeof(string))) {
|
if(originalValue != null) {
|
||||||
// Primitive types can be assigned directly
|
if(propertyType.IsPrimitive || (propertyType == typeof(string))) {
|
||||||
propertyInfo.SetValue(clone, originalValue, null);
|
// Primitive types can be assigned directly
|
||||||
} else if(propertyType.IsValueType) {
|
propertyInfo.SetValue(clone, originalValue, null);
|
||||||
// Value types are seen as part of the original type and are thus recursed into
|
} else if(propertyType.IsValueType) {
|
||||||
propertyInfo.SetValue(clone, shallowCloneComplexPropertyBased(originalValue), null);
|
// Value types are seen as part of the original type and are thus recursed into
|
||||||
} else if(propertyType.IsArray) { // Arrays are assigned directly in a shallow clone
|
propertyInfo.SetValue(clone, shallowCloneComplexPropertyBased(originalValue), null);
|
||||||
propertyInfo.SetValue(clone, originalValue, null);
|
} else if(propertyType.IsArray) { // Arrays are assigned directly in a shallow clone
|
||||||
} else { // Complex types are directly assigned without creating a copy
|
propertyInfo.SetValue(clone, originalValue, null);
|
||||||
propertyInfo.SetValue(clone, originalValue, null);
|
} else { // Complex types are directly assigned without creating a copy
|
||||||
|
propertyInfo.SetValue(clone, originalValue, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -366,20 +368,22 @@ namespace Nuclex.Support.Cloning {
|
||||||
);
|
);
|
||||||
for(int index = 0; index < propertyInfos.Length; ++index) {
|
for(int index = 0; index < propertyInfos.Length; ++index) {
|
||||||
PropertyInfo propertyInfo = propertyInfos[index];
|
PropertyInfo propertyInfo = propertyInfos[index];
|
||||||
Type propertyType = propertyInfo.PropertyType;
|
if(propertyInfo.CanRead && propertyInfo.CanWrite) {
|
||||||
object originalValue = propertyInfo.GetValue(original, null);
|
Type propertyType = propertyInfo.PropertyType;
|
||||||
if(originalValue != null) {
|
object originalValue = propertyInfo.GetValue(original, null);
|
||||||
if(propertyType.IsPrimitive || (propertyType == typeof(string))) {
|
if(originalValue != null) {
|
||||||
// Primitive types can be assigned directly
|
if(propertyType.IsPrimitive || (propertyType == typeof(string))) {
|
||||||
propertyInfo.SetValue(clone, originalValue, null);
|
// Primitive types can be assigned directly
|
||||||
} else if(propertyType.IsArray) { // Arrays need to be cloned element-by-element
|
propertyInfo.SetValue(clone, originalValue, null);
|
||||||
propertyInfo.SetValue(
|
} else if(propertyType.IsArray) { // Arrays need to be cloned element-by-element
|
||||||
clone,
|
propertyInfo.SetValue(
|
||||||
deepCloneArrayPropertyBased((Array)originalValue, propertyType.GetElementType()),
|
clone,
|
||||||
null
|
deepCloneArrayPropertyBased((Array)originalValue, propertyType.GetElementType()),
|
||||||
);
|
null
|
||||||
} else { // Complex types need to be cloned member-by-member
|
);
|
||||||
propertyInfo.SetValue(clone, deepCloneSinglePropertyBased(originalValue), null);
|
} else { // Complex types need to be cloned member-by-member
|
||||||
|
propertyInfo.SetValue(clone, deepCloneSinglePropertyBased(originalValue), null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,19 @@ namespace Nuclex.Support.Cloning {
|
||||||
Assert.IsNull(this.cloneFactory.DeepPropertyClone<object>(null));
|
Assert.IsNull(this.cloneFactory.DeepPropertyClone<object>(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies that clones of objects whose class doesn't possess a default constructor
|
||||||
|
/// can be made
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void ClassWithoutDefaultConstructorCanBeCloned() {
|
||||||
|
var original = new ClassWithoutDefaultConstructor(1234);
|
||||||
|
ClassWithoutDefaultConstructor clone = this.cloneFactory.DeepFieldClone(original);
|
||||||
|
|
||||||
|
Assert.AreNotSame(original, clone);
|
||||||
|
Assert.AreEqual(original.Dummy, clone.Dummy);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Verifies that clones of primitive types can be created</summary>
|
/// <summary>Verifies that clones of primitive types can be created</summary>
|
||||||
[Test]
|
[Test]
|
||||||
public void PrimitiveTypesCanBeCloned() {
|
public void PrimitiveTypesCanBeCloned() {
|
||||||
|
|
|
@ -184,7 +184,9 @@ namespace Nuclex.Support.Cloning {
|
||||||
);
|
);
|
||||||
for(int index = 0; index < propertyInfos.Length; ++index) {
|
for(int index = 0; index < propertyInfos.Length; ++index) {
|
||||||
PropertyInfo propertyInfo = propertyInfos[index];
|
PropertyInfo propertyInfo = propertyInfos[index];
|
||||||
info.AddValue(propertyInfo.Name, propertyInfo.GetValue(objectToSerialize, null));
|
if(propertyInfo.CanRead && propertyInfo.CanWrite) {
|
||||||
|
info.AddValue(propertyInfo.Name, propertyInfo.GetValue(objectToSerialize, null));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,11 +212,13 @@ namespace Nuclex.Support.Cloning {
|
||||||
);
|
);
|
||||||
for(int index = 0; index < propertyInfos.Length; ++index) {
|
for(int index = 0; index < propertyInfos.Length; ++index) {
|
||||||
PropertyInfo propertyInfo = propertyInfos[index];
|
PropertyInfo propertyInfo = propertyInfos[index];
|
||||||
propertyInfo.SetValue(
|
if(propertyInfo.CanRead && propertyInfo.CanWrite) {
|
||||||
deserializedObject,
|
propertyInfo.SetValue(
|
||||||
info.GetValue(propertyInfo.Name, propertyInfo.PropertyType),
|
deserializedObject,
|
||||||
null
|
info.GetValue(propertyInfo.Name, propertyInfo.PropertyType),
|
||||||
);
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return deserializedObject;
|
return deserializedObject;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user