< DOCTYPE html PUBLIC -WCDTD XHTML StrictEN httpwwwworgTRxhtmlDTDxhtml-strictdtd>
大家一定知道.NET對象是有二大類型的: 值類型和引用類型。 值類型對象的變量表示對象本身,而且具有“copy-on-assignment”的行為。也就是說, 以下的讨論不适用于值類型。
另一方面,引用類型的變量實際上是指向堆上的記憶體。 是以,如果你建立了一個引用類型的變量,并且将一個已存在的對象配置設定給它,實際上是建立了指向堆上的相同記憶體的另外一個對象。本文就是讨論這樣的情況:建立的一個對象的新的拷貝,并且儲存在一個變量中!
為什麼要克隆?
我認為當設定一個對象的狀态要付出昂貴的代價,并且又需要取得該對象的一個拷貝以便改變目前的一些狀态時,克隆就顯得十分必要。下面列舉一個剛好能展現我剛剛所說的情況的例子。 就拿 DataTable 類來說吧。建立一個 DataTable 會包含諸如以下的操作:為取得架構和資料而查詢資料庫、添加限制、設定主鍵等等。那麼,當需要該 DataTable 的一個新的拷貝,哪怕是對架構作極小的改變或添加新的一行記錄等等, 明智的選擇會是克隆已存在的對象再對其進行操作,而不是建立一個新的DataTable, 那樣将需要更多的時間和資源。
克隆也廣泛應用于數組和集合,這些時候往往會多次需要已存在對象的一個拷貝。
克隆的類型
我們基于克隆的程度将克隆分成兩大類:“深層”克隆和“淺表”克隆。“淺表”克隆得到一個新的執行個體,一個與原始對象類型相同、包含值類型字段的拷貝。但是,如果字段是引用類型的, 該引用将被拷貝, 而不是拷貝引用的對象。 是以,原始對象的引用和克隆對象的引用都指向同一個對象。另一方面, 對象的“深層”克隆包含原始對象直接或間接引用的對象的所有拷貝。下面舉例說明。
對象X引用對象A,對象A引用對象M。對象X的“淺表”克隆對象Y,同樣也引用了對象A。相對比的是,對象X的“深層”克隆對象Y,卻直接引用了對象B,并且間接引用對象N,這裡,對象B是對象A的拷貝,對象N是對象M的拷貝。
實作克隆
System.Object提供了受保護的方法 MemberwiseClone,可用來實作“淺表”克隆。由于該方法标記為“受保護”級别,是以,我們隻能在繼承類或該類内部才能通路該方法。
.NET定義了一個IClonable接口,一個需要“深層”克隆而不是“淺表”克隆的類必須實作該接口。我們需要提供一個好的實作方法來達到該接口的Clone方法實作的功能。
有許多方法可以實作“深層”克隆。一個方法是将對象串行化到記憶體流中,然後反串行化到一個新的對象。我們将使用一個二進制(Binary)的 Formatter類或SOAP formatter類來進行深層串行化。做一個深寫成連載長篇而刊登的 formatter 。 這個方法的問題是類和它的成員 (完整的類表) 必須被标記為serializable,否則formatter會發生錯誤。
反射是另外一個能達到相同目的的方法。 Amir Harel寫的一篇好文章吸引了我, 他使用該方法提供一個好的克隆實作。 這篇文章讨論得非常好! 以下是連結:
<a href="http://www.codeproject.com/csharp/cloneimpl_class.asp">http://www.codeproject.com/csharp/cloneimpl_class.asp</a>
上面讨論的任何一個方法,都要求對象的成員類型能支援自我克隆,以確定“深層”克隆能成功進行。也就是說, 對象必須是可串行化的(serializable) ,或者每個獨立的成員必須提供IClonable的實作。 如果不這樣,我們根本不可能對對象進行“深層”克隆!
綜述
克隆是提供給程式員的一個很好的方法。但是, 我們應該知道什麼時候需要提供這樣的功能,而且在某些情況下,嚴格地說,對象不應該提供這一個特性。 以SQLTransaction 類為例, 就不支援克隆。這一個類代表了SQL Server資料庫的一個事務。 克隆該對象沒有任何意義,因為我們可能不能夠了解一個資料庫的一個活動的事務的克隆! 是以,如果你認為克隆對象的狀态會産生應用程式邏輯上的沖突,就不需要支援克隆。
示例代碼:
using System;
using System.Reflection;
using System.Collections;
namespace Amir_Harel.Cloning
{
/// <summary>
/// <b>BaseObject</b> class is an abstract class for you to derive from. <br>
/// Every class that will be dirived from this class will support the <b>Clone</b> method automaticly.<br>
/// The class implements the interface <i>ICloneable</i> and there for every object that will be derived <br>
/// from this object will support the <i>ICloneable</i> 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 );
ICloneable clone = (ICloneable)de.Value;
dic[de.Key] = clone.Clone();
i++;
}
return newObject;
}
}
本文轉自 netcorner 部落格園部落格,原文連結:http://www.cnblogs.com/netcorner/archive/2008/06/06/2912136.html ,如需轉載請自行聯系原作者