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:
Markus Ewald 2012-02-08 19:26:05 +00:00
parent 48e1674912
commit 0290444140
7 changed files with 200 additions and 108 deletions

View File

@ -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

View File

@ -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
); );
}
} }
} }
} }

View File

@ -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() {

View File

@ -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() {

View File

@ -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);
}
} }
} }
} }

View File

@ -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() {

View File

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