本想接着上一篇详解泛型接着写一篇使用泛型时需要注意的一个性能问题,但是后来想着不如将之前的详解XX系列更正为现在的效率优化XX系列,记录在工作时遇到的一些性能优化的经验和技巧,如果有什么不足,还请大家多多指出;

  在使用集合时,通常为了防止装箱操作而选择List<T>、Dictionary<TKey, TValue>等泛型集合,但是在使用过程中如果使用不当,依然会产生大量的装箱操作;

  首先,将值类型的实例当做引用类型来使用时,即会产生装箱,例如:

  1. int num = ;
  2. object obj = num;
  3. IEquatable<int> iEquatable = num;

  其次,对于自定义结构,在正常使用时,通常需要注意一些误装箱的操作:

  1. public struct MyStruct
  2. {
  3. public int MyNum;
  4. }

  对该结构MyStruct的实例调用基类Object中的方法时,都会进行装箱操作,对于静态方法(Equals、ReferenceEquals)很好理解,对于实例方法,在CLR调用实例方法时,实际上会把调用这个方法的对象当作第一个参数传入实例方法,而基类Object中的实例方法都会将Object类型的对象作为第一个参数,因此也会发生装箱,这其中的实例方法包括GetType和虚方法Equals、GetHashCode、ToString;

  其中,GetType方法本身就是通过堆内存中与实例数据一起存储的类型对象指针来获取实例类型信息的,对于值类型实例,本身就没有这个开销成员,此处应使用typeof()运算符代替避免装箱;

  三个虚方法可以通过在MyStruct中重写来防止装箱操作;但是对于Equals方法,有一些需要区别注意的地方:

  在调用值类型基类ValueType中的ValueType.Equals(object obj)方法进行比较操作时,会对当前实例和实参obj进行装箱,共两次装箱(抽象基类ValueType依然是类类型);在MyStruct中重写了该方法MyStruct.Equals(object obj),在调用myStruct1.Equals(myStruct2)时,依然会对myStruct2进行装箱,共一次装箱,此时我们可以在MyStruct中声明一个Equals的重载方法,参数类型同样为MyStruct,同时对==和!=运算符进行重载:

  1. public struct MyStruct
  2. {
  3. public int MyNum;
  4. public override bool Equals(object obj) //调用时会对实参进行装箱
  5. {
  6. if (!(obj is MyStruct))
  7. {
  8. return false;
  9. }
  10. MyStruct other = (MyStruct)obj; //拆箱
  11. return this.MyNum == other.MyNum;
  12. }
  13. public bool Equals(MyStruct other) //重载Equals方法,避免装箱
  14. {
  15. return this.MyNum == other.MyNum;
  16. }
  17. public static bool operator ==(MyStruct left, MyStruct right) //比较时通常采用==运算符
  18. {
  19. return left.Equals(right);
  20. }
  21. public static bool operator !=(MyStruct left, MyStruct right)
  22. {
  23. return !(left == right);
  24. }
  25. }

  此时,在调用myStruct1.Equals(myStruct2)、myStruct1 == myStruct2、myStruct1 != myStruct2时都不再产生装箱操作;

  但是,在使用泛型方法时,例如对于以下的方法,重载方法并不会生效:

  1. static bool MyFunc<T>(T obj1, T obj2)
  2. {
  3. return obj1.Equals(obj2);
  4. }

  查看其生成的IL代码可以清楚的知道不生效的原因:

  其中默认对obj2进行了box指令调用,而对于obj1,在调用callvir指令时加入了前缀constrained指令,则会判断obj1的类型定义中是否存在Equals方法的重写,如果有则调用重写方法,如果没有,则装箱后调用基类ValueType中的虚方法;前面MyStruct的定义中重写了Equals方法,因此会调用该重写方法,此时只触发一次对obj2的装箱,但依然不是我们想要的;

  为了避免这个问题,我们需要在MyStruct的定义中实现IEquatable<T>接口,并在这个泛型方法的声明中添加约束:

  1. public struct MyStruct : IEquatable<MyStruct>
  2. {
  3. public int MyNum;
  4. public override bool Equals(object obj)
  5. {
  6. if (!(obj is MyStruct))
  7. {
  8. return false;
  9. }
  10. MyStruct other = (MyStruct)obj;
  11. return this.MyNum == other.MyNum;
  12. }
  13. public bool Equals(MyStruct other) //实现IEquatable<T>接口中的方法
  14. {
  15. return this.MyNum == other.MyNum;
  16. }
  17. public static bool operator ==(MyStruct left, MyStruct right)
  18. {
  19. return left.Equals(right);
  20. }
  21. public static bool operator !=(MyStruct left, MyStruct right)
  22. {
  23. return !(left == right);
  24. }
  25. }
  1. static bool MyFunc<T>(T obj1, T obj2) where T : IEquatable<T>
  2. {
  3. return obj1.Equals(obj2);
  4. }

  此时,查看其IL代码,可以发现没有了box指令,避免了装箱操作:

  对泛型集合List<Mystruct>使用一些内含比较的实例方法时,也会遇到上面的装箱问题,解决方法同样是实现IEquatable<T>接口;以常用的Contains方法举例:

  List<MyStruct>中的Contains方法中会调用泛型抽象类EqualityComparer<T>.Default的实例来进行比较,而在抽象类EqualityComparer<T>中,会根据类型参数T实例化对应的具体类实例,具体可查看EqualityComparer<T>.CreateComparer()中的实例生成逻辑,其中,会根据T是否实现了IEquatable<T>接口而实例化不同的类的实例:

  1. internal class GenericEqualityComparer<T>: EqualityComparer<T> where T: IEquatable<T>
  1. internal class ObjectEqualityComparer<T>: EqualityComparer<T>

  这两个类的具体实现这里不再赘述;

  基于上面的理解,对于值类型,实现基类的虚方法和IEquatable<T>接口对于避免装箱十分有必要;


