Hello World
Spiga

防止装箱落实到底,只做一半也是失败

2013-04-10 22:21 by 老赵, 11710 visits

.NET提供struct类型,正确使用可以减少对象数量,从而降低GC压力,提高性能。不过有时候我会发现,某些同学有这方面的意识,但是有时候一疏忽一偷懒,就没有得到相应的效果了。这里举一个真实的例子:假设我们要将一对int作为字典的键,用于映射到某些数据,那么你会怎么做?当然我们可以直接使用Tuple<int, int>,但这样就可能产生大量的对象。于是我们打算使用自定义的值类型:

private struct MyKey {
    private readonly int _a;
    private readonly int _b;

    public MyKey(int a, int b) {
        _a = a;
        _b = b;
    }
}

这么做正确吗?假如你做一下测试,会发现它已经可以“正确使用”了,但实际上还是错误的。我们用它来做字典的键,会依赖GetHashCodeEquals两个方法,由于MyKey没有提供这两个方法,就会自动使用System.ValueType里的实现,这便引起了装箱。

好吧,那么我们就来实现一下:

private struct MyKey {
    // ...

    public override int GetHashCode() {
        // ...
    }

    public override bool Equals(object that) {
        // ...
    }
}

那么现在呢?可能现在您就会比较容易意识到,即便GetHashCode已经没有问题了,但是Equals方法还是会引起装箱,因为that参数依然是object类型。

怎么破?当然有办法,因为像HashSet<T>或是Dictionary<TKey, TValue>集合其实都不会直接调用GetHashCodeEquals方法,都是通过一个IEqualityComparer<T>对象来委托调用的:

public interface IEqualityComparer<in T> {
    bool Equals(T x, T y);
    int GetHashCode(T obj);
}

假如在创建集合的时候没有提供比较器,则会使用默认的EqualityComparer<T>.Default对象,它的构造方法是这样的:

private static EqualityComparer<T> CreateComparer<T>() {
    Contract.Ensures(Contract.Result<EqualityComparer<T>>() != null);

    RuntimeType t = (RuntimeType)typeof(T);
    // Specialize type byte for performance reasons 
    if (t == typeof(byte)) {
        return (EqualityComparer<T>)(object)(new ByteEqualityComparer());
    }

    // If T implements IEquatable<T> return a GenericEqualityComparer<T>
    if (typeof(IEquatable<T>).IsAssignableFrom(t)) {
        return (EqualityComparer<T>)RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter(
                (RuntimeType)typeof(GenericEqualityComparer<int>), t);
    }

    // If T is a Nullable<U> where U implements IEquatable<U> return a NullableEqualityComparer<U>
    if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>)) {
        RuntimeType u = (RuntimeType)t.GetGenericArguments()[0];
        if (typeof(IEquatable<>).MakeGenericType(u).IsAssignableFrom(u)) {
            return (EqualityComparer<T>)RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter(
                    (RuntimeType)typeof(NullableEqualityComparer<int>), u);
        }
    }

    // If T is an int-based Enum, return an EnumEqualityComparer<T>
    // See the METHOD__JIT_HELPERS__UNSAFE_ENUM_CAST and METHOD__JIT_HELPERS__UNSAFE_ENUM_CAST_LONG cases in getILIntrinsicImplementation 
    if (t.IsEnum && Enum.GetUnderlyingType(t) == typeof(int)) {
        return (EqualityComparer<T>)RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter(
                (RuntimeType)typeof(EnumEqualityComparer<int>), t);
    }

    // Otherwise return an ObjectEqualityComparer<T> 
    return new ObjectEqualityComparer<T>();
}

可以看出,根据不同的情况它会使用各式不同的比较器。其中最适合我们的自然就是实现IEquatable<T>接口的分支了。于是我们可以这么做:

struct MyKey : IEquatable<MyKey> {
    // ...

    public bool Equals(MyKey that) {
        // ...
    }
}

这才是最终符合我们要求的做法。

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

