[译文]C# Heap(ing) Vs Stack(ing) in .NET: Part III
2012-05-17 23:52
579 查看
原文地址:http://www.c-sharpcorner.com/UploadFile/rmcochran/chsarp_memory401152006094206AM/chsarp_memory4.aspx
ACopyIsNotACopy
为了清楚的说明这个问题,我们来校验以下两种情形将会发生什么情况:位于堆里面的一个值类型和位于堆里面的一个引用类型。我们先看值类型。看下面的class和struct。我们有一个Dude类,该类包含一个Name元素和两个Shoe(s)。同时,添加一个CopyDude()方法来更加方便的newDudes.
publicstructShoe{
publicstringColor;
}
publicclassDude
{
publicstringName;
publicShoeRightShoe;
publicShoeLeftShoe;
publicDudeCopyDude()
{
DudenewPerson=newDude();
newPerson.Name=Name;
newPerson.LeftShoe=LeftShoe;
newPerson.RightShoe=RightShoe;
returnnewPerson;
}
publicoverridestringToString()
{
return(Name+":Dude!,Ihavea"+RightShoe.Color+
"shoeonmyrightfoot,anda"+
LeftShoe.Color+"onmyleftfoot.");
}
}我们的Dude是一个引用类型,但是Shoe是一个结构体,属于值类型,在堆栈上的反应是这样的:
当我们执行以下方法的时候:publicstaticvoidMain()
{
Class1pgm=newClass1();
DudeBill=newDude();
Bill.Name="Bill";
Bill.LeftShoe=newShoe();
Bill.RightShoe=newShoe();
Bill.LeftShoe.Color=Bill.RightShoe.Color="Blue";
DudeTed=Bill.CopyDude();
Ted.Name="Ted";
Ted.LeftShoe.Color=Ted.RightShoe.Color="Red";
Console.WriteLine(Bill.ToString());
Console.WriteLine(Ted.ToString());
}
我们得到了预期的结果:
Bill:Dude!,IhaveaBlueshoeonmyrightfoot,andaBlueonmyleftfoot.
Ted:Dude!,IhaveaRedshoeonmyrightfoot,andaRedonmyleftfoot.
如果我们把Shoe改成引用类型,会是怎样一种情况呢?问题就在于此。我们将Shoe改成引用类型(class):
publicclassShoe{
publicstringColor;
}
让后再次执行Main()方法,我们得到的结果是:
Bill:Dude!,IhaveaRedshoeonmyrightfoot,andaRedonmyleftfoot
Ted:Dude!,IhaveaRedshoeonmyrightfoot,andaRedonmyleftfoot
注意到红色部分,这显然是一个错误。这是我们在堆里面得到的结果:
由于我们现在使用Shoe作为引用类型而不是值类型,而,当我们拷贝引用类型的内容时,我们只是拷贝了指针。我们必须做一些额外的工作,让我们的引用类型看起来更像是值类型。
幸运的是,我们有一个接口可以帮助我们解决这个问题:ICloneable。这个接口是所有的Dudes都遵守的契约,并且定义了一个应用类型是如何复制的,以防止我们的”共享鞋(shoesharing)”的错误。所有的类要能被clone,都需要实现ICloneable接口,我们的Shoe也不例外。
ICloneable包含一个方法:Clone():
publicobjectClone()
{
}
我们将Shoe类改成如下:
publicclassShoe:ICloneable
{
publicstringColor;
#regionICloneableMembers
publicobjectClone()
{
ShoenewShoe=newShoe();
newShoe.Color=Color.Clone()asstring;
returnnewShoe;
}
#endregion
}
在Clone方法中,我们只是new了一个Shoe,克隆了所有的引用类型和拷贝了所有的值类型,然后返回一个新的object。也许你已经注意到了,string已经实现了ICloneable,因此我们可以直接调用Color.Clone()。由于Clone()方法是返回引用类型的,因此,在我们设定shoe的color之前我们必须重新设定引用的类型(retypethereference).
下一步,在我们的CopyDude()方法中,我们需要克隆shoes而不是拷贝他们:
publicDudeCopyDude()
{
DudenewPerson=newDude();
newPerson.Name=Name;
newPerson.LeftShoe=LeftShoe.Clone()asShoe;
newPerson.RightShoe=RightShoe.Clone()asShoe;
returnnewPerson;
}
现在,当我们运行Main()方法:
publicstaticvoidMain()
{
Class1pgm=newClass1();
DudeBill=newDude();
Bill.Name="Bill";
Bill.LeftShoe=newShoe();
Bill.RightShoe=newShoe();
Bill.LeftShoe.Color=Bill.RightShoe.Color="Blue";
DudeTed=Bill.CopyDude();
Ted.Name="Ted";
Ted.LeftShoe.Color=Ted.RightShoe.Color="Red";
Console.WriteLine(Bill.ToString());
Console.WriteLine(Ted.ToString());
}
我们得到的结果:
Bill:Dude!,IhaveaBlueshoeonmyrightfoot,andaBlueonmyleftfoot
Ted:Dude!,IhaveaRedshoeonmyrightfoot,andaRedonmyleftfoot
这正是我们需要的:
将东西包装起来(WrappingThingsUp)
作为通常的情况,我们总是想要克隆引用类型和拷贝值类型。(这将减少你为了防止头疼而不得不买的阿司匹林,当你调试这些错误的时候)
为了减少令人头痛的事,让我们更进一步,来整理Dude类,使它是想ICloneable,而不是使用CopyDude()方法。publicclassDude:ICloneable
{
publicstringName;
publicShoeRightShoe;
publicShoeLeftShoe;
publicoverridestringToString()
{
return(Name+":Dude!,Ihavea"+RightShoe.Color+
"shoeonmyrightfoot,anda"+
LeftShoe.Color+"onmyleftfoot.");
}
#regionICloneableMembers
publicobjectClone()
{
DudenewPerson=newDude();
newPerson.Name=Name.Clone()asstring;
newPerson.LeftShoe=LeftShoe.Clone()asShoe;
newPerson.RightShoe=RightShoe.Clone()asShoe;
returnnewPerson;
}
#endregion
}
我们也需要修改Main()方法来使用Dude.Clone():
publicstaticvoidMain()
{
Class1pgm=newClass1();
DudeBill=newDude();
Bill.Name="Bill";
Bill.LeftShoe=newShoe();
Bill.RightShoe=newShoe();
Bill.LeftShoe.Color=Bill.RightShoe.Color="Blue";
DudeTed=Bill.Clone()asDude;
Ted.Name="Ted";
Ted.LeftShoe.Color=Ted.RightShoe.Color="Red";
Console.WriteLine(Bill.ToString());
Console.WriteLine(Ted.ToString());
}
我们最后的输出是:
Bill:Dude!,IhaveaBlueshoeonmyrightfoot,andaBlueonmyleftfoot.
Ted:Dude!,IhaveaRedshoeonmyrightfoot,andaRedonmyleftfoot.
一切都正常。
一些值得注意的有趣的事是System.String的赋值操作(=操作符)事实上克隆了string,所以你不必担心重复的引用。然而,你需要注意防止内存膨胀。如果你看了上面的图标,由于string是一个引用类型,它需要指向堆里的另外一个对象,但为了简单,它显示成一个值类型。
总结:
通常的,如果我们有打算拷贝我们的对象,我们需要实现ICloneable。这样可以使我们的引用类型在行为上看起来有点像值类型。正如你所看到的,记录我们正在处理的是那种类型变量是很重要的,因为值类型和引用类型在分配内存时是不一样的。
待续…
ACopyIsNotACopy
为了清楚的说明这个问题,我们来校验以下两种情形将会发生什么情况:位于堆里面的一个值类型和位于堆里面的一个引用类型。我们先看值类型。看下面的class和struct。我们有一个Dude类,该类包含一个Name元素和两个Shoe(s)。同时,添加一个CopyDude()方法来更加方便的newDudes.
{
Class1pgm=newClass1();
DudeBill=newDude();
Bill.Name="Bill";
Bill.LeftShoe=newShoe();
Bill.RightShoe=newShoe();
Bill.LeftShoe.Color=Bill.RightShoe.Color="Blue";
DudeTed=Bill.CopyDude();
Ted.Name="Ted";
Ted.LeftShoe.Color=Ted.RightShoe.Color="Red";
Console.WriteLine(Bill.ToString());
Console.WriteLine(Ted.ToString());
}
我们得到了预期的结果:
Bill:Dude!,IhaveaBlueshoeonmyrightfoot,andaBlueonmyleftfoot.
Ted:Dude!,IhaveaRedshoeonmyrightfoot,andaRedonmyleftfoot.
如果我们把Shoe改成引用类型,会是怎样一种情况呢?问题就在于此。我们将Shoe改成引用类型(class):
publicstringColor;
}
让后再次执行Main()方法,我们得到的结果是:
Bill:Dude!,IhaveaRedshoeonmyrightfoot,andaRedonmyleftfoot
Ted:Dude!,IhaveaRedshoeonmyrightfoot,andaRedonmyleftfoot
注意到红色部分,这显然是一个错误。这是我们在堆里面得到的结果:
由于我们现在使用Shoe作为引用类型而不是值类型,而,当我们拷贝引用类型的内容时,我们只是拷贝了指针。我们必须做一些额外的工作,让我们的引用类型看起来更像是值类型。
幸运的是,我们有一个接口可以帮助我们解决这个问题:ICloneable。这个接口是所有的Dudes都遵守的契约,并且定义了一个应用类型是如何复制的,以防止我们的”共享鞋(shoesharing)”的错误。所有的类要能被clone,都需要实现ICloneable接口,我们的Shoe也不例外。
ICloneable包含一个方法:Clone():
{
}
我们将Shoe类改成如下:
{
publicstringColor;
#regionICloneableMembers
publicobjectClone()
{
ShoenewShoe=newShoe();
newShoe.Color=Color.Clone()asstring;
returnnewShoe;
}
#endregion
}
在Clone方法中,我们只是new了一个Shoe,克隆了所有的引用类型和拷贝了所有的值类型,然后返回一个新的object。也许你已经注意到了,string已经实现了ICloneable,因此我们可以直接调用Color.Clone()。由于Clone()方法是返回引用类型的,因此,在我们设定shoe的color之前我们必须重新设定引用的类型(retypethereference).
下一步,在我们的CopyDude()方法中,我们需要克隆shoes而不是拷贝他们:
{
DudenewPerson=newDude();
newPerson.Name=Name;
newPerson.LeftShoe=LeftShoe.Clone()asShoe;
newPerson.RightShoe=RightShoe.Clone()asShoe;
returnnewPerson;
}
现在,当我们运行Main()方法:
{
Class1pgm=newClass1();
DudeBill=newDude();
Bill.Name="Bill";
Bill.LeftShoe=newShoe();
Bill.RightShoe=newShoe();
Bill.LeftShoe.Color=Bill.RightShoe.Color="Blue";
DudeTed=Bill.CopyDude();
Ted.Name="Ted";
Ted.LeftShoe.Color=Ted.RightShoe.Color="Red";
Console.WriteLine(Bill.ToString());
Console.WriteLine(Ted.ToString());
}
我们得到的结果:
Bill:Dude!,IhaveaBlueshoeonmyrightfoot,andaBlueonmyleftfoot
Ted:Dude!,IhaveaRedshoeonmyrightfoot,andaRedonmyleftfoot
这正是我们需要的:
将东西包装起来(WrappingThingsUp)
作为通常的情况,我们总是想要克隆引用类型和拷贝值类型。(这将减少你为了防止头疼而不得不买的阿司匹林,当你调试这些错误的时候)
为了减少令人头痛的事,让我们更进一步,来整理Dude类,使它是想ICloneable,而不是使用CopyDude()方法。
{
publicstringName;
publicShoeRightShoe;
publicShoeLeftShoe;
publicoverridestringToString()
{
return(Name+":Dude!,Ihavea"+RightShoe.Color+
"shoeonmyrightfoot,anda"+
LeftShoe.Color+"onmyleftfoot.");
}
#regionICloneableMembers
publicobjectClone()
{
DudenewPerson=newDude();
newPerson.Name=Name.Clone()asstring;
newPerson.LeftShoe=LeftShoe.Clone()asShoe;
newPerson.RightShoe=RightShoe.Clone()asShoe;
returnnewPerson;
}
#endregion
}
我们也需要修改Main()方法来使用Dude.Clone():
{
Class1pgm=newClass1();
DudeBill=newDude();
Bill.Name="Bill";
Bill.LeftShoe=newShoe();
Bill.RightShoe=newShoe();
Bill.LeftShoe.Color=Bill.RightShoe.Color="Blue";
DudeTed=Bill.Clone()asDude;
Ted.Name="Ted";
Ted.LeftShoe.Color=Ted.RightShoe.Color="Red";
Console.WriteLine(Bill.ToString());
Console.WriteLine(Ted.ToString());
}
我们最后的输出是:
Bill:Dude!,IhaveaBlueshoeonmyrightfoot,andaBlueonmyleftfoot.
Ted:Dude!,IhaveaRedshoeonmyrightfoot,andaRedonmyleftfoot.
一切都正常。
一些值得注意的有趣的事是System.String的赋值操作(=操作符)事实上克隆了string,所以你不必担心重复的引用。然而,你需要注意防止内存膨胀。如果你看了上面的图标,由于string是一个引用类型,它需要指向堆里的另外一个对象,但为了简单,它显示成一个值类型。
总结:
通常的,如果我们有打算拷贝我们的对象,我们需要实现ICloneable。这样可以使我们的引用类型在行为上看起来有点像值类型。正如你所看到的,记录我们正在处理的是那种类型变量是很重要的,因为值类型和引用类型在分配内存时是不一样的。
待续…
相关文章推荐
- C# Heap(ing) Vs Stack(ing) in .NET: Part III
- [译文]C# Heap(ing) Vs Stack(ing) in .NET: Part II
- [译文]C# Heap(ing) Vs Stack(ing) in .NET: Part I
- [译文]C# Heap(ing) Vs Stack(ing) in .NET: Part IV
- C# Heap(ing) Vs Stack(ing) in .NET: Part I
- C# Heap(ing) Vs Stack(ing) in .NET: Part II
- C# Heap(ing) Vs Stack(ing) in .NET: Part I
- C# Heap(ing) Vs Stack(ing) in .NET: Part IV
- 深入浅出图解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) 第二节 栈基本工作原理
- 深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第三节 栈与堆,值类型与引用类型
- C# Heap(ing) Vs Stack(ing)
- 深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第五节 引用类型复制问题及用克隆接口ICloneable修复
- 深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第四节 参数传递对堆栈的影响 1
- C# Heap(ing) Vs Stack(ing) in .NET: Part IV
- 深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第六节 理解垃圾回收GC,提搞程序性能
- C# Heap(ing) Vs Stack(ing) in .NET: Part I
- 深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第五节 引用类型复制问题及用克隆接口ICloneable修复