在.NET 4.0中,值类型的Equals方法默认实现是什么?

28
两个文档似乎在这个主题上存在矛盾: - ValueType.Equals Method 表示“Equals 方法的默认实现使用反射来比较 obj 和此实例的对应字段。” - Object.Equals Method (Object) 表示“Equals 的默认实现支持引用类型的引用相等性和值类型的按位相等性。”
那么是按位相等还是反射呢?
我瞥了一眼 ValueType 的源代码,并找到了一条注释,它说:
// 如果该对象中没有 GC 引用,我们可以避免反射 // 并进行快速的 memcmp 有人能解释一下什么是“GC 引用”吗?我猜它是一个具有引用类型的字段,但我不确定。
如果我创建一个只有值类型字段的 struct,它的实例是否总是以快速方式进行比较?
更新:.Net 4.5 的文档已经得到了显着改进:它不再存在矛盾,并且现在更好地理解默认值类型相等性检查的工作原理。

虽然这并不回答您的问题,但值类型应重写Equals和operator equals也值得注意。http://msdn.microsoft.com/en-us/library/ms182276.aspx - Timiz0r
我正要问同样的问题 ;) - Thomas Levesque
2个回答

45

System.ValueType.Equals方法很特殊。它按顺序执行以下步骤,直到得出结果:

  1. 如果进行比较的obj为'null',则返回false
  2. 如果thisobj参数是不同类型,则返回false
  3. 如果类型是“可平坦化的”,则比较内存图像。如果它们相同,则返回true
  4. 最后,它使用反射来调用每个值的配对实例字段的 Equals 方法进行比较。如果其中任何一个字段不相等,则返回false。否则返回true。请注意,它从不调用基础方法Object.Equals

因为它使用反射来比较字段,所以您应该在创建任何ValueType始终重写Equals方法。 反射很慢。

当它是“GCReference”或结构体中的字段是引用类型时,它最终会在每个字段上使用反射进行比较。 这是必需的,因为struct实际上具有指向堆上引用类型位置的指针。

如果结构体中没有使用引用类型,并且它们是相同类型,则保证字段的顺序相同,在内存中的大小也相同,因此可以直接比较内存。

对于只包含值类型字段的结构体,例如仅包含一个int字段的结构体,在比较时不会执行反射。 没有任何字段引用堆上的任何内容,因此没有GCReferenceGCHandle。 此外,该结构的任何实例都将具有字段的相同内存布局(有一些小例外),因此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的默认实现


是的,回答很好。你可以强调最后一个问题的答案是“是”。 - H H
1
所以,我假设,如果你知道你在做什么,例如,如果你只有int字段,你可以依赖默认实现。谢谢你的回答。 - Gebb
2
@Gebb - 你不必知道自己在做什么就能使用它。你可以依赖Microsoft的默认实现,但由于它不是标准的一部分,你可能会发现代码在Mono甚至.NET CE上的行为不同。此外,由于它不是标准规定的,行为可能会在未来发生变化。Microsoft建议你覆盖默认实现,这最终是最好的选择。无论你获得多少小的性能提升都是微不足道的,如果有的话也难以衡量。 - Christopher Currens
1
实际上,有人可以认为Equals的“默认”行为最终是正确的。例如,如果有一个计算成本很高的公式,并且希望在字典中缓存参数值和结果,则可能将0.0传递给公式可能会产生与传递-0.0不同的结果,因此字典应该将这些值视为不同。太糟糕了,没有干净的方法(据我所知)来测试两个浮点数、双精度或Decimal值是否“真正”相等。 - supercat
我不明白为什么他们没有让c#编译器或CLR自动向任何未明确指定的值类型添加合理的默认实现。99%的时间,每个值类型都是完全相同的样板文件。 - Mike Marynowski
@supercat 0.0f-0.0f 都返回哈希码 0,并且在 equals 方法中返回 true,因此根据我的观察,字典会将它们视为相同的键。 - Mike Marynowski

0

由于我不是这个领域的真正专家,我会直接表达我的想法:

文档(依据我的理解)指出,如果你的结构体有一个是对象(引用类型)的字段,则无法避免反射。

因此,如果你有以下内容:

    public struct SomeStruct
    {
        public object ObjectTest
    }

如果没有反射,ObjectTest就无法进行比较。所以我们需要使用反射。以下是本文的一部分,它好像在说我是对的:

"ValueType.Equals - Equals方法的默认实现使用反射来比较obj和该实例的对应字段。"


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接