代码改变世界

.net垃圾回收学习[C#中的Stack和heap][续3]

2011-08-21 22:31  一一九九  阅读(203)  评论(0编辑  收藏  举报

From: http://www.c-sharpcorner.com/UploadFile/rmcochran/chsarp_memory401152006094206AM/chsarp_memory4.aspx

In this article we'll cover an issue that arises from having reference variables in the heap and how to fix it using ICloneable.

A Copy is not A Copy

         为了定义问题所在,我们将要观察在heap上有一个value type和 reference type的区别。首先,我们看一下value type. 有如下代码:

           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.");
                }
           }

他们在heap上的样子大体如下所示:

heapvsstack3-1

然后执行以下代码:

           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;

           }

然后得到的输出是:

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

很明显这是一个错误,我们在堆里的情况如下所示:

image

因为我们此时使用Shoe作为一个引用类型,所以复制的时候复制的是地址,而不是值。

幸好我们有IConeable接口,这个接口是一个协议保证所有的引用类型都被复制以避免出现共享的错误,所有的需要被"Cloned"的Classes都应该使用IConealbe Interface,包括shoe Class.

IClonable接口包含一个方法Clone()

                  public object Clone()
                  {
                  }

然后我们在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方法中,我们New一个新的Shoe, 复制所有的引用类型并且返回新的对象。你可能注意到String class已经实现了ICloneable,所以我们直接调用了Color.Clone()方法,因为Clone()返回了一个Object 对象,我们在使用新的对象的时候需要进行一下强制类型转换。

新的CopyDude()方法实现如下:

                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
此时heap 的情况是:
image

 

一般的最佳实践是我们直接实现Clone()方法,而不是提供额外的CopyXX方法:

           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
             }

Something interesting to note is that the assignment operator (the "=" sign) for the System.String class actually clones the string so you don't have to worry about duplicate references. However you do have to watch our for memory bloating. If you look back at the diagrams, because the string is a reference type it really should be a pointer to another object in the heap, but for simplicity's sake, it's shown as a value type.