C# – 值类型等于方法 – 为什么编译器使用reflection?

我只是遇到了一些非常奇怪的东西:当你在值类型上使用Equals()方法时(如果这个方法当然没有被覆盖)你会得到一些非常慢的东西 – 使用一对一的字段进行比较反思! 如:

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

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

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

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

对我来说很奇怪。

谢谢 :)

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

以下是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可能会在这里注入适当的代码。至于它为什么这么慢,我无法告诉你。

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

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

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

 public bool Equals(MyStruct obj) { return obj.i == i; } 

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

考虑到效果,我认为语言设计团队做得对。 对于初学者来说,从C ++中已知的Compilergenerated方法很难理解。 让我们看一下使用自动生成的struct.Equals在C#中会发生什么:

就像现在一样,.Equals()的概念很简单:

  • 每个结构都从ValueTypeinheritanceEquals。
  • 如果覆盖,则应用自定义Equals方法。

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

  • 每个结构都从ObjectinheritanceEquals。 (ValueType将不再实现自己的版本)
  • Object.Equals现在始终(!)覆盖,可以通过编译器生成的Equals方法或用户实现来覆盖

现在我们的struct有一个自动生成的覆盖方法,代码阅读器看不到! 那你怎么知道基础方法Object.Equals不适用于你的struct? 通过学习所有自动编译生成方法的案例。 这正是学习C ++的负担之一。

考虑将高效的struct Equals留给用户并保持概念简单,需要标准的默认Equals方法是一个很好的决定。

也就是说,性能关键结构应该超越Equals。 以下代码显示

在.Net 4.5.1上测量的3606 vs 53毫秒

这种性能提升肯定是由于避免虚拟等于,但无论如何,所以如果调用虚拟Object.Equals,则增益会低得多。 但是,性能关键案例不会调用Object.Equals,因此这里的增益将适用。

 using System; using System.Diagnostics; struct A { public int X; public int Y; } struct B : IEquatable { 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(); aX = 73; aY = 42; A aa = new A(); aX = 173; aY = 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(); bX = 73; bY = 42; B bb = new B(); bX = 173; bY = 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/