Object Cloning Using Reflection
Today on Alastair Patrick’s blog he shared a way to use reflection to clone objects which I found really interesting. I took the liberty of expanding the example a little so I could better understand the effects deep and shallow cloning had. I also replaced his main() with a NUnit test.
using System;
using System.Reflection;
using NUnit.Framework;
public class DeepCopyAttribute : Attribute
{
}
public class PrototypeCloner
{
public object Clone( object prototype )
{
object clone = CreateNewInstance( prototype.GetType() );
CopyPropertyValuesFromPrototype( clone, prototype );
return clone;
}
private object CreateNewInstance(Type type)
{
ConstructorInfo defaultConstructor = type.GetConstructor( new Type[0] );
return defaultConstructor.Invoke(new object[0]);
}
private bool IsPropertyDeepCopied( PropertyInfo property )
{
if( property.GetCustomAttributes( typeof(DeepCopyAttribute), true).Length
!= 0 )
{
return true;
}
else
{
return false;
}
}
private void CopyPropertyValuesFromPrototype(object clone, object prototype)
{
foreach (PropertyInfo property in prototype.GetType().GetProperties())
{
if (IsPropertyDeepCopied(property))
{
object prototypeProperty = property.GetValue(prototype, null);
object cloneProperty = Clone(prototypeProperty);
property.SetValue(clone, cloneProperty, null);
}
else
{
property.SetValue(clone, property.GetValue(prototype, null), null);
}
}
}
}
public abstract class Weapon
{
}
public class LaserDisruptor: Weapon
{
public float RateOfFire
{
get
{
return rateOfFire;
}
set
{
rateOfFire = value;
}
}
private float rateOfFire = 1000;
}
public abstract class Entity {}
public class SpaceMonkey: Entity
{
public float Speed
{
get
{
return speed;
}
set
{
speed = value;
}
}
private float speed = 5;
private Weapon deepCopyWeapon = new LaserDisruptor();
[DeepCopyAttribute()]
public Weapon DeepCopyWeapon
{
get
{
return deepCopyWeapon;
}
set
{
deepCopyWeapon = value;
}
}
private Weapon shallowCopyWeapon = new LaserDisruptor();
public Weapon ShallowCopyWeapon
{
get
{
return shallowCopyWeapon;
}
set
{
shallowCopyWeapon = value;
}
}
}
[TestFixture]
public class PrototypeClonerTests
{
[Test]
public void Clone()
{
SpaceMonkey prototypeObject = new SpaceMonkey();
PrototypeCloner cloner = new PrototypeCloner();
Entity clonedObject = null;
clonedObject = (Entity)cloner.Clone(prototypeObject);
SpaceMonkey clonedMonkey = (SpaceMonkey)clonedObject;
// Testing for Equality
Assert.AreEqual( prototypeObject.Speed, clonedMonkey.Speed, "Fail1" );
// -- This is false because it is a Deep Copy property and this class
// doesn't override the Equals or == operator so the Assert's
// AreSame and AreEqual tests have the identical result
Assert.IsFalse(
prototypeObject.DeepCopyWeapon == clonedMonkey.DeepCopyWeapon,
"Fail2" );
Assert.AreEqual(
((LaserDisruptor)prototypeObject.DeepCopyWeapon).RateOfFire,
((LaserDisruptor)clonedMonkey.DeepCopyWeapon).RateOfFire, "Fail3" );
// -- Are equal, as in the same object because it is the Shallow Copy
Assert.AreEqual( prototypeObject.ShallowCopyWeapon,
clonedMonkey.ShallowCopyWeapon,
"Fail4" );
Assert.AreEqual(
((LaserDisruptor)prototypeObject.ShallowCopyWeapon).RateOfFire,
((LaserDisruptor)clonedMonkey.ShallowCopyWeapon).RateOfFire, "Fail5" );
// Testing for Same
// -- Only the DeepCopyAttribute properties should be the same
// -- Not same because it is not a referance object (float)
Assert.IsFalse(
object.ReferenceEquals( prototypeObject.Speed, clonedMonkey.Speed ),
"Fail6" );
// -- Not same because it is a DeepCopyAttribute
Assert.IsFalse(
object.ReferenceEquals( prototypeObject.DeepCopyWeapon,
clonedMonkey.DeepCopyWeapon),
"Fail7" );
// -- Not same because it is not a referance object (float)
Assert.IsFalse( object.ReferenceEquals(
((LaserDisruptor)prototypeObject.DeepCopyWeapon).RateOfFire,
((LaserDisruptor)clonedMonkey.DeepCopyWeapon).RateOfFire), "Fail8" );
// -- The only referance object that isn't a deep copy Are Same
Assert.AreSame( prototypeObject.ShallowCopyWeapon,
clonedMonkey.ShallowCopyWeapon,
"Fail9" );
// -- Not same because it is not a referance object (float)
Assert.IsFalse( object.ReferenceEquals(
((LaserDisruptor)prototypeObject.ShallowCopyWeapon).RateOfFire,
((LaserDisruptor)clonedMonkey.ShallowCopyWeapon).RateOfFire),
"Fail10" );
}
}