程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> 深入淺出圖解C#堆與棧 C# Heap(ing) VS Stack(ing) 第五節 引用類型復制問題及用克隆接口ICl

深入淺出圖解C#堆與棧 C# Heap(ing) VS Stack(ing) 第五節 引用類型復制問題及用克隆接口ICl

編輯:C#入門知識

堆中引用類型復制問題及用克隆接口ICloneable修復


導航


深入淺出圖解C#堆與棧 C# Heap(ing) VS Stack(ing) 第一節 理解堆與棧

深入淺出圖解C#堆與棧 C# Heap(ing) VS Stack(ing) 第二節 棧基本工作原理

深入淺出圖解C#堆與棧 C# Heap(ing) VS Stack(ing) 第三節 棧與堆,值類型與引用類型

深入淺出圖解C#堆與棧 C# Heap(ing) VS Stack(ing) 第四節 參數傳遞對堆棧的影響 1

深入淺出圖解C#堆與棧 C# Heap(ing) VS Stack(ing) 第四節 參數傳遞對堆棧的影響 2

深入淺出圖解C#堆與棧 C# Heap(ing) VS Stack(ing) 第五節 引用類型復制問題及用克隆接口ICloneable修復


前言


雖然在.Net Framework 中我們不必考慮內在管理和垃圾回收(GC),但是為了優化應用程序性能我們始終需要了解內存管理和垃圾回收(GC)。另外,了解內存管理可以幫助我們理解在每一個程序中定義的每一個變量是怎樣工作的。

簡介

這一節我們將介紹引用類型變量在堆中存儲時會產生的問題,同時介紹怎麼樣使用克隆接口ICloneable去修復這種問題。

復制不僅僅是復制


為了更清晰的闡述這個問題,讓我們測試一下在堆中存儲值類型變量和引用類型變量時會產生的不同情況。

值類型測試


首先,我們看一下值類型。下面是一個類和一個結構類型(值類型),Dude類包含一個Name元素和兩個Shoe元素。我們有一個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換成引用類型呢?

引用類型測試


當我們把Shoe改成引用類型時,問題就產生了。
public class Shoe{
               public string Color;
           }

執行同樣上面的Main()方法,結果改變了,如下:
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像值類型一樣工作。 很幸運,我們有一個接口可以幫我們實現:ICloneable。當Dude類實現它時,我們會聲明一個Clone()方法用來產生新的Dude復制類。(譯外話:復制類及其成員跟原始類不產生任何重疊,即我們所說的深復制) 看下面代碼:
ICloneable consists of one method: Clone()

                  public object Clone()
                  {
 
                  }

Here's how we'll implement it in the Shoe class:

           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()方法裡,我們創建了一個新的Shoe,克隆所有引用類型變量,復制所有值類型變量,最後返回新的對象Shoe。有些既有類已經實現了ICloneable,我們直接使用即可,如String。因此,我們直接使用Color.Clone()。因為Clone()返回object對象,我們需要進行一下類型轉換。 下一步,我們在CopyDude()方法裡,用克隆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;
                }

再次執行主方法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.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類實現IConeable代替使用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
             }

在主方法Main()使用Dude.Clone():
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.

特殊引用類型String


在C#中有趣的是,當System.String 使用操作符“=”時,實際上是進行了克隆(深復制)。你不必擔心你只是在操作一個指針,它會在內存中創建一個新的對象。但是,你一定要注意內存的占用問題(譯外話:比如為什麼我們使用StringBuilder代替String+String+String+String...)。如果我們回頭去看上面的圖解中,你會發現Stirng類型在圖中並不是一個針指向另一個內存對象,而是為了盡可能的簡單,把它當成值類型來演示了。

總結


在實際工作中,當我們需要復制引用類型變量時,我們最好讓它實現ICloneable接口。這樣可以讓引用類型模仿值類型的使用,從而防止意外的錯誤產生。你可以看到,慎重得理不同的類型非常重要,因為值類型和引用類型在內存中的分配是不同的。

翻譯:http://www.c-sharpcorner.com/UploadFile/rmcochran/chsarp_memory401152006094206AM/chsarp_memory4.aspx

  1. 上一頁:
  2. 下一頁: