【问题标题】:In .NET 4.0, What is the default implementation of Equals for value types?在 .NET 4.0 中,值类型的 Equals 的默认实现是什么?
【发布时间】:2012-01-09 01:57:33
【问题描述】:

这两个文档页面似乎在这个主题上相互矛盾:

那么是按位相等还是反射?

看了ValueType的源码,发现有评论说

//如果这个对象没有GC引用我们可以避免反射

// 做一个快速的 memcmp

有人能解释一下“GC 引用”是什么意思吗?我猜这是一个具有引用类型的字段,但我不确定。

如果我创建一个只有值类型字段的struct,它的实例是否总是以快速的方式进行比较?

更新:.Net 4.5 的文档得到了显着改进:它没有提到的矛盾,现在可以更好地理解默认值类型相等检查的工作原理。

【问题讨论】:

标签: c# .net clr


【解决方案1】:

System.ValueType.Equals 很特别。它按顺序执行以下步骤,直到得到一些结果:

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

因为它使用反射来比较字段,所以您应该始终覆盖 Equals 在您创建的任何ValueType 上。反射很慢。

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

如果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的默认实现

【讨论】:

  • 是的,很好的答案。您可以强调最后一个问题的答案是“是”。
  • 所以,我假设,如果您知道自己在做什么,例如,如果您只有 int 字段,则可以依赖默认实现。谢谢你的回答。
  • @Gebb - 你不必知道你在做什么来使用它。您可以依赖 Microsoft 的 默认实现,但由于它不是标准的一部分,您可能会发现代码在 Mono 甚至 .NET CE 上的行为有所不同。此外,这种行为将来可能会发生变化,因为无论如何它都不是由标准决定的。 Microsoft 建议您覆盖默认实现,这最终是最佳选择。无论您获得什么微小的性能提升,即使可以衡量,也可以忽略不计。
  • 实际上,有人可能会争辩说Equals 的“默认”行为最终是正确的。例如,如果有一个计算成本很高的公式,并且希望将参数值和结果缓存在字典中,则将0.0 传递给公式可能会产生与传递-0.0 不同的结果,因此字典应该将这些值视为不同的。太糟糕了,没有干净的方法(据我所知)来测试两个 float、double 或 Decimal 值是否“真的”相等。
  • 我不明白他们为什么不让 c# 编译器或 CLR 自动为任何未明确指定的值类型添加合理的默认实现。 99% 的情况下,每种值类型都一遍又一遍地使用完全相同的样板。
【解决方案2】:

我不是该领域的真正专家,我会继续提出我的想法: 文档(据我说)指出,如果您的结构有一个对象(引用类型)字段,则反射无法避免。

如果你有以下情况:

    public struct SomeStruct
    {
        public object ObjectTest
    }

没有反射就无法比较 ObjectTest。所以会用到反射。这部分文字似乎在说我是对的:

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

【讨论】:

    猜你喜欢
    • 2015-04-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-08
    • 1970-01-01
    • 2021-11-18
    相关资源
    最近更新 更多