程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> C#堆VS棧(Part Three)

C#堆VS棧(Part Three)

編輯:C#入門知識

C#堆VS棧(Part Three)


   在本系列的第一篇文章《C#堆棧對比(Part Two)》中,介紹了值類型和引用類型在參數傳遞時的不同,本文將討論如何應用ICloneable接口實現去修復引在堆上的用變量所帶來的問題。            本文是系列文章的第三部分。                注:限於本人英文理解能力,以及技術經驗,文中如有錯誤之處,還請各位不吝指出。     拷貝不是復制那麼簡單     為了更清楚的表達這個問題,我們來考察一下堆上的值類型與堆上的引用類型。首先,我們來看看值類型。跟隨如下的類和結構體,我們有一個包含Name和兩個Shoe字段的Dude類。我們有一個CopyDude方法方便我們產生一個新的Dude(花花公子)。     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.");                 } }     我們的Dude類是一個引用類型(原本中此處為變量類型,作者已更正)並且Shoe結構體是類的一個成員,他們都在堆上。     注:這裡體現了值類型是在棧上還是在堆上,完全取決於其生命時的地點。             當我們運行如下的方法時:     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.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.     如果我們將Shoe改為引用類型呢?那將就是個問題,更改如下:   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     紅色的鞋子在另一個人身上,這明顯是錯的,你能看出這是怎麼發生的嗎?下圖就是內存引用示例:         因為現在我們用Shoe的引用類型代替值類型,並且拷貝引用類型內容時僅僅是拷貝了指針(不是指針真正指向的對象),我們必須做一些額外工作使我們的引用類型的Shoe更符合值類型的行為。     注:上面這個例子中當Shoe為值類型時,已經伴隨Dude的構造方法生成了一個完全獨立的結構體Shoe對象,所以Bill為藍色的鞋,Ted為紅色的鞋;當Shoe為引用類型時,Shoe僅僅初始化了一次,所以Ted在使用Shoe時,其實更改的還是唯一初始化一次時的Shoe的內容,所以導致了最後大家都為紅鞋。下文會應用深拷貝解決引用類型復制指針的問題。     幸運的是,我們有一個ICloneable接口來幫我們解決問題。這個接口是一個基本的契約,所有Dudes遵守這個契約並且規定如何按順序的復制避免Shoe Sharing問題。我們所有將被復制的類應該使用ICloneable接口,包括Shoe類。     ICloneable包括一個方法:Clone()     下面我們將實現這個接口: public class Shoe : ICloneable {           public string Color;           #region ICloneable Members           public object Clone()           {                       Shoe newShoe = new Shoe();                       newShoe.Color = Color.Clone() as string;                       return newShoe;            }            #endregion }     在Clone內部,我們僅僅是New了一個新的Shoe對象,復制所有引用類型並且拷貝值類型,然後返回一個新對象。你可能注意到了String類已經實現了ICloneable接口,所以我們能調用Color.Clone方法。因為Clone返回一個對象的引用,我們必須在設置Shoe的顏色之前將類型顯示轉換成Shoe類型。     注:String類型是一種特殊的引用類型,其表現形式類似於值類型,因為字符串不可改變,如果改變則產生一個新對象,請參考這裡。     下一步,在我們的CopyDude方法中我們需要克隆Shoes代替拷貝。     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() {                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.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     這就是我們想要的。       將事物包裹起來     作為一個練習,我們希望總是克隆引用類型和復制值類型。(這將降低當你調試程序錯誤時所購買治療頭疼的阿司匹林的數量)     所以,在頭疼降低的情況下,讓我們走的更遠一些並且讓我們整理下Dude類實現ICloneable接口方法代替CopyDude方法。     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  }     我們所要做的僅僅是通過使用Dude.Clone改變Main方法中的內容。     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.     所以一切都很正常。     有一個很有意思的地方需要注意,System.String的操作符“=”真是的克隆了字符串,所以你不必擔心重復的引用。然而你必須注意內存膨脹。如果你回頭看看圖示,字符串是引用類型,它真的本應該是一個指向堆的指針,但是簡單起見,它的作用類似於值類型。   總結     作為一個練習,如果我們打算每次都拷貝對象,我們應該實現ICloneable接口。這將確保我們的引用類型有點像模仿值類型的行為。正如你所見到的那樣,記錄我們正在處理的變量是重要的,因為引用類型和值類型在創建內存上的區別。     在這下一篇文章中,我們將審視一種降低內存印記的方式。     1.  引用類型的拷貝一定要注意是深拷貝,還是簡單的指針復制的淺拷貝。     2.  System.String類型是特殊的引用類型,實際作用效果類似於值類型。     3.  引用類型應該實現ICloneable接口,實現深拷貝,即對象拷貝而非指針拷貝。    

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved