January 08, 2005

Prototype pattern using reflection

The prototype pattern is one of the originals described by the "Gang of Four" in their book "Design Patterns". The idea is that a class contains a virtual method that is overridden in each derived class to create a clone. Given a prototype object, it is then possible to create as many clones as needed by invoking the clone method.

An application of this pattern in video games would be to implement spawning. The level data would contain a prototype object for each spawning entity. Whenever an instance of this kind of entity needed to be spawned, the prototype would be cloned and the clone positioned at the spawn point.

Although this is very powerful, one thing I don't like about the prototype pattern is it involves a lot of typing. I actually have to override the clone method every time I add a new entity class. As I will show, this code is almost completely redundant.

I have modified the prototype pattern to take advantage of the C# reflection API. Now there is no need to override a clone method. The prototypes are cloned simply by using reflection to examine the prototype at runtime, instantiate a new object of the same class and then copy the property values from the prototype into the clone. If a property is of a reference type, I also provide a way of specifying whether to do shallow or deep copy. Shallow copy is the default. Deep copy is requested by applying a DeepCopy attribute to the property.
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)
{
return property.PropertyType.GetCustomAttributes(
typeof(DeepCopyAttribute), true).Length != 0;
}

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;

[DeepCopyAttribute()]
public Weapon Weapon
{
get { return weapon; }
set { weapon = value; }
}
private Weapon weapon = new LaserDisruptor();
}

class Program
{
static void Main(string[] args)
{
SpaceMonkey prototypeObject = new SpaceMonkey();

// Create 10 clones of the prototype.
PrototypeCloner cloner = new PrototypeCloner();
for (int i = 0; i != 10; ++i)
{
Entity clonedObject = (Entity)cloner.Clone(prototypeObject);
}
}
}
Something I love about C# is it's so easy to get things done. This program compiled correctly first time and did exactly what I expected! Actually the first part was easy, Visual C# 2005 parses your code as you type and tells you a lot of the errors before you even build :)

Comments:
I think there maybe a bug in the IsPropertyDeepCopied() function. I hope you don't mind but I extended your example a little, see my blogThe solutions that seems to work for me is:
private bool IsPropertyDeepCopied( PropertyInfo property )
{
if( property.GetCustomAttributes( typeof(DeepCopyAttribute), true).Length
!= 0 )
{
return true;
}
else
{
return false;
}
}
(Sadly the comment system won't let me pretty it up any :( )
 
This comment has been removed by a blog administrator.
 
Thanks for the bug fix, I'll look int it. This isn't intended to be a complete implementation, BTW. It's just a simple example.
 
You have to love reflection, I used to use serialization to deep copy classes but didn't work too well when you wanted to keep referenced parent nodes, so this is good stuff. It might be worth having a check to see if the property is not of value type as well (I know it's not meant as a full example), I'm not exactly sure what would happen when trying to deepcopy a value type.
 
if you have save/load you can also often use their associated methods to clone.

reloading from disk each time; saving to memory, and loading multiple times from there; saving to a output stream that is secretly writing to an input stream....
 
Got here through googling phrase "cloning c# reflection". As I need to clone many data structures and don't have an access to their code I think that you bit of code could be a great utility. Is it public domain?
 
not what i needed, why not start your blog all over and make it interesting this time?
 
Post a Comment

<< Home

This page is powered by Blogger. Isn't yours?