IEqualityComparer qui utilise ReferenceEquals
-
19-09-2019 - |
Question
Y at-il une implémentation IEqualityComparer<T>
par défaut qui utilise ReferenceEquals
?
EqualityComparer<T>.Default
utilise ObjectComparer, qui utilise object.Equals()
. Dans mon cas, les objets implémentent déjà IEquatable<T>
, que je dois ignorer et comparer par la référence de l'objet uniquement.
La solution
Juste au cas où il n'y a pas implémentation par défaut, ceci est mon propre:
Modifier par 280Z28: Justification de l'utilisation RuntimeHelpers.GetHashCode(object)
, dont beaucoup vous avez probablement jamais vu auparavant. :) Cette méthode a deux effets qui en font le correct appel à cette mise en œuvre:
- Il retourne 0 quand l'objet est nul. Depuis fonctionne
ReferenceEquals
pour les paramètres nuls, donc si la mise en œuvre du comparateur de GetHashCode (). - Il appelle
Object.GetHashCode()
non virtuellement.ReferenceEquals
ne tient pas compte spécifiquement des redéfinitionsEquals
, de sorte que la mise en œuvre de GetHashCode () devrait utiliser une méthode spéciale qui correspond à l'effet de ReferenceEquals, ce qui est exactement ce que RuntimeHelpers.GetHashCode est pour.
[end 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
}
Autres conseils
Je pensais qu'il était temps de mettre à jour les réponses précédentes à la mise en œuvre .Net4.0 + où il simplifie en devenant grâce non générique contravariance sur l'interface 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);
}
}
a besoin là que pour exister une instance pour tous vos vérifiait au lieu d'un pour chaque T
de type référence l'égalité comme ce fut le cas avant.
vous enregistrez également la saisie de ne pas avoir à spécifier T
chaque fois que vous voulez utiliser!
Pour préciser pour ceux qui ne sont pas familiers avec les concepts de Covariance et Contravariance .. .
class MyClass
{
ISet<MyClass> setOfMyClass = new HashSet<MyClass>(ReferenceEqualityComparer.Default);
}
... fonctionnera très bien. Ceci est pas limité par exemple HashSet<object>
ou similaire (en .Net4.0).
Aussi pour ceux qui se demandent pourquoi x == y
est l'égalité de référence, il est parce que la ==
operator est une méthode statique, ce qui signifie qu'il est résolu à la compilation et à la compilation x et y sont de type object
donc ici, il décide de le ==
operator de object
- qui est le real méthode de l'égalité de référence. (En fait, le procédé de Object.ReferenceEquals(object, object)
est simplement une redirection vers l'objet est égale à l'opérateur.)
Voici une implémentation simple pour 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);
}
EDIT (Vous ne devez pas lire, sauf si vous êtes intéressé par les commentaires ci-dessous)
@AnorZaken a consacré plusieurs paragraphes aux trois lettres du modificateur de new
ici. Résumons.
La méthode Equals(object,object)
instance définie unique implémente la méthode Equals
des deux interfaces déclarées pour ce type, IEqualityComparer
et son homologue IEqualityComparer<object>
générique. Les signatures sont identiques, de sorte que cette définition satisfait aux deux interfaces.
La méthode d'instance ReferenceEqualityComparer.Equals(object,object)
cache statique méthode object.Equals(object,object)
.
Sans new
le compilateur met en garde à ce sujet. Qu'est-ce que cela signifie réellement?
Cela signifie que si vous voulez appeler les méthodes object.Equals
statiques, vous ne pouvez pas l'appeler sur une par exemple de ReferenceEqualityComparer
. Est-ce important?
Non. En fait, il est le comportement souhaité. Cela signifie que si vous voulez appeler object.Equals(a,b)
vous ne pouvez pas le faire via un code tel que ReferenceEqualityComparer.Default.Equals(a,b)
. Ce code demande clairement référence l'égalité - personne ne peut raisonnablement s'attendre à réaliser l'égalité par défaut / valeur. Pourquoi ne pas le code juste object.Equals(a,b)
plus explicite de toute façon? Ainsi, l'utilisation de new
fournit un comportement raisonnable et souhaitable, et permet la compilation sans avertissement.
Sinon, comment pourriez-vous supprimer l'avertissement? Si vous utilisez un #pragma warning disable 108
/ #pragma warning restore 108
alors le résultat est le même que l'utilisation new
, sauf que vous avez ajouté un tas plus de bruit à votre code. new
suffit et explique l'intention plus clairement aux autres.
Sinon, vous pouvez utiliser des implémentations explicites pour les deux méthodes de Equals
d'interface, mais si vous avez utilisé ReferenceEqualityComparer.Default.Equals(a,b)
vous pas l'égalité de référence du tout.
En réalité, cachant des méthodes statiques avec des méthodes d'instance est rarement un problème parce que les méthodes statiques sont déréférencé d'un spécificateur de type, pas un prescripteur d'instance. Autrement dit, vous utilisez Foo.StaticMethod()
pas new Foo().StaticMethod()
. Appel de méthodes statiques de cas est inutile au mieux et trompeur / incorrect au pire.
En outre, pour l'égalité des comparateurs, vous utilisez rarement leurs types de béton directement. Au contraire, vous les utiliser avec les API telles que les collections.
Ainsi, alors que cela était intéressant et parfois déroutant discussion, il était plutôt inutile.
Microsoft fournit ObjectReferenceEqualityComparer
dans System.Data.Entity.Infrastructure
.
Il suffit d'utiliser ObjectReferenceEqualityComparer.Default
comme comparateur.