這個做法建議不要實做 ICloneable
介面,還有 Deep Copy
和 Shallow Copy
的概念。
下面這個介面是 ICloneable
的所有內容,他只需要實做一個 Clone
方法,但這有一個問題就是這個介面沒有明確說明
該實做 Deep Copy
還是 Shallow Copy
,所以每個人就會根據自己類別的需求來實做,這種不規範就會就成問題。
public interface ICloneable
{
object Clone();
}
Shallow Copy
: 當成員為 Value Type
的時候複製新內容,但如果為 Reference Type
只會複製參考。
Deep Copy
: 不管 Value Type
或 Reference Type
都會複製一份新的內容。
由於 Shallow Copy
在複製 Reference Type
的時候只會複製參考,因此複製後的物件會與舊物件共享同一份參考。
Deep Copy
則會遞迴複製所有物件,確保新物件與原物件完全獨立。
參考這段程式碼,輸出的結果為 null,這是因為 Derived
類別透過繼承使用 Clone
方法,但是新複製的型別為 BaseType
所以不可能轉換成 Derived
類型,最後導致 d2
變數為 null
,同時寫基底類別的 Clone
方法的時候不可能知道衍生類別會增加什麼成員,
這也代表你只要在基底類別實做了 ICloneable
介面,就必須在衍生類別也實做它才能做到正確的複製。
void Main()
{
Derived d = new Derived();
Derived d2 = d.Clone() as Derived;
if (d2 == null)
Console.WriteLine("null");
}
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];
}
要解決這個問題可以建立 Copy Constructor
,它是一種能用來複製的建構函式,通常需要傳入一個物件並建造出一個相等的物件。
這樣就能避免在基底類別實做 ICloneable
介面,之後在衍生類別只要呼叫這個 Copy Constructor
就能做到複製的效果。
class BaseType
{
private string label;
private int[] values;
protected BaseType() // 預設建構子
{
label = "class name";
values = new int[10];
}
// Copy Constructor,供衍生類別使用
protected BaseType(BaseType other)
{
label = other.label;
values = (int[])other.values.Clone();
}
}
sealed class Derived : BaseType, ICloneable
{
private double[] dValues;
public Derived()
{
dValues = new double[10];
}
private Derived(Derived other) : base(other)
{
dValues = (double[])other.dValues.Clone();
}
public object Clone()
{
return new Derived(this);
}
}
Summary
實做 ICloneable
介面會影響到繼承,導致衍生類別都需要額外實做 ICloneable
介面保證功能能正常運作,由於不能確定使用者提供的
是 Deep Copy
還是 Shallow Copy
所以建議是完全不要實做 ICloneable
介面比較好,改成用 Copy Constructor
明確進行複製也能達到同樣的效果。