【转】深入浅出图解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());
}
我們得到了期望的結果:
如果我們把Shoe換成引用類型呢?
?
引用類型測試
?
當我們把Shoe改成引用類型時,問題就產生了。
?public class Shoe{
public string Color;
}
執行同樣上面的Main()方法,結果改變了,如下:
這并不是我們期望的結果。很明顯,出錯了!看下面的圖解:
?
因為現在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());
}
最后得到期望的結果:
?
特殊引用類型String
?
在C#中有趣的是,當?System.String 使用操作符“=”時,實際上是進行了克隆(深復制)。你不必擔心你只是在操作一個指針,它會在內存中創建一個新的對象。但是,你一定要注意內存的占用問題(譯外話:比如為什么在一定情況下我們使用StringBuilder代替String+String+String+String...前者速度稍慢初始化耗多點內存但在大字符串操作上節省內存,后者速度稍快初始化簡單但在大字符串操作上耗內存)。如果我們回頭去看上面的圖解中,你會發現Stirng類型在圖中并不是一個針指向另一個內存對象,而是為了盡可能的簡單,把它當成值類型來演示了。
?
總結
?
在實際工作中,當我們需要復制引用類型變量時,我們最好讓它實現ICloneable接口。這樣可以讓引用類型模仿值類型的使用,從而防止意外的錯誤產生。你可以看到,慎重得理不同的類型非常重要,因為值類型和引用類型在內存中的分配是不同的。
?
?
翻譯:?http://www.c-sharpcorner.com/UploadFile/rmcochran/chsarp_memory401152006094206AM/chsarp_memory4.aspx
總結
以上是生活随笔為你收集整理的【转】深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第五节 引用类型复制问题及用克隆接口ICloneable修复的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: “夏枯草”凉茶名字和价格遭吐槽 网友试喝
- 下一篇: 【转】PE文件结构详解--(完整版)