Back to Blog

Object Cloning Using Reflection

by MrPhil
blog

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