如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的认可是我写作的最大动力!

作者:Minotauros
出处:https://www.cnblogs.com/minotauros/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

C#效率优化(1)-- 使用泛型时避免装箱的更多相关文章

  1. php程序效率优化的一些策略小结

    php程序效率优化的一些策略小结   1.在可以用file_get_contents替代file.fopen.feof.fgets等系列方法的情况下,尽量用 file_get_contents,因为他 ...

  2. jquery选择器效率优化问题

    jquery选择器效率优化问题   jquery选择器固然强大,但是使用不当回导致效率问题: 1.要养成将jQuery对象缓存进变量的习惯 //不好的写法 $('#btn').bind("c ...

  3. Jenkins Kubernetes Slave 调度效率优化小记

    Jenkins K8S Slave 调度效率优化 by yue994488@126.com 使用kubernetes为测试工具Gatling进行大规模压测,压测期间发现Jenkins调度压测实例较慢, ...

  4. 见招拆招-PostgreSQL中文全文索引效率优化

    * { color: #3e3e3e } body { font-family: "Helvetica Neue", Helvetica, "Hiragino Sans ...

  5. Unity3d代码及效率优化总结

    1.PC平台的话保持场景中显示的顶点数少于200K~3M,移动设备的话少于10W,一切取决于你的目标GPU与CPU. 2.如果你用U3D自带的SHADER,在表现不差的情况下选择Mobile或Unli ...

  6. 【mysql】mysql统计查询count的效率优化问题

    mysql统计查询count的效率优化问题 涉及到一个问题 就是 mysql的二级索引的问题,聚簇索引和非聚簇索引 引申地址:https://www.cnblogs.com/sxdcgaq8080/p ...

  7. php效率优化

    php效率优化 最近在公司一边自学一边写PHP程序,由于公司对程序的运行效率要求很高,而自己又是个新手,一开始就注意程序的效率很重要,这里就结合网上的一些资料,总结下php程序效率优化的一些策略:1. ...

  8. 浅谈自底向上的Shell脚本编程及效率优化

    作者:沐星晨 出处:http://blog.csdn.net/sosodream/article/details/6276758 浅谈自底向上的Shell脚本编程及效率优化 小论文,大家多批评指导:) ...

  9. QRowTable表格控件(三)-效率优化之-合理使用QStandardItem

    目录 一.开心一刻 二.概述 三.效果展示 四.QStandardItem 1.QStandardItem是什么鬼 2.性能分析 3.QStandardItem使用上的坑 五.相关文章 原文链接:QR ...

随机推荐

  1. kalman滤波(三)---各种滤波的方法汇总+优化的方法

    大神解答 一.前提 最一般的状态估计问题,我们会根据系统是否线性,把它们分为线性/非线性系统.同时,对于噪声,根据它们是否为高斯分布,分为高斯/非高斯噪声系统.现实中最常见的,也是最困难的问题,是非线 ...

  2. 自定义RPC框架--基于JAVA实现

    视频教程地址 DT课堂(原名颜群) 整体思路RPC(Remote Procedure Call),即远程过程调用.使用RPC,可以像使用本地的程序一样使用远程计算机上的程序.RPC使得开发分布式程序更 ...

  3. 28.Mysql权限与安全

    28.Mysql权限与安全28.1 Mysql权限管理 28.1.1 权限系统的工作原理对连接的用户进行身份认证,合法的用户通过认证,不合法的用户拒绝连接:对通过认证的合法用户赋予相应的权限,用户可以 ...

  4. 新版本wireshark tshark使用

    Wireshark-tshark wireshark 指令模式 => tshark Windows 及Linux 可至安裝目錄執行>tshark tshark.exe -i 7(利用-D找 ...

  5. 杨其菊201771010134《面向对象程序设计(java)》第一周学习总结

    第一部分:课程准备部分 填写课程学习 平台注册账号, 平台名称 注册账号 博客园:www.cnblogs.com 安迪儿 程序设计评测:https://pintia.cn/ 迷路的麋鹿回不来家了 代码 ...

  6. poj3130 (半平面交

    题意:判断是否存在内核. 半平面交存板子. /* gyt Live up to every day */ #include<cstdio> #include<cmath> #i ...

  7. node.js获取参数的常用方法

    1.req.body 2.req.query 3.req.params 一.req.body例子 body不是nodejs默认提供的,你需要载入body-parser中间件才可以使用req.body, ...

  8. ASM的一些小坑

    变量必需放到数据段,才有直接对地址赋值的访问权限 segment .data n1 dw 55h segment .text global _nasm_function _nasm_function: ...

  9. Codeforces791 C. Bear and Different Names

    C. Bear and Different Names time limit per test 1 second memory limit per test 256 megabytes input s ...

  10. Learning WCF:Life Cycle of Service instance

    示例代码下载地址:WCFDemo1Day 概述 客户端向WCF服务发出请求后,服务端会实例化一个Service对象(实现了契约接口的对象)用来处理请求,实例化Service对象以及维护其生命周期的方式 ...