zoukankan      html  css  js  c++  java
  • C#代码中背后进行的值拷贝

    一种经常发生的装箱

    Int32 i = 100;
    Console.WriteLine("The number is: " + i);

            通过VS SDK Tools里的IL DASM工具看看产生的IL代码:

    .method private hidebysig static void  Main(string[] args) cil managed
    {
      .entrypoint
      // Code size       27 (0x1b)
      .maxstack  2
      .locals init ([0] int32 i)
      IL_0000:  nop
      IL_0001:  ldc.i4.s   100
      IL_0003:  stloc.0
      IL_0004:  ldstr      "The number is: "
      IL_0009:  ldloc.0
      IL_000a:  box        [mscorlib]System.Int32
      IL_000f:  call       string [mscorlib]System.String::Concat(object,
                                                                  object)
      IL_0014:  call       void [mscorlib]System.Console::WriteLine(string)
      IL_0019:  nop
      IL_001a:  ret
    } // end of method Program::Main

            可以发现在IL_000a行有一个box装箱操作. 这主要是因为Console.WriteLine方法是输出一个字符串, 这时我们输入了带+号的计算式, 会调用String.Concat(Object arg0, Object arg1)的方法, 如此以来刚刚的Int32数据会被装箱成一个Object数据.

    完成一次装箱的步骤

    1. 新分配托管堆内存(大小为值类型实例大小加上一个方法表指针和一个SyncBlockIndex)

    2. 将值类型的实例字段拷贝到新分配的内存中

    3. 返回托管堆中新分配对象的引用地址

    避免这样的装箱

            装箱就像给一件物品打包, 这需要一点时间, 上面的代码装箱时间可以忽略不计, 但如果这样的代码出现在一个循环次数比较多的中就需要改进一下. 但避免这样的装箱很简单, 把上面两行代码改成这样:

    Int32 i = 100;
    Console.WriteLine("The number is: " + i.ToString());

            代码只是简单的将Int32变成一个String类型(引用类型), 有人怀疑ToString()方法会执行一次装箱, 因为他们觉得i是一个值类型, 而String是一个引用类型. 但可以查看这两句产生的IL代码看看有没有发生装箱:

    .method private hidebysig static void  Main(string[] args) cil managed
    {
      .entrypoint
      // Code size       28 (0x1c)
      .maxstack  2
      .locals init ([0] int32 i)
      IL_0000:  nop
      IL_0001:  ldc.i4.s   100
      IL_0003:  stloc.0
      IL_0004:  ldstr      "The number is: "
      IL_0009:  ldloca.s   i
      IL_000b:  call       instance string [mscorlib]System.Int32::ToString()
      IL_0010:  call       string [mscorlib]System.String::Concat(string,
                                                                  string)
      IL_0015:  call       void [mscorlib]System.Console::WriteLine(string)
      IL_001a:  nop
      IL_001b:  ret
    } // end of method Program::Main

            可以发现ToString()方法并不会产生任何box装箱操作的, 仅仅是值类型获得获得值的字符串表现形式罢了.

    值类型与引用类型之间的转换

            在使用new关键字创建一个引用类型对象的时候, 这个对象总是存在在托管堆里, 返回的是指向这个对象的指针. 每一次创建引用类型的实例, 都需要从托管堆中分配内存, 垃圾回收机制会管理着这些内存. 如果每种类型都被这样管理着, 这种机制会对程序的性能产生一些负面影响, 因此对于那些经常使用的简单类型, CLR把他们归于值类型, 它们被分配在堆栈上.

            所有被称为”类”的都是引用类型! 特别注意的是System.String, 它也是个类, 它也是引用类型, 由于一种”字符串驻留”技术, 使它成为了”拥有值类型特点的引用类型”. 而结构或者枚举类型都是值类型, 比如Int32它也只不过是一个struct罢了.

            值类型因为不受垃圾回收机制等等作用, 在某些情况下可以获得更好的性能. 但如果值类型的实例如果经常被某Class经常调用比如被放到List<T>之类的集合(也是类)中, 程序会开辟另外的内存, 把该值类型实例的值拷贝到该内存里…这样做会影响到性能.

            因此我个人觉得值在下面两个情况下拷贝了, 并且我们本不太希望这样的事情发生:

    1. 方法传递的参数类型是Object类型. 当然这样的做法是为了能够兼容其它各种类型的参数, 不过通过可以重载这样的方法避免一次值类型->Object类型的操作.

    2. 值类型数据被某个Class使用了.

    内存何时被释放

            值类型的变量在作用域结束后就自动释放了, 而引用类型都需要通过垃圾回收机制来释放内存.

            但是, Stream也是一个类, 按道理它产生的实例也受托管代码管理, 并有垃圾回收机制对它的资源(内存)进行回收. 但我们还需要输入一遍xxStrean.Close()和xxStream.Dispose(), 原因是内存回收的回收具有不确定性. 如果不写xxStream.Dispose(), CLR的确在某个时刻也会回收它的资源, 只不过出于以下两点考虑, 我们需要输入xxStream.Dispose():

    1. 针对Stream类, 内存资源比较有限, 需要及时得释放已经确定不需要再使用的资源. 其他的比如网络连接的资源同样如此.

    2. Stream打开的资源大多是独享的, 在它没被释放之前, 如果其它的代码试图再次打开这个资源, 会抛出异常

            当然如果觉得写xxStrean.Close()和xxStream.Dispose()比较烦的话, C#提供了using语句块的用法:

    using(FileStream fs = new FileStream(......))
    {
        //......
    }

            上面代码中的fs会在using语句块结束前得到及时的释放. 当然using后面()中的对象需要实现IDisposable 接口, 这个接口里面提供了Dispose()方法.

    作者:Create Chen
    出处:http://technology.cnblogs.com
    说明:文章为作者平时里的思考和练习,可能有不当之处,请博客园的园友们多提宝贵意见。
    知识共享许可协议本作品采用知识共享署名-非商业性使用-相同方式共享 2.5 中国大陆许可协议进行许可。

  • 相关阅读:
    会计基础模拟练习2
    Foxmail邮箱最新应用指南
    会计基础第一节内容概述
    会计基础一
    如何解决Linux中Tomcat启动失败
    Linux 打开端口方法(防火墙操作)
    @PathVariable为空时指定默认值
    Thymeleaf 遍历Map 输出Key Value
    thymeleaf中的th:each用法
    Linux后台执行脚本 &与nohup
  • 原文地址:https://www.cnblogs.com/technology/p/2012508.html
Copyright © 2011-2022 走看看