zoukankan      html  css  js  c++  java
  • [译文]C# Heap(ing) Vs Stack(ing) in .NET: Part III

    原文地址:http://www.c-sharpcorner.com/UploadFile/rmcochran/chsarp_memory401152006094206AM/chsarp_memory4.aspx

    A Copy Is Not A Copy

    为了清楚的说明这个问题,我们来校验以下两种情形将会发生什么情况:位于堆里面的一个值类型和位于堆里面的一个引用类型。我们先看值类型。看下面的class和struct。我们有一个Dude类,该类包含一个Name元素和两个Shoe(s)。同时,添加一个CopyDude()方法来更加方便的new Dudes.

              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.");
    
                    }
               }
     
    我们的Dude是一个引用类型,但是Shoe是一个结构体,属于值类型,在堆栈上的反应是这样的:
     
    当我们执行以下方法的时候:
               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改成引用类型,会是怎样一种情况呢?问题就在于此。我们将Shoe改成引用类型(class):

    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作为引用类型而不是值类型,而,当我们拷贝引用类型的内容时,我们只是拷贝了指针。我们必须做一些额外的工作,让我们的引用类型看起来更像是值类型。

     

    幸运的是,我们有一个接口可以帮助我们解决这个问题:ICloneable。这个接口是所有的Dudes都遵守的契约,并且定义了一个应用类型是如何复制的,以防止我们的”共享鞋(shoe sharing)”的错误。所有的类要能被clone,都需要实现ICloneable接口,我们的Shoe也不例外。

     

    ICloneable包含一个方法:Clone():

    public object Clone()
    
                      {
    
                      }

     

    我们将Shoe类改成如下:

                 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,克隆了所有的引用类型和拷贝了所有的值类型,然后返回一个新的object。也许你已经注意到了,string已经实现了ICloneable,因此我们可以直接调用Color.Clone()。由于Clone()方法是返回引用类型的,因此,在我们设定shoe的color之前我们必须重新设定引用的类型(retype the reference).

    下一步,在我们的CopyDude()方法中,我们需要克隆shoes而不是拷贝他们:

                    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

     

    这正是我们需要的:

     

    将东西包装起来(Wrapping Things Up)

     

    作为通常的情况,我们总是想要克隆引用类型和拷贝值类型。(这将减少你为了防止头疼而不得不买的阿司匹林,当你调试这些错误的时候)

     

    为了减少令人头痛的事,让我们更进一步,来整理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的赋值操作(=操作符)事实上克隆了string,所以你不必担心重复的引用。然而,你需要注意防止内存膨胀。如果你看了上面的图标,由于string是一个引用类型,它需要指向堆里的另外一个对象,但为了简单,它显示成一个值类型。

     

    总结:

    通常的,如果我们有打算拷贝我们的对象,我们需要实现ICloneable。这样可以使我们的引用类型在行为上看起来有点像值类型。正如你所看到的,记录我们正在处理的是那种类型变量是很重要的,因为值类型和引用类型在分配内存时是不一样的。

     

    待续…

  • 相关阅读:
    缺少环境变量导致Xilinx Platform Studio无法打开之解决方法
    PS利用EMIO控制LED灯
    利用zedboard添加自定义IP核完成简易计算器
    读后感 关于 《不要一辈子靠技术混饭吃》(挪窝第一篇)
    挪窝了,再谈blog
    windows server 2008 无法打开角色,角色错误,刷新服务器时出现意外错误,HRESULT:0x80070422
    Linux 基础正则表达式和扩展正则表达式
    Linux 基础正则表达式
    Linux 通配符与特殊符号
    Linux 基础命令
  • 原文地址:https://www.cnblogs.com/tian2010/p/2507087.html
Copyright © 2011-2022 走看看