在" .NET的堆和棧01,基本概念、值類型內存分配"中,了解了"堆"和"棧"的基本概念,以及值類型的內存分配。我們知道:當執行一個方法的時候,值類型實例會在"棧"上分配內存,而引用類型實例會在"堆"上分配內存,當方法執行完畢,"棧"上的實例由操作系統自動釋放,"堆"上的實例由.NET Framework的GC進行回收。
在" .NET的堆和棧02,值類型和引用類型參數傳遞以及內存分配"中,我們了解了值類型參數和引用類型參數在傳遞時的內存分配情況。
而本篇的重點要放在:引用類型對象拷貝以及內存分配。
主要包括:
■ 引用類型對象拷貝 成員都是值類型
■ 引用類型對象拷貝 包含引用類型成員
引用類型對象拷貝 成員都是值類型
public struct Shoe{ public string Color; } public class Dude { public string Name; public Shoe RightShoe; public Shoe LeftShoe; public Dude CopyDude() { Dude newPerson = new Dude(); newPerson.Name = Name; newPerson.LeftShoe = LeftShoe; newPerson.RightShoe = RightShoe; return newPerson; } public override string ToString() { return (Name + " : Dude!, I have a " + RightShoe.Color + " shoe on my right foot, and a " + LeftShoe.Color + " on my left foot."); } } public static void Main() { Dude Bill = new Dude(); Bill.Name = "Bill"; Bill.LeftShoe = new Shoe(); Bill.RightShoe = new Shoe(); Bill.LeftShoe.Color = Bill.RightShoe.Color = "Blue"; Dude Ted = Bill.CopyDude(); Ted.Name = "Ted"; Ted.LeftShoe.Color = Ted.RightShoe.Color = "Red"; Console.WriteLine(Bill.ToString()); Console.WriteLine(Ted.ToString()); }
輸出結果:
Bill : Dude!, I have a Red shoe on my right foot, and a Red on my left foot
Ted : Dude!, I have a Red shoe on my right foot, and a Red on my left foot
以上,當引用類型的屬性、成員都是值類型的時候,拷貝是完全拷貝。
引用類型對象拷貝 包含引用類型成員
把Shoe由struct值類型改成引用類型class。
public class Shoe{ public string Color; }
再次運行,輸出結果:
Bill : Dude!, I have a Red shoe on my right foot, and a Red on my left foot
Ted : Dude!, I have a Red shoe on my right foot, and a Red on my left foot
當Dude類包含引用類型屬性Shoe的時候,在托管堆上的情況是這樣的:
拷貝后,2個Dude的Shoe類型的屬性指向了同一個托管堆內的Shoe實例,改變Shoe的值會同時影響到2個Dude。
很顯然,這不是我們期望的完全拷貝,如何做到完全拷貝呢?
--實現ICloneable接口
ICloneable接口的Clone()方法,允許我們在拷貝的時候,進行一些自定義設置。
讓引用類Shoe實現ICloneable接口。
public class Shoe : ICloneable { public string Color; public object Clone() { Shoe newShoe = new Shoe(); newShoe.Color = Color.Clone() as string; return newShoe; } }
以上,Shoe的string類型屬性Color之所以可以使用Color.Clone()方法,是因為string也實現了ICloneable接口;又由于Clone()返回類型是object,所以,在使用Color.Clone()方法之后,需要把object轉換成string類型。
現在,在Dude類的CopyDude()方法中,當拷貝Shoe類型屬性的時候,就可以使用Shoe獨有的拷貝方法Clone()。
public Dude CopyDude() { Dude newPerson = new Dude(); newPerson.Name = Name; newPerson.LeftShoe = LeftShoe.Clone() as Shoe; newPerson.RightShoe = RightShoe.Clone() as Shoe; return newPerson; }
客戶端程序。
public static void Main() { Dude Bill = new Dude(); Bill.Name = "Bill"; Bill.LeftShoe = new Shoe(); Bill.RightShoe = new Shoe(); Bill.LeftShoe.Color = Bill.RightShoe.Color = "Blue"; Dude Ted = Bill.CopyDude(); Ted.Name = "Ted"; Ted.LeftShoe.Color = Ted.RightShoe.Color = "Red"; Console.WriteLine(Bill.ToString()); Console.WriteLine(Ted.ToString()); }
輸出結果:
Bill : Dude!, I have a Blue shoe on my right foot, and a Blue on my left foot
Ted : Dude!, I have a Red shoe on my right foot, and a Red on my left foot
這正是我們期望的完全拷貝!
完全拷貝,托管堆上的情況是這樣的:
當然也可以讓同時包含值類型和引用類型成員,同時需要拷貝的類實現ICloneable接口。
public class Dude: ICloneable { public string Name; public Shoe RightShoe; public Shoe LeftShoe; public override string ToString() { return (Name + " : Dude!, I have a " + RightShoe.Color + " shoe on my right foot, and a " + LeftShoe.Color + " on my left foot."); } #region ICloneable Members public object Clone() { Dude newPerson = new Dude(); newPerson.Name = Name.Clone() as string; newPerson.LeftShoe = LeftShoe.Clone() as Shoe; newPerson.RightShoe = RightShoe.Clone() as Shoe; return newPerson; } #endregion }
客戶端調用。
public static void Main() { Class1 pgm = new Class1(); Dude Bill = new Dude(); Bill.Name = "Bill"; Bill.LeftShoe = new Shoe(); Bill.RightShoe = new Shoe(); Bill.LeftShoe.Color = Bill.RightShoe.Color = "Blue"; Dude Ted = Bill.Clone() as Dude; Ted.Name = "Ted"; Ted.LeftShoe.Color = Ted.RightShoe.Color = "Red"; Console.WriteLine(Bill.ToString()); Console.WriteLine(Ted.ToString()); }
輸出結果:
Bill : Dude!, I have a Blue shoe on my right foot, and a Blue on my left foot.
Ted : Dude!, I have a Red shoe on my right foot, and a Red on my left foot.
也是我們期望的完全拷貝!
參考資料:
C# Heap(ing) Vs Stack(ing) in .NET: Part III
".NET的堆和棧"系列包括:
新聞熱點
疑難解答