代码之家  ›  专栏  ›  技术社区  ›  Sylvain Rodrigue

C-值类型等于方法-为什么编译器使用反射?

  •  15
  • Sylvain Rodrigue  · 技术社区  · 15 年前

    我刚刚遇到了一些很奇怪的事情:当你对一个值类型使用equals()方法时(当然,如果这个方法没有被重写的话),你会得到一些 非常非常 慢——使用反射将字段与一个字段进行比较!如:

    public struct MyStruct{
       int i;
    }
    
       (...)
    
       MyStruct s, t;
       s.i = 0;
       t.i = 1;
       if ( s.Equals( t ))   /*  s.i will be compared to t.i via reflection here. */
          (...)
    

    我的问题是:为什么C编译器不生成一个简单的方法来比较值类型?比如(在mystrut的定义中):

       public override bool Equals( Object o ){
          if ( this.i == o.i )
             return true;
          else
             return false;
       }
    

    编译器在编译时知道mystrut的字段是什么,为什么要等到运行时才能枚举mystrut字段?

    我觉得很奇怪。

    谢谢)

    补充 对不起,我才意识到,当然, Equals 不是语言关键字,而是运行时方法…编译器完全不知道这个方法。所以在这里用反射来感知。

    3 回复  |  直到 7 年前
        1
  •  10
  •   snarf    15 年前

    以下是mscorlib中解压缩的valuetype.equals方法:

    public override bool Equals(object obj)
    {
        if (obj == null)
        {
            return false;
        }
        RuntimeType type = (RuntimeType) base.GetType();
        RuntimeType type2 = (RuntimeType) obj.GetType();
        if (type2 != type)
        {
            return false;
        }
        object a = this;
        if (CanCompareBits(this))
        {
            return FastEqualsCheck(a, obj);
        }
        FieldInfo[] fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
        for (int i = 0; i < fields.Length; i++)
        {
            object obj3 = ((RtFieldInfo) fields[i]).InternalGetValue(a, false);
            object obj4 = ((RtFieldInfo) fields[i]).InternalGetValue(obj, false);
            if (obj3 == null)
            {
                if (obj4 != null)
                {
                    return false;
                }
            }
            else if (!obj3.Equals(obj4))
            {
                return false;
            }
        }
        return true;
    }
    

    在可能的情况下,将进行一点明智的比较(注意canCompareBits和fasteQualscheck,这两个都定义为InternalCall)。JIT可能会在这里注入适当的代码。至于为什么这么慢,我不能告诉你。

        2
  •  10
  •   Mehrdad Afshari    15 年前

    它不使用反射 当它不需要的时候 . 它只是一点一点地比较数值,以防 struct 如果可以的话。但是,如果 结构 成员(或成员成员、任何子代成员)重写 object.Equals 并提供了自己的实现,显然,它不能依靠逐位比较来计算返回值。

    速度慢的原因是 Equals 属于类型 object 和值类型必须装箱才能作为 对象 . 装箱涉及到在堆上分配内存,以及将值类型复制到该位置的内存。

    您可以手动为 等于 你自己的方法 结构 作为防止装箱的参数:

    public bool Equals(MyStruct obj) {
         return obj.i == i;
    }
    
        3
  •  3
  •   citykid    7 年前

    编译器生成函数的想法是合理的。

    考虑到效果,但是我认为语言设计团队做得对。C++中已知的编译生成器方法对于初学者来说是很难理解的。让我们看看自动生成的结构在C中会发生什么。等于:

    现在,.equals()的概念很简单:

    • 每个结构从valuetype继承等于。
    • 如果重写,则应用自定义等于方法。

    如果编译器总是创建equals方法,我们可以有:

    • 每个结构从对象继承等于。(ValueType将不再实现自己的版本)
    • object.equals现在始终是(!)由编译器生成的equals方法或由用户实现重写

    现在我们的结构有一个代码阅读器看不到的自动生成的重写方法!那么,您如何知道基方法object.equals不适用于您的结构呢?通过学习自动编译器生成方法的所有情况。这正是学习C++的负担之一。

    如果让高效结构等于用户并保持概念简单,需要一个标准的默认等于方法,那么将是一个明智的决定。

    也就是说,性能关键结构应该重写equals。下面的代码显示

    三千六百零六 VS 53毫秒 在.NET 4.5.1上测量

    这种性能的提高当然是由于避免了虚拟的等号,但是无论如何,如果虚拟的object.equals被称为增益会低得多。性能关键的情况不会调用object.equals,但是这里的增益将适用。

    using System;
    using System.Diagnostics;
    
    struct A
    {
        public int X;
        public int Y;
    }
    
    struct B : IEquatable<B>
    {
        public bool Equals(B other)
        {
            return this.X == other.X && this.Y == other.Y;
        }
    
        public override bool Equals(object obj)
        {
            return obj is B && Equals((B)obj);
        }
    
        public int X;
        public int Y;
    }
    
    
    class Program
    {
        static void Main(string[] args)
        {
            var N = 100000000;
    
            A a = new A();
            a.X = 73;
            a.Y = 42;
            A aa = new A();
            a.X = 173;
            a.Y = 142;
    
            var sw = Stopwatch.StartNew();
            for (int i = 0; i < N; i++)
            {
                if (a.Equals(aa))
                {
                    Console.WriteLine("never ever");
                }
            }
            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
    
            B b = new B();
            b.X = 73;
            b.Y = 42;
            B bb = new B();
            b.X = 173;
            b.Y = 142;
    
            sw = Stopwatch.StartNew();
            for (int i = 0; i < N; i++)
            {
                if (b.Equals(bb))
                {
                    Console.WriteLine("never ever");
                }
            }
            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
        }
    }
    

    也见 http://blog.martindoms.com/2011/01/03/c-tip-override-equals-on-value-types-for-better-performance/