IEqualityComparer <T>使用ReferenceEquals
有没有使用ReferenceEquals
的默认IEqualityComparer<T>
实现?
EqualityComparer<T>.Default
使用ObjectComparer,它使用object.Equals()
。 在我的情况下,对象已经实现了IEquatable<T>
,我只需要忽略和比较对象的引用。
以防万一没有默认的实现,这是我自己的:
编辑280Z28:使用RuntimeHelpers.GetHashCode(object)
理由,你可能以前没有见过。 :)这个方法有两个效果,使它成为这个实现的正确调用:
- 当对象为空时,它返回0。 由于
ReferenceEquals
适用于null参数,所以比较器的GetHashCode()实现也是如此。 - 它非虚拟地调用
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 }
我认为现在是时候把以前的答案更新到.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); } }
现在只需要为所有引用相等检查存在一个实例,而不是每个typesT
都有一个实例,就像以前一样。
你也可以节省打字时间,而不必在每次使用时都指定T
!
澄清那些不熟悉协方差和反variables概念的人
class MyClass { ISet<MyClass> setOfMyClass = new HashSet<MyClass>(ReferenceEqualityComparer.Default); }
…会工作得很好。 这不限于例如HashSet<object>
或类似的(在.Net4.0中)。
对于任何想知道为什么x == y
是引用相等的人,这是因为==
运算符是一个静态方法,这意味着它在编译时被parsing,而在编译时x和y是typesobject
所以在这里parsing为object
的==
运算符 – 这是真正的引用等式方法。 (实际上, Object.ReferenceEquals(object, object)
方法只是一个redirect到object对象的操作符。)
这是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); }
编辑 (你不必读这个,除非你有兴趣在下面的评论)
@AnorZaken在这里为new
修饰语的三个字母写了许多段落。 我们总结一下。
单个定义的实例Equals(object,object)
方法实现了这个types的两个声明接口的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)
呢? object.Equals(a,b)
呢? 所以new
的使用提供了明智的和理想的行为,并且允许编译而不用警告。
你还可以怎样压制这个警告? 如果您使用#pragma warning disable 108
/ #pragma warning restore 108
则结果与使用new
的结果相同,只是您已向代码中添加了更多噪声。 new
足够,并更清楚地解释其他人的意图。
或者,你可以使用显式实现的两个接口Equals
方法,但是如果你使用了ReferenceEqualityComparer.Default.Equals(a,b)
你根本就没有引用的相等性。
实际上,使用实例方法隐藏静态方法几乎不是问题,因为静态方法是从types说明符中取消引用的,而不是实例说明符。 也就是说,你使用Foo.StaticMethod()
而不是new Foo().StaticMethod()
。 从实例中调用静态方法是不必要的,最糟糕的是误导/不正确。
而且,对于相等比较器,您很less直接使用它们的具体types。 而是将它们与API(如集合)一起使用。
所以虽然这是一个有趣的,有时令人困惑的讨论,但它是没有结果的。