Introduction
Although the subject of cloning in the real world is controversial, in the .NET world
it is still safe to use, or isn�t it?
How many times did you find yourself implementing the ICloneable
interface for your class, but every time you do the same code, or you do a specific
code for each class. And what about, when you add a new field to the class, and you
forgot to update the Clone
method for the new field. Believe me, this
sort of thing leads to annoying bugs.
This is where my class comes to the rescue. With a little help from the
reflection mechanism, I created an abstract class that implements the
ICloneable
interface with the default behavior. Now you are
probably asking yourself: What is the default behavior? Well I�m glad you asked. Default behavior
for cloning, is to clone every field in the class by the following algorithm:
- For each field in the class, ask if it supports
the
ICloneable
interface.
- If the field doesn�t support the
ICloneable
interface,
then the field is set in the regular manner, which means that if this field is a value type,
then the value will be copied, but if the field is a reference type, the clone field will
be pointing to the same object.
- If the field supports the
ICloneable
interface,
we use its Clone
method to set it in the clone object.
- If the field supports the
IEnumerable
interface, then we need to
check if it supports the IList
or the IDictionary
interface. If it does, then we iterate the collection, and for each item in the
collection we ask if it supports the ICloneable
interface.
How to use
All you have to do to make your class support the ICloneable
interface,
is to derive your class from the BaseObject
as follow:
public class MyClass : BaseObject
{
public string myStr =�test�;
public int id;
}
public class MyContainer : BaseObject
{
public string name = �test2�;
public MyClass[] myArray= new MyClass[5];
public class MyContainer()
{
for(int i=0 ; i<5 ; i++)
{
this.myArray[I] = new MyClass();
}
}
}
Now in the Main
method you can do the following:
static void Main(string[] args)
{
MyContainer con1 = new MyContainer();
MyContainer con2 = (MyContainer)con1.Clone();
con2.myArray[0].id = 5;
}
When inspecting the con2
instance you will see that the MyClass
instance in
the first index was changed to 5, but the con1
instance remained without changes. So you can
see that any field you will add to your class, which support the ICloneable
interface will be cloned as well. Furthermore, if the field supports the IList
interface or the IDictionary
interface, the method will detect it
and will loop through all the items and will try to clone them as well.
Implementation
public abstract class BaseObject : ICloneable
{
public object Clone()
{
object newObject = Activator.CreateInstance( this.GetType() );
FieldInfo[] fields = newObject.GetType().GetFields();
int i = 0;
foreach( FieldInfo fi in this.GetType().GetFields() )
{
Type ICloneType = fi.FieldType.
GetInterface( "ICloneable" , true );
if( ICloneType != null )
{
ICloneable IClone = (ICloneable)fi.GetValue(this);
fields[i].SetValue( newObject , IClone.Clone() );
}
else
{
fields[i].SetValue( newObject , fi.GetValue(this) );
}
Type IEnumerableType = fi.FieldType.GetInterface
( "IEnumerable" , true );
if( IEnumerableType != null )
{
IEnumerable IEnum = (IEnumerable)fi.GetValue(this);
Type IListType = fields[i].FieldType.GetInterface
( "IList" , true );
Type IDicType = fields[i].FieldType.GetInterface
( "IDictionary" , true );
int j = 0;
if( IListType != null )
{
IList list = (IList)fields[i].GetValue(newObject);
foreach( object obj in IEnum )
{
ICloneType = obj.GetType().
GetInterface( "ICloneable" , true );
if( ICloneType != null )
{
ICloneable clone = (ICloneable)obj;
list[j] = clone.Clone();
}
j++;
}
}
else if( IDicType != null )
{
IDictionary dic = (IDictionary)fields[i].
GetValue(newObject);
j = 0;
foreach( DictionaryEntry de in IEnum )
{
ICloneType = de.Value.GetType().
GetInterface( "ICloneable" , true );
if( ICloneType != null )
{
ICloneable clone = (ICloneable)de.Value;
dic[de.Key] = clone.Clone();
}
j++;
}
}
}
i++;
}
return newObject;
}
}