C# 中用于克隆对象的基类






4.55/5 (36投票s)
2002年12月30日
2分钟阅读

552715

2113
这个类为你实现了 ICloneable 接口。
引言
虽然现实世界中克隆是一个有争议的话题,但在 .NET 世界中,它仍然可以安全使用,或者说,真的可以吗?
你多少次发现自己为你的类实现 ICloneable
接口,但每次都写相同的代码,或者为每个类编写特定的代码。另外,当你向类中添加一个新字段时,却忘记更新 Clone
方法以适应这个新字段,该怎么办?相信我,这种事情会导致令人讨厌的错误。
这时,我的类就派上用场了。借助反射机制,我创建了一个抽象类,它实现了 ICloneable
接口,并提供了默认行为。你可能想问:默认行为是什么?我很高兴你问了。克隆的默认行为是,通过以下算法克隆类中的每个字段:
- 对于类中的每个字段,询问它是否支持
ICloneable
接口。 - 如果该字段不支持
ICloneable
接口,则以常规方式设置该字段,这意味着如果该字段是值类型,则将复制该值,但如果该字段是引用类型,则克隆字段将指向相同的对象。 - 如果该字段支持
ICloneable
接口,我们使用它的Clone
方法将其设置为克隆对象中的值。 - 如果该字段支持
IEnumerable
接口,则需要检查它是否支持IList
或IDictionary
接口。如果支持,则遍历集合,并对集合中的每个项目询问它是否支持ICloneable
接口。
如何使用
要使你的类支持 ICloneable
接口,只需将你的类从 BaseObject
派生,如下所示:
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();
}
}
}
现在在 Main
方法中,你可以执行以下操作:
static void Main(string[] args)
{
MyContainer con1 = new MyContainer();
MyContainer con2 = (MyContainer)con1.Clone();
con2.myArray[0].id = 5;
}
检查 con2
实例时,你将看到第一个索引中的 MyClass
实例已更改为 5,但 con1
实例保持不变。因此,你可以看到,你添加到类中的任何支持 ICloneable
接口的字段都将被克隆。此外,如果该字段支持 IList
接口或 IDictionary
接口,该方法将检测到它并循环遍历所有项目并尝试克隆它们。
实现
/// <summary>
/// BaseObject class is an abstract class for you to derive from.
/// Every class that will be dirived from this class will support the
/// Clone method automaticly.
/// The class implements the interface ICloneable and there
/// for every object that will be derived
/// from this object will support the ICloneable interface as well.
/// </summary>
public abstract class BaseObject : ICloneable
{
/// <summary>
/// Clone the object, and returning a reference to a cloned object.
/// </summary>
/// <returns>Reference to the new cloned
/// object.</returns>
public object Clone()
{
//First we create an instance of this specific type.
object newObject = Activator.CreateInstance( this.GetType() );
//We get the array of fields for the new type instance.
FieldInfo[] fields = newObject.GetType().GetFields();
int i = 0;
foreach( FieldInfo fi in this.GetType().GetFields() )
{
//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.
fields[i].SetValue( newObject , IClone.Clone() );
}
else
{
// If the field doesn't support the ICloneable
// interface then just set it.
fields[i].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 = fields[i].FieldType.GetInterface
( "IList" , true );
Type IDicType = fields[i].FieldType.GetInterface
( "IDictionary" , true );
int j = 0;
if( IListType != null )
{
//Getting the IList interface.
IList list = (IList)fields[i].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[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++;
}
}
else if( IDicType != null )
{
//Getting the dictionary interface.
IDictionary dic = (IDictionary)fields[i].
GetValue(newObject);
j = 0;
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[de.Key] = clone.Clone();
}
j++;
}
}
}
i++;
}
return newObject;
}
}