IEqualityComparer <T>使用ReferenceEquals

Yur*_*rik 57 .net c# iequalitycomparer referenceequals

是否有使用的默认IEqualityComparer<T>实现ReferenceEquals

EqualityComparer<T>.Default使用ObjectComparer,它使用object.Equals().在我的例子中,对象已经实现IEquatable<T>,我需要忽略并仅通过对象的引用进行比较.

Yur*_*rik 56

为了防止没有默认实现,这是我自己的:

编辑280Z28:使用的理由RuntimeHelpers.GetHashCode(object),你们许多人可能以前从未见过.:)这个方法有两个效果,使它成为这个实现的正确调用:

  1. 当对象为null时,它返回0.由于ReferenceEquals适用于null参数,因此比较器的GetHashCode()实现也应如此.
  2. Object.GetHashCode()非虚拟地称呼.ReferenceEquals特别是忽略了任何覆盖Equals,因此GetHashCode()的实现应该使用与ReferenceEquals的效果匹配的特殊方法,这正是RuntimeHelpers.GetHashCode的用途.

[结束280Z28]

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

/// <summary>
/// A generic object comparerer that would only use object's reference, 
/// ignoring any <see cref="IEquatable{T}"/> or <see cref="object.Equals(object)"/>  overrides.
/// </summary>
public class ObjectReferenceEqualityComparer<T> : EqualityComparer<T>
    where T : class
{
    private static IEqualityComparer<T> _defaultComparer;

    public new static IEqualityComparer<T> Default
    {
        get { return _defaultComparer ?? (_defaultComparer = new ObjectReferenceEqualityComparer<T>()); }
    }

    #region IEqualityComparer<T> Members

    public override bool Equals(T x, T y)
    {
        return ReferenceEquals(x, y);
    }

    public override int GetHashCode(T obj)
    {
        return RuntimeHelpers.GetHashCode(obj);
    }

    #endregion
}
Run Code Online (Sandbox Code Playgroud)

  • 继承"EqualityComparer <T>"并不是一个好主意.静态成员"继承"真的令人困惑; 所以像这样重复使用"默认"并不是一个好主意.此外,虚拟方法比普通方法慢; 因为这可能是一个在紧密循环中使用的类型,为什么会增加不必要的开销呢?最后,根据你已经拥有的东西实现`IEqualityComparer`是微不足道的,那么为什么不直截了当地避免依赖呢? (5认同)
  • @EamonNerbonne无论如何,这些方法仍然是虚拟调用,因为该类型几乎是通过IEqualityComparer &lt;T&gt;接口专门使用的。一旦将方法放在vtable中,无论在哪里实现,调用开销都是相同的。 (3认同)

Ano*_*ken 16

我认为现在是时候将以前的答案实施更新到.Net4.0 +,由于IEqualityComparer<in T>界面的逆转,它变得非常规的简化:

using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

public sealed class ReferenceEqualityComparer
    : IEqualityComparer, IEqualityComparer<object>
{
    public static readonly ReferenceEqualityComparer Default
        = new ReferenceEqualityComparer(); // JIT-lazy is sufficiently lazy imo.

    private ReferenceEqualityComparer() { } // <-- A matter of opinion / style.

    public bool Equals(object x, object y)
    {
        return x == y; // This is reference equality! (See explanation below.)
    }

    public int GetHashCode(object obj)
    {
        return RuntimeHelpers.GetHashCode(obj);
    }
}
Run Code Online (Sandbox Code Playgroud)

现在只需要为所有引用相等性检查存在一个实例,而不是T像以前那样为每种类型存在一个实例.

您也可以通过不必T每次都要使用它来保存输入!


澄清那些不熟悉协方差和反演的概念......

class MyClass
{
    ISet<MyClass> setOfMyClass = new HashSet<MyClass>(ReferenceEqualityComparer.Default);
}
Run Code Online (Sandbox Code Playgroud)

......会工作得很好.这并不限定于例如HashSet<object>或相似的(在.Net4.0).


此外,任何人想知道为什么x == y是引用相等,这是因为==运营商是一个静态方法,这意味着它在编译时得到解决,并在编译时x和y型的object所以在这里解析为==运营商object-这是真正的参考平等方法.(实际上该Object.ReferenceEquals(object, object)方法只是重定向到对象equals运算符.)

  • 显式实现接口,将解决完全隐藏的问题。我想没有人会直接使用 `ReferenceEqualityComparer.Default.Equals(a, b)` 而不是 `ReferenceEquals(a, b)`。如果传递到一个期待`IEqualityComparer&lt;T&gt;`的地方 - 一切都会好起来的。 (2认同)

Dre*_*kes 7

这是C#6的简单实现.

public sealed class ReferenceEqualityComparer : IEqualityComparer, IEqualityComparer<object>
{
    public static ReferenceEqualityComparer Default { get; } = new ReferenceEqualityComparer();

    public new bool Equals(object x, object y) => ReferenceEquals(x, y);
    public int GetHashCode(object obj) => RuntimeHelpers.GetHashCode(obj);
}
Run Code Online (Sandbox Code Playgroud)

编辑(除非您对以下评论感兴趣,否则您不必阅读此内容)

@AnorZaken在new这里为修饰符的三个字母投入了许多段落.让我们总结一下.

单个定义的实例Equals(object,object)方法实现Equals此类型的两个声明接口的方法IEqualityComparer及其通用对应方法IEqualityComparer<object>.签名是相同的,因此该定义满足两个接口.

实例方法ReferenceEqualityComparer.Equals(object,object)隐藏静态 object.Equals(object,object)方法.

没有new编译器警告这一点.这究竟意味着什么?

这意味着如果要调用静态object.Equals方法,则无法在实例上调用它ReferenceEqualityComparer.这是一个大问题吗?

不,实际上这是理想的行为.这意味着,如果你想打电话,object.Equals(a,b)你不能通过代码这样做ReferenceEqualityComparer.Default.Equals(a,b).该代码显然正在请求引用相等 - 没有人会合理地期望它执行默认/值相等.你为什么不编码更明确的代码object.Equals(a,b)呢?因此,使用new提供了明智和理想的行为,并允许编译没有警告.

你怎么能抑制警告?如果使用#pragma warning disable 108/ #pragma warning restore 108然后结果与使用相同new,除非您为代码添加了更多噪声.new足以满足并更清楚地向他人解释意图.

或者,您可以对两个接口Equals方法使用显式实现,但是如果您使用了ReferenceEqualityComparer.Default.Equals(a,b),则根本不会具有引用相等性.

实际上,使用实例方法隐藏静态方法很少是一个问题,因为静态方法是从类型说明符而不是实例说明符中取消引用的.也就是说,你Foo.StaticMethod()没有使用new Foo().StaticMethod().从实例调用静态方法充其量是不必要的,在最坏的情况下是误导/错误的.

此外,对于相等比较器,您很少直接使用它们的具体类型.相反,您可以将它们与API(如集合)一起使用.

因此,尽管这是一个有趣且有时令人困惑的讨论,但它却毫无结果.