首页 > 代码库 > .NET的堆和栈03,引用类型对象拷贝以及内存分配

.NET的堆和栈03,引用类型对象拷贝以及内存分配

在" .NET的堆和栈01,基本概念、值类型内存分配"中,了解了"堆"和"栈"的基本概念,以及值类型的内存分配。我们知道:当执行一个方法的时候,值类型实例会在"栈"上分配内存,而引用类型实例会在"堆"上分配内存,当方法执行完毕,"栈"上的实例由操作系统自动释放,"堆"上的实例由.NET Framework的GC进行回收。


在" .NET的堆和栈02,值类型和引用类型参数传递以及内存分配"中,我们了解了值类型参数和引用类型参数在传递时的内存分配情况。


而本篇的重点要放在:引用类型对象拷贝以及内存分配。

 

主要包括:
■  引用类型对象拷贝 成员都是值类型
■  引用类型对象拷贝 包含引用类型成员

 

  引用类型对象拷贝 成员都是值类型

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.");                }            }           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.CopyDude();                  Ted.Name = "Ted";                  Ted.LeftShoe.Color = Ted.RightShoe.Color = "Red";                   Console.WriteLine(Bill.ToString());                  Console.WriteLine(Ted.ToString());                       }

 

输出结果:
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

 

以上,当引用类型的属性、成员都是值类型的时候,拷贝是完全拷贝。

16

 

  引用类型对象拷贝 包含引用类型成员

把Shoe由struct值类型改成引用类型class。

public class Shoe{               public string Color;           }

 

再次运行,输出结果:
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

 

当Dude类包含引用类型属性Shoe的时候,在托管堆上的情况是这样的:
17  

 

拷贝后,2个Dude的Shoe类型的属性指向了同一个托管堆内的Shoe实例,改变Shoe的值会同时影响到2个Dude。


很显然,这不是我们期望的完全拷贝,如果做到完全拷贝呢?
--实现ICloneable接口       

 

ICloneable接口的Clone()方法,允许我们在拷贝的时候,进行一些自定义设置。

 

让引用类Shoe实现ICloneable接口。

public class Shoe : ICloneable             {                  public string Color;                   public object Clone()                  {                      Shoe newShoe = new Shoe();                      newShoe.Color = Color.Clone() as string;                      return newShoe;                  }             }

以上,Shoe的string类型属性Color之所以可以使用Color.Clone()方法,是因为string也实现了ICloneable接口;又由于Clone()返回类型是object,所以,在使用Color.Clone()方法之后,需要把object转换成string类型。


现在,在Dude类的CopyDude()方法中,当拷贝Shoe类型属性的时候,就可以使用Shoe独有的拷贝方法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;                }

 

客户端程序。

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.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  

 

这正是我们期望的完全拷贝!

 

完全拷贝,托管堆上的情况是这样的:
18

 

当然也可以让同时包含值类型和引用类型成员,同时需要拷贝的类实现ICloneable接口。

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             }

 

客户端调用。

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.

 

也是我们期望的完全拷贝!

 

参考资料:
C# Heap(ing) Vs Stack(ing) in .NET: Part III

 

".NET的堆和栈"系列包括:

.NET的堆和栈01,基本概念、值类型内存分配

.NET的堆和栈02,值类型和引用类型参数传递以及内存分配

.NET的堆和栈03,引用类型对象拷贝以及内存分配