所谓复制
为了更好的说明问题,我们来对比一下堆上的值类型与引用类型。首先看一下值类型,下面的类和结构体,有一个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.");
}
}
因为Shoe是Dude类的一个属性,所以它们都在堆中。
像下面这样运行
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变成引用类型会怎么样?
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从值类型改成了引用类型,当引用类型的内容被copy时只是copy指向它的指针(而不是指针指向的真实的object),我们需要做一些额外的工作来让引用类型表现的像一个值类型。
很庆幸,有一个接口可以帮助我们完成这个:ICloneable。这个接口约定了所有的Dude要接受并且定义如何复制引用类型,来避免上面的问题。所有需要被复制的类都需要使用这个接口,包括Shoe class。
ICloneable 包含一个方法: Clone()
public object Clone()
{
}
下面是它在Shoe类中的实现
public class Shoe : ICloneable
{
public string Color;
public object Clone()
{
Shoe newShoe = new Shoe();
newShoe.Color = Color.Clone() as string;
return newShoe;
}
}
在Clone()方法内部,创建了一个新的Shoe,复制所有的引用类型和值类型并且返回这个新实例。你可能注意到了,string类已经实现了ICloneable接口,所以我们可以调用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
使我们想要的
封装
作为一个常规练习,我们总是要clone引用类型和复制类型。下面我们更进一步,直接将Dude类实现ICloneable来替代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.
一些有意思的事情需要注意,等号操作符对System.String做了clone操作所以不必担心string类型的复制问题。但是需要注意内存膨胀。string作为引用类型确实是一个指针指向堆中的一块地方,只是单纯的表现的像是值类型。
原文链接:
https://www.c-sharpcorner.com/article/C-Sharp-heaping-vs-stacking-in-net-part-iii/