|
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;
}
}
| You must Sign In to use this message board. |
|
| | Msgs 1 to 25 of 77 (Total in Forum: 77) (Refresh) | FirstPrevNext |
|
|
 |
|
|
it looks like this code traverses through my arrays, ~1 million indicies, has anyone else noticed this or found a solution
re: So I just took off this whole part if( IEnumerableType != null ) { up to i++, and it seems to work fine, that was looping through any array
modified on Wednesday, July 16, 2008 4:50 PM
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi there! I've got a problem with this code i cannot understand. In the very beginning there is following code
FieldInfo[] fields = newObject.GetType().GetFields();
And the problem is that it is returning empty set. and none of the fields are being copied.
What might be the reason?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
 |
|
|
I had to quickly clone my big data class, so I tried it but it didn't worked for me at all. So I quickly wrote the following code (VB)
Inside Clone function:
Dim newObject As Object = Activator.CreateInstance(Me.[GetType]())
Dim fields As PropertyInfo() = newObject.[GetType]().GetProperties For Each p As PropertyInfo In fields If p.CanWrite And p.CanRead Then Dim thisProperty As PropertyInfo = Me.GetType().GetProperty(p.Name) p.SetValue(newObject, thisProperty.GetValue(Me, Nothing), Nothing) End If Next
Return newObject
Maybe, it work for someone else too (worked for me atleast).
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Your implementation does only perform a shallow copy of the object, which is already provided in the Object class (method MemberWiseClone).
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
First of all thankyou for your implementation of cloning objects, I tryied something similar but was not as complete as yours.
=== BINDING FLAGS === Second, when I was reading and developing my Clonse class one of the first things i've learned is the use of BindingFalgs, which allow you to accurate the search of fields.
With your aproach, not all the fields are retrieved when .GetType().GetFields() is called, becaus you aren't passing any BindingFlags parameter. This affects a lot to the clonation because, for example, private members are not cloned.
Here my contribution:
Replace this code:
//We get the array of fields for the new type instance. FieldInfo[] fields = newObject.GetType().GetFields(bf); int i = 0; foreach (FieldInfo fi in ObjectToClone.GetType().GetFields(bf)) { ... }
For this one:
BindingFlags bf = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static;
//We get the array of fields for the new type instance. FieldInfo[] fields = newObject.GetType().GetFields(bf); int i = 0; foreach (FieldInfo fi in ObjectToClone.GetType().GetFields(bf)) { ... }
You can find more info in this article: Reflector.
=== GENERICS ===
Apply generics to this aproach is very simple, but must change a little bit the philosophy of the class.
implementing ICloneable.Clone forces you to return object, while we want to use Generics. This implies that our Clone function must return the correct type.
So we can do something like:
public T Clone(T ObjectToClone){ ... // The code you've posted in your article, changing 'this' for 'ObjectToClone'. }
But this implies not being implementing ICloneable.
The result of all of this is that if you want to use generics for clonation you can't implement ICloneable interface.
The solution i've found is to develp an static class T with an static function T Clone(T ObjectToClone), and use it as a tool when we need to clone. The code resutls very smart:
CLONATOR:
public static Clonator < T > { public static T Clone(ref T ObjectToClone) { // The code you've posted in your article, changing // 'this' for 'ObjectToClone' and // object newObject = Activator.CreateInstance(ObjectToClone.GetType()); // for // T newObject = (T) Activator.CreateInstance(ObjectToClone.GetType()); } }
How yo use it:
MyClass a = new MyClass(..); // modify this class as wanted..
MyClass b = Clonator< MyClass >.Clone(ref a);
Of course you are free to test it and tell me your xperience!
-- modified at 5:43 Thursday 15th November, 2007
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
I changed the Sourcecode to support Generic Lists. Also I Kicked out the i++ iterations, as we didn't need them realy.
public object Clone() { //First we create an instance of this specific type. object newObject = Activator.CreateInstance(this.GetType());
foreach (FieldInfo fi in this.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) { //We query if the fiels support the ICloneable interface. Type ICloneType = fi.FieldType. GetInterface("ICloneable", true);
if (ICloneType != null) { //Getting the ICloneable interface from the object. ICloneable IClone = (ICloneable)fi.GetValue(this);
//We use the clone method to set the new value to the field. fi.SetValue(newObject, IClone.Clone()); } else { if(fi.FieldType.GetInterface("IEnumerable",true) != null) { //Create an Instance of the Collection Type (instead of using a reference) fi.SetValue(newObject, Activator.CreateInstance(fi.FieldType)); } else { // If the field doesn't support the ICloneable // interface then just set it. fi.SetValue(newObject, fi.GetValue(this)); } }
//Now we check if the object support the //IEnumerable interface, so if it does //we need to enumerate all its items and check if //they support the ICloneable interface. Type IEnumerableType = fi.FieldType.GetInterface ("IEnumerable", true); if (IEnumerableType != null) { //Get the IEnumerable interface from the field. IEnumerable IEnum = (IEnumerable)fi.GetValue(this);
//This version support the IList and the //IDictionary interfaces to iterate on collections. Type IListType = fi.FieldType.GetInterface ("IList", true); Type IDicType = fi.FieldType.GetInterface ("IDictionary", true);
if (IListType != null) { //Getting the IList interface. IList list = (IList)fi.GetValue(newObject);
foreach (object obj in IEnum) { //Checking to see if the current item //support the ICloneable interface. ICloneType = obj.GetType(). GetInterface("ICloneable", true);
if (ICloneType != null) { //If it does support the ICloneable interface, //we use it to set the clone of //the object in the list. ICloneable clone = (ICloneable)obj; list.Add(clone.Clone()); // list[j] = clone.Clone(); }
//NOTE: If the item in the list is not //support the ICloneable interface then in the //cloned list this item will be the same //item as in the original list //(as long as this type is a reference type).
} } else if (IDicType != null) { //Getting the dictionary interface. IDictionary dic = (IDictionary)fi. GetValue(newObject);
foreach (DictionaryEntry de in IEnum) { //Checking to see if the item //support the ICloneable interface. ICloneType = de.Value.GetType(). GetInterface("ICloneable", true);
if (ICloneType != null) { ICloneable clone = (ICloneable)de.Value; dic.Add(de.Key, clone.Clone()); //dic[de.Key] = clone.Clone(); } } } } } return newObject; }
It just works fine for me.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hello
If I have a class like the following, how can I use your approach to assign the event handlers to the new cloned object?
public class MyTextBox : TextBox, ICloneable { public object Clone() { MyTextBox clone = new MyTextBox(); clone.Click += this.Click; //compiler error! The event '....Click' can only appear on the left hand side of += or -= } }
Mohammed Hamid A.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hello,
I had to reuse this implementation on my own classes with different specifications: - we only wanted to copy primitive types plus some "basic" .NET types (string, enum, DateTime...) - we needed to copy all the inherited members - we only wanted to copy non public fields
I also noticed that GetFields does not necessary return the fields in the same order. I noticed it systematically on the first and second calls but for safety reasons I preferred to lookup the corresponding field every time.
I though I'd post my modified version... Sorry for the comments in french but that is a prerequisite of the project I am working on!
public virtual object Clone() { // on cree une nouvelle instance Objet clone = (Objet) Activator.CreateInstance(this.GetType());
// le binding type qu'on utilise BindingFlags bf = BindingFlags.Instance | BindingFlags.NonPublic;
// on va remonter la hiérarchie des types Type oldType = this.GetType(); Type newType = clone.GetType(); while (oldType != null && newType != null) { // les champs pour des deux instances // on ne recupere pas necessairement les champs dans le meme ordre! FieldInfo[] oldFields = oldType.GetFields(bf); FieldInfo[] newFields = newType.GetFields(bf);
// on copie les types cloneable foreach (FieldInfo oldFI in oldFields) { // recuperer le champ correspondant FieldInfo newFI = null; for (int i=0; i { if (newFields[i].Name == oldFI.Name) { newFI = newFields[i]; break; } } if (newFI == null) { continue; }
// on recupere la valeur object value = oldFI.GetValue(this); if (value != null) { if ( oldFI.FieldType.IsPrimitive || oldFI.FieldType.IsEnum || oldFI.FieldType == typeof(String) || oldFI.FieldType == typeof(Decimal) || oldFI.FieldType == typeof(DateTime) || oldFI.FieldType == typeof(TimeSpan)) { // on met a jour la valeur directement newFI.SetValue(clone, value); } } }
// iteration oldType = oldType.BaseType; newType = newType.BaseType; }
return clone; }
Voilà!
-- modified at 11:38 Tuesday 25th July, 2006
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Theres a lot of problems in this implementation but the idea has bee kicking around many places..
Before cloning anything in C# read this... It's an excelent article. http://www.agiledeveloper.com/articles/cloning072002.htm
basicly it shows an excelent way to clone.
class myclass() : ICloneable { public readonly int v; protected myclass(myclass another) { v = another.v; }
public object Clone() { return new myclass(this); } }
This is elegant and customisable. If you can't see why then read the article...
Re the serilization "better way"; serialisation may not always be appropriate: -it does not handle all feild types. -not all feilds may be serizable -cpu cost is very high compared to a copy constructor. -public events and delegates tend to get messy. -behavious of serialisation changed from .NET 1.1 to 2.0....
{
|
| Sign In·View Thread·PermaLink | 3.67/5 (2 votes) |
|
|
|
 |
|
|
Sleek - but you have to modify the copy constructor if you add a new field to a class - with the solution in this article you don't
|
| Sign In·View Thread·PermaLink | 3.80/5 (4 votes) |
|
|
|
 |
|
|
Please consider the following code segment in your article: foreach( object obj in IEnum ) { //Checking to see if the current item //support the ICloneable interface. ICloneType = obj.GetType(). GetInterface( "ICloneable" , true ); if( ICloneType != null ) { //If it does support the ICloneable interface, //we use it to set the clone of //the object in the list. ICloneable clone = (ICloneable)obj;
list[j] = clone.Clone(); }
//NOTE: If the item in the list is not //support the ICloneable interface then in the //cloned list this item will be the same //item as in the original list //(as long as this type is a reference type).
j++; }
The foreach loop crashes immediately due to this expression: list[j] = clone.Clone(); because you're trying to modify the list contents but an enumerator remains valid as long as the collection remains unchanged. If changes are made to the collection, such as adding, modifying, or deleting elements, the enumerator is irrecoverably invalidated and its behavior is undefined. Any solutions?
Arash Sabet Computer Engineer E-mail: arash.afifi@gmail.com
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
Again: If we equip the folliwing if statetment inside the foreach loop you will figure out that list[j] is pointing to the same 'obj' object. That's why an exception is thrown during iteration iEnum object:
if( FieldInfo.ReferenceEquals(list[j],obj) ) { .... }
The above if statemtent returns true, so the body of foreach loop has to be optimized and modified.
Arash Sabet Computer Engineer E-mail: arash.afifi@gmail.com
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
Still I believe that this cloning routine is not mature enough to use it in serious applications because:
1. It doesn't clone referenced values. Just sets references! this is not the meaning of deep copying. 2. It doesn't support objects inherited from super classes. The fields of super classes (base classes) can't be iterated in the way that this article describes.
Again this is not a mature article. Just throwing some idea.
Arash Sabet Computer Engineer E-mail: arash.afifi@gmail.com
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
A problem occurred to me when cloning classes with string fields that were empty. An exception was thrown telling me "object reference..." The reason was that this line evaluated to null: ICloneable IClone = (ICloneable)fi.GetValue(this) as ICloneable; And the exception was thrown on the next line that "SetValue"
I just added a check for null and in that case copied the value as a none cloneable type.
/Marcus
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi,
I took inspiration from your class, and produced mine : http://csharpfr.com/code.aspx?ID=34850
I operate a deep copy. Each Chile member of reference type is cloned, even if it dos not support ICloneable (recursive calls are done).
By Renfield Have fun ! Visual Basic MVP
-- modified at 8:08 Wednesday 30th November, 2005
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I'm working with you code but found a problem with the vObj is null. I added a check for it being null as the first test in the first if statement and it works fine.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I liked your class due to the recursion. One thing I found is that the GetType method on a PropertyInfo object via reflection doesn't give you the actual property's info, it gives you the reflection type, so you have to use the PropertyType property rather than the GetType method when using reflection to see if a property implements an interface, etc. In addition I had to add code check for null references as mentioned by another reply in this thread.
Also, I needed to be able to deep clone classes with properties that were based on generic collections (Lists and Dictionaries) so I had to do a little tweaking for that and implement the ICloneable interface into inherited classes for those two.
Finally, since the State of Alaska where I work requires all code to be in VB.NET I had to convert it over from C# to VB. You can see the final result at http://programmingcorner.blogspot.com/2007_01_01_programmingcorner_archive.html
The above was using the .NET 2.0 framework so I don't know if there may be some minor differences for using this approach in .NET 1.0.
|
| Sign In·View Thread·PermaLink | 4.40/5 (2 votes) |
|
|
|
 |
|
|
First of all nice article. I like this sort of article. Because of concentrating a specific area. Try to submit more like this for a specific topic.
Good work.
Sreejith Nair [ My Articles ]
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I've been having problems getting the above clone method to work.
It wasn't until I added the following parameters to the GetFields() method calls that I was able to retrieve any fields at all and thus allow the "foreach (FieldInfo fi..." loop to execute.
I changed the GetFields calls in the first few lines to: "GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public)" and I stopped receiving an empty collection and finally received a clone of the object I wished.
No other posts complain about this error so I'm wondering if it's because I'm using C# .NET 2005 which makes use of the newer .NET 2.0 framework.
Any comments on this are welcome; I'm new to .NET and I'm finding the Beta documentation quite unhelpful. I also think MSDN has become worse recently. I remember being able to browse directly to the subject I wanted but now I find it tougher by far.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
| | |