C# 在.NET4.0中,值类型的Equals的默认实现是什么?
在这个主题上,两个文档页似乎相互矛盾:C# 在.NET4.0中,值类型的Equals的默认实现是什么?,c#,.net,clr,C#,.net,Clr,在这个主题上,两个文档页似乎相互矛盾: 表示“Equals方法的默认实现使用反射来比较obj和该实例的相应字段。” 表示“Equals的默认实现支持引用类型的引用相等,以及值类型的位相等” 那么它是位相等还是反射 我浏览了一下ValueType的源代码,发现一条评论说 //如果此对象中没有GC引用,则可以避免反射 //然后做一个快速的memcmp 有人能澄清“GC参考”是什么意思吗?我猜这是一个有引用类型的字段,但我不确定 如果我创建了一个只包含值类型字段的struct,它的实例是否总是以快
- 表示“Equals方法的默认实现使用反射来比较obj和该实例的相应字段。”
- 表示“Equals的默认实现支持引用类型的引用相等,以及值类型的位相等”
ValueType
的源代码,发现一条评论说
//如果此对象中没有GC引用,则可以避免反射
//然后做一个快速的memcmp
有人能澄清“GC参考”是什么意思吗?我猜这是一个有引用类型的字段,但我不确定
如果我创建了一个只包含值类型字段的struct
,它的实例是否总是以快速方式进行比较
更新:对.Net 4.5的文档进行了显著改进:它没有上述矛盾,现在可以更好地理解默认值类型相等性检查的工作原理。非常特殊。它按顺序执行以下步骤,直到得到一些结果:
obj
为“null”,则返回false
this
和obj
参数的类型不同,则返回false
true
李>
Equals
成对的实例字段。如果其中任何字段不相等,则返回false
。否则返回true
。请注意,它从不调用基本方法,Object.Equals
ValueType
上的Equals
。反射很慢
当它是一个“GCReference”或结构中的一个字段是引用类型时,它会在每个字段上使用反射来进行比较。它必须这样做,因为struct
实际上有一个指向堆上引用类型位置的指针
如果结构中没有使用引用类型,并且它们是相同的类型,则保证字段的顺序相同,并且在内存中大小相同,因此它可以只比较裸内存
对于仅具有字段值类型的结构,即仅具有一个int
字段的结构,在比较过程中不会进行反射。没有任何字段引用堆上的任何内容,因此没有GCReference
或GCHandle
。此外,此结构的任何实例都将具有相同的字段内存布局(只有少数例外),因此CLR团队可以执行直接内存比较(memcmp),这比其他选项快得多
因此,是的,如果您的结构中只有值类型,那么它将更快地执行memcmp,而不是反射比较,但您可能不想这样做。继续读
这并不意味着您应该使用默认的Equals
实现。事实上,不要这样做。住手。它进行位比较,这并不总是准确的。你说什么?让我告诉你:
private struct MyThing
{
public float MyFloat;
}
private static void Main(string[] args)
{
MyThing f, s;
f.MyFloat = 0.0f;
s.MyFloat = -0.0f;
Console.WriteLine(f.Equals(s)); // prints False
Console.WriteLine(0.0f == -0.0f); // prints True
}
这些数字在数学上是相等的,但在二进制表示中却不相等。因此,我要再次强调,不要依赖ValueType的默认实现。Equals不是这一领域的真正专家,我只想继续阐述我的想法: 文档(根据我的说法)指出,如果您的结构具有对象(引用类型)字段,则无法避免反射 因此,如果您有以下情况:
public struct SomeStruct
{
public object ObjectTest
}
ObjectTest无法在没有反射的情况下进行比较。因此,将使用反射。这部分文字似乎在说我是对的:
“ValueType.Equals-Equals方法的默认实现使用反射来比较obj和此实例的相应字段。”
虽然这并不能回答您的问题,但值得注意的是,值类型应该覆盖Equals和运算符Equals。我正要问同样的问题;)是的,回答得好。你可以强调最后一个问题的答案是“是”。因此,我假设,如果你知道你在做什么,例如,如果你只有int字段,你可以依赖默认的实现。谢谢你的回答。@Gebb-你不必知道你在做什么来使用它。您可以依赖Microsoft的默认实现,但由于它不是标准的一部分,您可能会发现Mono甚至.NET CE上的代码行为有所不同。此外,这种行为在未来可能会改变,因为无论如何,标准并不是一成不变的。Microsoft建议您覆盖默认实现,这最终是最佳选择。无论你得到什么小小的性能提升,如果可以测量的话,都是可以忽略不计的。事实上,有人可能会认为
的“默认”行为最终是正确的。例如,如果有一个计算成本很高的公式,并且希望将参数值和结果缓存到字典中,则将0.0
传递到公式可能会产生与传递-0.0
不同的结果,因此字典应将这些值视为不同的。太糟糕了,就我所知,没有干净的方法来测试两个浮点、双精度或十进制
值是否“真的”相等。我不明白为什么他们不让c#编译器或CLR自动为任何没有显式指定值类型的值类型添加合理的默认实现。99%的时候都是一模一样的