38 条回复

  1. 老赵
    admin
    链接

    老赵 2013-04-11 10:50:00

    隐藏了某Go粉(Gopher的音译)冲上来嗷嗷骂所带来的一系列无关评论,需要了解其细节的同学可以登录后阅读。

  2. 链接

    好好狸 2013-04-17 01:17:09

    看到最后,觉得这种 style 真像 C++ 的一个 pattern , CRTP。当然 C# 的泛型借鉴了不少 C++ 的

  3. 猫咪
    122.10.133.*
    链接

    猫咪 2013-04-18 13:30:36

    Wind.js的官网 文档 很不全面,那几个示例 只是列举了,异步编程的一两种情况,但我相信 wind.js支持的并不值这几种,而且好多链接本应该在网站导航里就应该出现,但要在文章里才能找到, ,$await有很多功能,但我找不到可以更多的示例用来学习,而且 wind.js的讨论组 我也进不去(GFW的原因吧),希望 能出一个 系统的 使用指南。

  4. 链接

    阿斯顿 2013-04-26 13:30:18

    隐藏功能不错

  5. kyo_lynn
    123.177.18.*
    链接

    kyo_lynn 2013-05-15 15:55:27

    不错,开发中经常会遇到需要两个或多个基本类型作为字典的key来处理的情况。之前从来没用过struct,以后可尝试用一下。

  6. 链接

    2013-05-17 14:17:20

    老赵,ie10(win7)非兼容模式下,登陆liveid,貌似无法正常跳转.

  7. 链接

    Holly Yang 2013-05-17 17:59:51

    只是浅薄 没有看明白 文章

    private struct MyKey {
        private readonly int _a;
        private readonly int _b;
    
        public MyKey(int a, int b) {
            _a = a;
            _b = b;
        }
    }
    

    这个类 能做什么 给装箱有啥关系 不太明白

    望指教

  8. 链接

    Haart 2013-05-20 12:55:41

    @Helly Yang

    这个不是类,是结构。结构是值类型,类是引用类型。 只有值类型才存在装箱。

  9. gsralex
    123.6.84.*
    链接

    gsralex 2013-06-14 20:37:35

    IEquatable遇到非T类型还是会装箱,即使是int之类,不过已经是极限状态了。

  10. gsralex
    123.6.84.*
    链接

    gsralex 2013-06-14 20:38:01

    貌似Gavatar被封了。。。

  11. 老赵
    admin
    链接

    老赵 2013-06-14 21:46:39

    @gsralex

    怎么会遇到非T类型?泛型保证类型安全的。

  12. gsralex
    123.6.84.*
    链接

    gsralex 2013-06-14 22:09:57

    public class Class1
    {
        int intValue;
        MyKey myKeyValue;
        public void test()
        {
            myKeyValue.Equals(intValue);
        }
    }
    
    struct MyKey : IEquatable<MyKey>
    {
        // ...
    
        public bool Equals(MyKey that)
        {
            // ...
            return true;
        }
    }
    
  13. 老赵
    admin
    链接

    老赵 2013-06-15 00:41:54

    @gsralex

    拜托搞清楚现在是什么情况好不好,你怎么不直接写object o = new MyKey()啊?

  14. gsralex
    123.6.84.*
    链接

    gsralex 2013-06-15 08:18:02

    呵呵,硬挑毛病来的

  15. 老赵
    admin
    链接

    老赵 2013-06-16 18:27:46

    @gsralex

    绝对比鸡蛋里挑骨头还挑骨头的那种瞎挑……

  16. 小红
    114.254.116.*
    链接

    小红 2013-06-23 22:43:52

    楼主 有交流群吗

  17. 链接

    涛 吴 2013-07-25 16:22:57

    隐藏的评论更精彩

  18. 老赵
    admin
    链接

    老赵 2013-07-26 23:25:02

    @涛 吴

    还真有人看,真不错,哈哈。

  19. Gavin.Z
    221.12.171.*
    链接

    Gavin.Z 2013-08-06 16:11:16

    隐藏的评论更精彩

  20. 链接

    mis dibowe 2013-12-02 15:25:39

    以前对技术是梦寐以求,现在觉得其实也就那样,最主要的还是生活,想想还是做管理好一些。

  21. keyle_xiao
    222.65.16.*
    链接

    keyle_xiao 2014-01-20 17:52:12

    感觉写的都很细 lz用心了

  22. 链接

    Kwaiming Cheung 2014-02-06 10:15:40

    看了隐藏评论了!其实我觉得老赵也别太愤世嫉俗了!哈哈!什么人都有,免得气坏自己。我是很尊敬一些能把技术分享出来的人,即使不是大神也是一些很棒的普通人,很讨厌一些有语言信仰的人,只会无脑喷,这样只会害死自己,那些学面向对象的人一点都不懂得面向对象的思想,却还在编程。他们不知道是不知道还是忘了一个场景的对象只是一个实例,并不是对象的全部。有些时候在一些场景适用,一些场景不适用很正常。没有什么优秀的而庞大的系统是能够靠一门语言就能出来的。

  23. 阿亮
    220.176.242.*
    链接

    阿亮 2014-02-27 22:07:39

    呵呵,简单的地方能体现出经验和水平。我也打算玩c#玩到60岁,好玩,写的代码就像画一个艺术品一样好玩。

  24. 链接

    Raezzium 2014-05-08 22:54:41

    赵兄,F#中,我实现了IEquatable接口,为什么使用“=”比较两个结构的时候,还是调用Object.Equals?这有办法破解吗?F#中无法重载"="

  25. 绝命吹水师
    203.100.82.*
    链接

    绝命吹水师 2015-06-17 21:49:36

    试试登录~ 哥们,我按你说的多写了。

  26. droggo
    136.158.30.*
    链接

    droggo 2021-01-10 22:52:14

    Thank you for sharing this. This is very interesting. I’ve read many articles about this. But not as detailed like this. Hopefully people continue to post something like this. While I was searching I found this free survival games for pc download And also this https://among-us.io/

  27. zoroseo2020
    35.241.103.*
    链接

    zoroseo2020 2021-03-17 17:55:28

    所有的成功,都来自于不倦的努力和奔跑福彩双色球;所有幸福,都来自平凡的奋斗和坚持澳洲幸运20,你无法找到捷径。

  28. rencao
    34.92.139.*
    链接

    rencao 2022-07-23 13:41:28

    SG飞艇国首相特蕾莎·梅访华期间,作SG时时彩为外交大臣的约翰逊表示他将致力于加强英中贸易联系,并为英国服务业向中国出口和中国在英投资开拓幸运时时彩更广泛的渠道

  29. kericnnoe
    111.242.194.*
    链接

    kericnnoe 2022-12-21 04:06:34

    如果發現大多數沙龍娛樂城的玩家都下注同一個盤口時,那這時候就可以先避一避不要下注

  30. amel
    35.215.165.*
    链接

    amel 2023-07-28 11:40:05

    NET提供struct类型,正确使用可以减少对象数量,[url][url]幸运飞艇走势图https://1687580.com/view/xingyft/pk10kai.html[/url][/url] [url][url]双色球开奖网https://1687580.com/view/fcssq/index.html[/url][/url]从而降低GC压力,提高性能。不过有时候我会发现,某些同学有这方面的意识,但是有时候一疏忽一偷懒,就没有得到相应的效果了。这里举一个真实的例子:假设我们要将一对int作为字典的键,用于映射到某些数据,那么你会怎么做?当然我们可以直接使用Tuple,但这样就可能产生大量的对象。于是我们打算使用自定义的值类型:

已自动隐藏某些不合适的评论内容(主题无关,争吵谩骂,装疯卖傻等等),如需阅读,请准备好眼药水并点此登陆后查看(如登陆后仍无法浏览请留言告知)。

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我