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
  • 如果类型为“blittable”,则比较内存映像。如果它们相同,则返回
    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%的时候都是一模一样的