博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C#代码中背后进行的值拷贝
阅读量:7209 次
发布时间:2019-06-29

本文共 3099 字,大约阅读时间需要 10 分钟。

一种经常发生的装箱

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()方法.

转载地址:http://eerum.baihongyu.com/

你可能感兴趣的文章
mysql删除sql表添加别名及删除sql的注意事项
查看>>
tmux 基本用法
查看>>
iOS:使用NSRegularExpression正则去掉一串字符串中所有的特殊字符和标点
查看>>
腾讯工程师带你深入解析 MySQL binlog
查看>>
C指针操作ip地址以4个字节的int类型作为传输对象
查看>>
怎样在注冊表禁用或打开windows系统右键菜单
查看>>
项目管理学习笔记之四.风险管理
查看>>
Cisco VPP(1) 简单介绍
查看>>
RxJS -- Subscription
查看>>
关于TF(词频) 和TF-IDF(词频-逆向文件频率 )的理解
查看>>
析构函数
查看>>
手写体识别中用到的Tensorflow函数复习
查看>>
php中str_repeat函数
查看>>
MSSQL sql server 2005/2008 row_number()函数应用之–删除表中重复记录,只保留一条不重复数据...
查看>>
深入浅出理解linux inode结构【转】
查看>>
MySQL 误操作后数据恢复(update,delete忘加where条件)【转】
查看>>
jquery为动态添加元素绑定点击事件
查看>>
Linux常用基本命令:三剑客命令之-awk动作用法(1)
查看>>
[设备]Linux设备是否可以被多个进程或者线程同时Open?
查看>>
Mac下的SecureCRT使用技巧
查看>>