Hello World
Spiga

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

2013-04-10 22:21 by 老赵, 10779 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

33 条回复

  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

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

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

发表回复

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

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

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

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

使用Live Messenger联系我