那么是按位相等还是反射呢?
我瞥了一眼 ValueType 的源代码,并找到了一条注释,它说:
// 如果该对象中没有 GC 引用,我们可以避免反射 // 并进行快速的 memcmp 有人能解释一下什么是“GC 引用”吗?我猜它是一个具有引用类型的字段,但我不确定。
如果我创建一个只有值类型字段的 struct,它的实例是否总是以快速方式进行比较?
更新:.Net 4.5 的文档已经得到了显着改进:它不再存在矛盾,并且现在更好地理解默认值类型相等性检查的工作原理。
System.ValueType.Equals
方法很特殊。它按顺序执行以下步骤,直到得出结果:
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的默认实现
Equals
的“默认”行为最终是正确的。例如,如果有一个计算成本很高的公式,并且希望在字典中缓存参数值和结果,则可能将0.0
传递给公式可能会产生与传递-0.0
不同的结果,因此字典应该将这些值视为不同。太糟糕了,没有干净的方法(据我所知)来测试两个浮点数、双精度或Decimal
值是否“真正”相等。 - supercat0.0f
和 -0.0f
都返回哈希码 0
,并且在 equals 方法中返回 true
,因此根据我的观察,字典会将它们视为相同的键。 - Mike Marynowski由于我不是这个领域的真正专家,我会直接表达我的想法:
文档(依据我的理解)指出,如果你的结构体有一个是对象(引用类型)的字段,则无法避免反射。
因此,如果你有以下内容:
public struct SomeStruct
{
public object ObjectTest
}
如果没有反射,ObjectTest就无法进行比较。所以我们需要使用反射。以下是本文的一部分,它好像在说我是对的:
"ValueType.Equals - Equals方法的默认实现使用反射来比较obj和该实例的对应字段。"