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

Syl*_*gue 16 c# compiler-construction struct

我只是遇到了一些非常奇怪的东西:当你在一个值类型上使用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. */
      (...)
Run Code Online (Sandbox Code Playgroud)

我的问题:为什么C#编译器不生成比较值类型的简单方法?像(在MyStruct的定义中):

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

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

对我来说很奇怪.

谢谢 :)

补充:对不起,我只是意识到,当然,Equals它不是语言关键字而是运行时方法......编译器完全不知道这种方法.所以在这里使用反射是有意义的.

Meh*_*ari 10

它不使用反射时,它并不需要.它只是逐位比较值,struct如果它可以这样做.但是,如果任何struct成员(或成员的成员,任何后代)覆盖object.Equals并提供其自己的实现,显然,它不能依赖于逐位比较来计算返回值.

它变慢的原因是必须将Equals类型object和值类型的参数设置为框,以将其视为object.拳击涉及在堆上分配内存和将值类型复制到该位置的内存.

您可以手动为Equals方法提供重载,该方法将您自己的struct参数作为参数来阻止装箱:

public bool Equals(MyStruct obj) {
     return obj.i == i;
}
Run Code Online (Sandbox Code Playgroud)

  • 它在某些情况下使用反射.如果它检测到它只是blit结果,它会这样做 - 但如果字段中有引用类型(或包含引用类型的类型),它必须做一个更痛苦的过程. (4认同)
  • 西尔万:他们是对的.正如Jon所说,如果struct包含引用类型作为成员,它必须在该字段上调用Equals.我更新了答案以反映这一点.我试图提出的一点是,当它不需要时它不使用反射(就像你的例子). (2认同)
  • 我不得不说,我不明白为什么有参考时不能进行逐位比较.如果两个引用指向同一个对象,那么指针何时不完全相等? (2认同)
  • @Snarfblam:从Mehrdad发布的代码中,似乎它在结构中包含的每个引用对象上调用Equals - 几乎是递归的.如果两个对象相等但它们的引用不相同,则逐位比较将失败. (2认同)

sna*_*arf 10

以下是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;
}
Run Code Online (Sandbox Code Playgroud)

如果可能的话,将进行逐位比较(注意CanCompareBits和FastEqualsCheck,两者都被定义为InternalCall.JIT可能会在这里注入适当的代码.至于它为什么这么慢,我不能告诉你.

  • 我想知道如果运行时为任何尚未定义的结构自动生成`Equals`覆盖,是否存在任何兼容性问题:`bool Equals(object other){return StructComparer <thisType> .EqualsProc(ref这个,其他); ``,其中`EqualsProc`是静态类`StructComparer <thisType>`中的静态委托字段?这种方法可以避免每次比较对象时都必须使用Reflection,并且还可以避免装箱步骤. (3认同)