【转】深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第五节 引用类型复制问题及用克隆接口ICloneable修复
前言
?
雖然在.Net Framework 中我們不必考慮內(nèi)在管理和垃圾回收(GC),但是為了優(yōu)化應(yīng)用程序性能我們始終需要了解內(nèi)存管理和垃圾回收(GC)。另外,了解內(nèi)存管理可以幫助我們理解在每一個(gè)程序中定義的每一個(gè)變量是怎樣工作的。
?
簡(jiǎn)介
這一節(jié)我們將介紹引用類型變量在堆中存儲(chǔ)時(shí)會(huì)產(chǎn)生的問(wèn)題,同時(shí)介紹怎么樣使用克隆接口ICloneable去修復(fù)這種問(wèn)題。
?
?
復(fù)制不僅僅是復(fù)制
?
為了更清晰的闡述這個(gè)問(wèn)題,讓我們測(cè)試一下在堆中存儲(chǔ)值類型變量和引用類型變量時(shí)會(huì)產(chǎn)生的不同情況。
?
值類型測(cè)試
?
首先,我們看一下值類型。下面是一個(gè)類和一個(gè)結(jié)構(gòu)類型(值類型),Dude類包含一個(gè)Name元素和兩個(gè)Shoe元素。我們有一個(gè)CopyDude()方法用來(lái)復(fù)制生成新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類是一個(gè)復(fù)雜類型,因?yàn)橹?類型結(jié)構(gòu)Shoe是它的成員, 它們都將存儲(chǔ)在堆中。
?
?
當(dāng)我們執(zhí)行下面的方法時(shí):
?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());
}
我們得到了期望的結(jié)果:
如果我們把Shoe換成引用類型呢?
?
引用類型測(cè)試
?
當(dāng)我們把Shoe改成引用類型時(shí),問(wèn)題就產(chǎn)生了。
?public class Shoe{
public string Color;
}
執(zhí)行同樣上面的Main()方法,結(jié)果改變了,如下:
這并不是我們期望的結(jié)果。很明顯,出錯(cuò)了!看下面的圖解:
?
因?yàn)楝F(xiàn)在Shoe是引用類型而不是值類型,當(dāng)我們進(jìn)行復(fù)制時(shí)僅是復(fù)制了指針,我們并沒(méi)有復(fù)制指針真正對(duì)應(yīng)的對(duì)象。這就需要我們做一些額外的工作使引用類型Shoe像值類型一樣工作。
很幸運(yùn),我們有一個(gè)接口可以幫我們實(shí)現(xiàn):ICloneable。當(dāng)Dude類實(shí)現(xiàn)它時(shí),我們會(huì)聲明一個(gè)Clone()方法用來(lái)產(chǎn)生新的Dude復(fù)制類。(譯外話:復(fù)制類及其成員跟原始類不產(chǎn)生任何重疊,即我們所說(shuō)的深復(fù)制) ? 看下面代碼:
?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()方法里,我們創(chuàng)建了一個(gè)新的Shoe,克隆所有引用類型變量,復(fù)制所有值類型變量,最后返回新的對(duì)象Shoe。有些既有類已經(jīng)實(shí)現(xiàn)了ICloneable,我們直接使用即可,如String。因此,我們直接使用Color.Clone()。因?yàn)镃lone()返回object對(duì)象,我們需要進(jìn)行一下類型轉(zhuǎn)換。
下一步,我們?cè)贑opyDude()方法里,用克隆Clone()代替復(fù)制:
?public Dude CopyDude()
{
Dude newPerson = new Dude();
newPerson.Name = Name;
newPerson.LeftShoe = LeftShoe.Clone() as Shoe;
newPerson.RightShoe = RightShoe.Clone() as Shoe;
return newPerson;
}
再次執(zhí)行主方法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());
}
我們得到了期望的結(jié)果:
?
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
下面是圖解:
?
整理我們的代碼
?
在實(shí)踐中,我們是希望克隆引用類型并復(fù)制值類型的。這會(huì)讓你回避很多不易察覺(jué)的錯(cuò)誤,就像上面演示的一樣。這種錯(cuò)誤有時(shí)不易被調(diào)試出來(lái),會(huì)讓你很頭疼。
?
因此,為了減輕頭疼,讓我們更進(jìn)一步清理上面的代碼。我們讓Dude類實(shí)現(xiàn)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());
}
最后得到期望的結(jié)果:
?
特殊引用類型String
?
在C#中有趣的是,當(dāng)?System.String 使用操作符“=”時(shí),實(shí)際上是進(jìn)行了克隆(深復(fù)制)。你不必?fù)?dān)心你只是在操作一個(gè)指針,它會(huì)在內(nèi)存中創(chuàng)建一個(gè)新的對(duì)象。但是,你一定要注意內(nèi)存的占用問(wèn)題(譯外話:比如為什么在一定情況下我們使用StringBuilder代替String+String+String+String...前者速度稍慢初始化耗多點(diǎn)內(nèi)存但在大字符串操作上節(jié)省內(nèi)存,后者速度稍快初始化簡(jiǎn)單但在大字符串操作上耗內(nèi)存)。如果我們回頭去看上面的圖解中,你會(huì)發(fā)現(xiàn)Stirng類型在圖中并不是一個(gè)針指向另一個(gè)內(nèi)存對(duì)象,而是為了盡可能的簡(jiǎn)單,把它當(dāng)成值類型來(lái)演示了。
?
總結(jié)
?
在實(shí)際工作中,當(dāng)我們需要復(fù)制引用類型變量時(shí),我們最好讓它實(shí)現(xiàn)ICloneable接口。這樣可以讓引用類型模仿值類型的使用,從而防止意外的錯(cuò)誤產(chǎn)生。你可以看到,慎重得理不同的類型非常重要,因?yàn)橹殿愋秃鸵妙愋驮趦?nèi)存中的分配是不同的。
?
?
翻譯:?http://www.c-sharpcorner.com/UploadFile/rmcochran/chsarp_memory401152006094206AM/chsarp_memory4.aspx
總結(jié)
以上是生活随笔為你收集整理的【转】深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第五节 引用类型复制问题及用克隆接口ICloneable修复的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: “夏枯草”凉茶名字和价格遭吐槽 网友试喝
- 下一篇: C#的变迁史02 - C# 2.0篇