.NET中栈和堆的比较(三)
来源:互联网 发布:preg match 数组 编辑:程序博客网 时间:2024/05/18 07:20
需要回顾堆栈基础,值类型和引用类型,请转到第一部分和第二部分
第一部分
http://agassi001.cnblogs.com/archive/2006/05/10/396574.html
第二部分
http://agassi001.cnblogs.com/archive/2006/05/13/399080.html
* 副本并不是真的副本
为了清楚的阐明问题,让我们来比较一下当堆中存在值类型和引用类型时都发生了些什么。首先来看看值类型,如下面的类和结构。这里有一个类Dude,它的成员中有一个string型的Name字段及两个Shoe类型的字段--RightShoe、LeftShoe,还有一个CopyDude()方法可以很容易地生成新的Dude实例。
{
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的两个字段是Dude类的成员,所以它们都被放在了堆上。
当我们执行以下的方法时:
{
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());
}
我们得到了预期的结果:
Ted : Dude!, I have a Red shoe on my right foot, and a Red on my left foot.
如果我们将结构Shoe换成引用类型会发生什么?问题就在于此。
假如我们将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
可以看到红鞋子被穿到别人(Bill)脚上了,很明显出错了。你想知道这是为什么吗?我们再来看看堆就明白了。
由于我们现在使用的Shoe是引用类型而非值类型,当引用类型的内容被拷贝时实际上只拷贝了该类型的指针(并没有拷贝实际的对象),我们需要作一些额外的工作来使我们的引用类型能够像值类型一样使用。
幸运的是.NET Framework中已经有了一个IClonealbe接口(System.ICloneable)来帮助我们解决问题。使用这个接口可以规定所有的Dude类必须遵守和定义引用类型应如何被复制,以避免出现"共享鞋子"的问题。所有需要被克隆的类都需要使用ICloneable接口,包括Shoe类。
System.IClonealbe只有一个方法定义:Clone()
public object Clone()
{
}
我们应该在Shoe类中这样实现:
{
public string Color;
#region ICloneable Members
public object Clone()
{
Shoe newShoe = new Shoe();
newShoe.Color = Color.Clone() as string;
return newShoe;
}
#endregion
}
在方法Clone()中,我们创建了一个新的Shoe对象,克隆了所有引用类型,并拷贝了所有值类型,然后返回了这个新对象。你可能注意到了string类已经实现了ICloneable接口,所以我们可以直接调用Color.Clone()方法。因为Clone()方法返回的是对象的引用,所以我们需要在设置鞋的颜色前重构这个引用。
接着,在我们的CopyDude()方法中我们需要克隆鞋子而非拷贝它们:
{
Dude newPerson = new Dude();
newPerson.Name = Name;
newPerson.LeftShoe = LeftShoe.Clone() as Shoe;
newPerson.RightShoe = RightShoe.Clone() as Shoe;
return newPerson;
}
现在,当我们执行Main()函数时:
{
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());
}
我们得到的是:
Ted : Dude!, I have a Red shoe on my right foot, and a Red on my left foot
这就是我们想要的。
在通常情况下,我们应该"克隆"引用类型,"拷贝"值类型。(这样,在你调试以上介绍的情况中的问题时,会减少你买来控制头痛的阿司匹林的药量)
在头痛减少的激烈下,我们可以更进一步地使用Dude类来实现IClonealbe,而不是使用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.CopyDude()方法改为Dude.Clone():
public static void Main()
{
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());
}
最后的结果是:
Ted : Dude!, I have a Red shoe on my right foot, and a Red on my left foot.
非常好!
比较有意思的是请注意为System.String类分配的操作符("="号),它实际上是将string型对象进行克隆,所以你不必担心会发生引用拷贝。尽管如此你还是得注意一下内存的膨胀。
如果你重新看一下前面的那些图,会发现string型应该是引用类型,所以它应该是一个指针(这个指针指向堆中的另一个对象),但是为了方便起见,我在图中将string型表示为值类型(实际上应该是一个指针),因为通过"="号重新被赋值的string型对象实际上是被自动克隆过后的。
总结一下:
通常,如果我们打算将我们的对象用于拷贝,那么我们的类应该实现IClonealbe借口,这样能够使引用类型仿效值类型的行为。从中可以看到,搞清楚我们所使用的变量的类型是非常重要的,因为在值类型和引用类型的对象在内存中的分配是有区别的。
在下一部分内容中,会看到我们是怎样来减少代码在内存中的"脚印"的,将会谈到期待已久的垃圾回收器(Garbage Collection)。
- .NET中栈和堆的比较(三)
- .NET中栈和堆的比较
- .NET中栈和堆的比较
- .NET中栈和堆的比较
- .NET中栈和堆的比较
- .NET中栈和堆的比较
- .NET中栈和堆的比较
- .NET中栈和堆的比较
- NET中栈和堆的区别(比较)
- .NET中栈和堆的比较(一)
- .NET中栈和堆的比较(二)
- .NET中栈和堆的比较(一)
- .NET中栈和堆的比较(二)
- .NET中栈和堆的比较(四)
- NET中栈和堆的区别(比较)
- 继续:.NET中栈和堆的比较之三-四
- .NET中栈和堆的比较 #1
- .NET中栈和堆的比较 #2
- 微软和谷歌荣膺"亚洲最受尊敬跨国公司"
- 总结mysql不能远程访问的方法
- .NET中栈和堆的比较(二)
- 英特尔:iPhone表现不佳是ARM处理器过错
- 微软CTO:中国不必模仿印度软件模式
- .NET中栈和堆的比较(三)
- 分析师:别指望Windows 7在2010年前上市
- .NET中栈和堆的比较(四)
- OpenOffice 3.0发布下载数量暴增
- 首次参与现场实施的一点小体会
- 谷歌Adsense年底前将推出本地电子支付
- amoeba 的文章将移至 http://amoeba.meidusa.com
- 蝎,和瓶一起变老吧
- 乔布斯与戴尔10年之争 苹果今天可收购戴尔