我刚刚遇到了一些对我来说非常奇怪的事情:当你在值类型上使用 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# 编译器不生成一个简单的方法来比较值类型?类似于(在 MyStruct 的定义中):
public override bool Equals( Object o ){
if ( this.i == o.i )
return true;
else
return false;
}
编译器在编译时就知道 MyStruct 的字段是什么,为什么要等到运行时才枚举 MyStruct 字段?
对我来说很奇怪。
谢谢:)
ADDED:抱歉,我刚刚意识到,当然,
Equals
不是一个语言关键字,而是一个运行时方法......编译器完全不知道这个方法。所以在这里使用反射是有意义的。
以下是来自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 可能会在这里注入适当的代码。至于为什么这么慢,我无法告诉你。
当不需要时,它不使用反射。它只是一点一点地比较值,以防struct
(如果可以的话)。但是,如果任何
struct
成员(或成员的成员、任何后代)重写
object.Equals
并提供自己的实现,显然,它不能依赖于逐位比较来计算返回值。速度慢的原因是
Equals
的参数是
object
类型,并且值类型必须装箱才能被视为
object
。装箱涉及在堆上分配内存以及将值类型复制到该位置的内存。您可以手动为
Equals
方法提供重载,该方法将您自己的
struct
作为参数以防止装箱:
public bool Equals(MyStruct obj) {
return obj.i == i;
}
考虑到效果,我认为语言设计团队做得对。 C++ 中编译器生成的方法对于初学者来说很难理解。让我们看看在 C# 中使用自动生成的 struct.Equals 会发生什么:
现在,.Equals() 的概念很简单:
将高效的结构 Equals 留给用户并保持概念简单,需要一个标准的默认 Equals 方法,这是一个很好的决定。
也就是说,性能关键结构应该覆盖 Equals。下面的代码显示了
3606 与 53 毫秒 在 .Net 4.5.1 上测量
这种性能增益肯定是由于避免了虚拟 Equals,但无论如何,如果调用虚拟 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/