我只是遇到了一些非常奇怪的东西:当你在值类型上使用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
当然不是语言关键字,而是运行时方法......编译器完全不知道这种方法。所以在这里使用反射是有意义的。
答案 0 :(得分:10)
当它不需要时,它不使用反射。它只是在struct
的情况下逐位比较值,如果它可以这样做的话。但是,如果任何struct
成员(或成员的成员,任何后代)覆盖object.Equals
并提供其自己的实现,显然,它不能依赖于逐位比较来计算返回值。
速度慢的原因是Equals
的参数属于object
类型,且值类型必须加框以视为object
。拳击涉及在堆上分配内存和将值类型复制到该位置的内存。
您可以手动为Equals
方法提供重载,该方法将您自己的struct
作为参数来阻止装箱:
public bool Equals(MyStruct obj) {
return obj.i == i;
}
答案 1 :(得分: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;
}
如果可能,将进行逐位比较(注意CanCompareBits和FastEqualsCheck,两者都被定义为InternalCall.JIT可能会在这里注入适当的代码。至于为什么它如此慢,我无法'告诉你。
答案 2 :(得分:3)
编译器生成函数的想法是合理的。
考虑到效果,但我认为语言设计团队做得对。对于初学者来说,从C ++中已知的Compilergenerated方法很难理解。让我们看一下使用自动生成的struct.Equals在C#中会发生什么:
就像现在一样,.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<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/