ICloneable 看起來不錯:實作介面即支援複製。不想支援複製就別實作。但物件不在「真空」環境運行,考慮對衍生類別影響,最好還是對 ICloneable 支援謹慎。一旦某型別支援 ICloneable,所有衍生類別必須保持一致:所有成員必須支援 ICloneable 或提供機制支援複製。支援深複製的物件,設計時若包含網狀結構物件,複製成問題。ICloneable 覺察此問題,官方定義說明:同時支援深複製與淺複製。淺複製建立新物件,複製當前物件所有成員變數。若成員為引用型別,新物件與源物件包含相同引用。深複製遞歸複製所有成員變數、引用型別也遞歸複製。內建型別(如整數)深淺複製結果相同。哪種為我們型別應支援?取決於型別本身。但單一型別混用深淺複製導致許多不一致問題。一旦涉及 ICloneable,難脫身。大多數時候完全避免用 ICloneable,讓類別更簡單。使用與實現都相對簡單得多。
任何只以內建型別為成員的實值型別無需支援 ICloneable;用簡單指派陳述式複製結構所有值比 Clone() 高效得多。Clone() 必須對回傳型別裝箱,強制轉換為 System.Object 引用。調用者還得強制轉換從箱子取回值。我知道你有能力這樣做,但別用 Clone() 取代指派陳述式。
最常見:實值型別包含字串:
public struct ErrorMessage
{
private int errCode, details;
private string msg;
}
字串特殊,因為是不變類別。指定錯誤訊息字串,所有錯誤訊息類引用同一字串,不導致問題,與其他一般引用型別不同。若在任何引用修改 msg,會為它重新建立 string 物件(原則 7)。(譯註:string 很有意思,很多 C++/C# 程式設計師不理解,導致低效甚至錯誤。應好好理解 C# string、String、StringBuilder 關係,對學好 C# 很有幫助。此設計思想可沿用到自有型別。)
一般情況,結構包含任意引用型別,複製情況複雜。內建指派陳述式對結構淺複製,兩結構引用變數引用同一物件。若要深複製,必須對引用型別也複製,且知曉該引用型別是否支援 Clone() 深複製。無論哪種,都不用對實值型別添加 ICloneable 支援,指派陳述式會對實值型別建立新副本。
一句概括實值型別:沒有任何理由給實值型別添加 ICloneable 支援!
引用型別應支援 ICloneable 明確給出深/淺複製。明智選擇是添加支援,因這明確要求所有衍生類別也必須支援。看簡單繼承關係:
class BaseType : ICloneable
{
private string _label = "class name";
private int[] _values = new int[10];
public object Clone()
{
BaseType rVal = new BaseType();
rVal._label = _label;
for(int i=0; i<_values.Length; i++) rVal._values[i] = _values[i];
return rVal;
}
}
class Derived : BaseType
{
private double[] _dValues = new double[10];
static void Main(string[] args)
{
Derived d = new Derived();
Derived d2 = d.Clone() as Derived;
if (d2 == null) Console.WriteLine("null");
}
}