68 votes

IEqualityComparer<T> qui utilise ReferenceEquals

Existe-t-il une implémentation par défaut de IEqualityComparer qui utilise ReferenceEquals ?

EqualityComparer<T>.Default utilise ObjectComparer, qui utilise object.Equals(). Dans mon cas, les objets implémentent déjà IEquatable, que j'ai besoin d'ignorer et de comparer par référence d'objet uniquement.

Merci !

5 votes

.Net 5.0 introduira ReferenceEqualityComparer comme mentionné dans Top 10 des nouvelles API de .NET 5.0 - point 2) qui fait également référence à cette question.

62voto

Yurik Points 2005

Au cas où il n'y aurait pas d'implémentation par défaut, voici la mienne :

Edit by 280Z28 : Justification de l'utilisation RuntimeHelpers.GetHashCode(object) que beaucoup d'entre vous n'ont probablement jamais vu auparavant :) Cette méthode a deux effets qui en font la correct pour cette mise en œuvre :

  1. Il renvoie 0 lorsque l'objet est nul. Depuis ReferenceEquals fonctionne pour les paramètres nuls, il en va de même pour l'implémentation de GetHashCode() du comparateur.
  2. Il appelle Object.GetHashCode() non virtuellement. ReferenceEquals ignore spécifiquement tout remplacement de Equals L'implémentation de GetHashCode() doit donc utiliser une méthode spéciale qui correspond à l'effet de ReferenceEquals, ce qui est exactement le but de RuntimeHelpers.GetHashCode.

[fin 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
}

1 votes

La seule chose que je n'ai pas aimée, c'est que Default viole l'hypothèse de pureté d'un getter de propriété. Puisque le CLR n'exécute pas l'initialisateur statique de la classe tant qu'un membre de la classe n'est pas référencé, l'initialisation en ligne que j'ai ajoutée a le même effet d'initialisation paresseuse que la propriété, mais ne viole pas la contrainte de pureté. J'ai également scellé le type.

1 votes

Enfin, j'ai dérivé de EqualityComparer<T> pour récupérer l'implémentation de IEqualityComparer (non-générique). D'ailleurs, ce type exact est un internal dans l'assemblage System.Xaml de .NET 4 (dans l'espace de noms System.Xaml.Schema).

0 votes

Je n'avais même pas pensé au cas null - merci ! Quant à Default - EqualityComparer.Default (comparateur par défaut utilisé en interne) a une structure similaire. Je suppose que MS ne suit pas ses propres directives :)

21voto

AnorZaken Points 828

J'ai pensé qu'il était temps de mettre à jour l'implémentation des réponses précédentes vers .Net4.0+, où les génériques ne sont plus nécessaires grâce à la contravariance sur le modèle d'implémentation. IEqualityComparer<in T> interface :

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

public sealed class ReferenceEqualityComparer
    : IEqualityComparer, IEqualityComparer<object>
{
    private ReferenceEqualityComparer() { }

    public static readonly ReferenceEqualityComparer Default
        = new ReferenceEqualityComparer();

    public /*new*/ 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);
    }
}

Il suffit maintenant d'une seule instance pour toutes les vérifications de l'égalité des références, au lieu d'une pour chaque type. T comme c'était le cas auparavant.

Vous n'avez plus besoin de spécifier T à chaque fois que vous voulez l'utiliser et éviter également de polluer avec des types d'exécution génériques inutiles.


Quant à savoir pourquoi x == y est une égalité de référence, c'est parce que le == est une méthode statique, ce qui signifie qu'elle est résolue au moment de la compilation, et qu'au moment de la compilation, l'opérateur x y y Les arguments sont de type object .

En fait, c'est ce que le Object.ReferenceEquals(object, object) méthode source code ressemble :

public static bool ReferenceEquals(object objA, object objB) {
    return objA == objB;
}

Pour clarifier pour ceux qui ne sont pas familiers avec les concepts de Covariance et contravariance ...

class MyClass
{
    ISet<MyClass> setOfMyClass = new HashSet<MyClass>(ReferenceEqualityComparer.Default);
}

...le code ci-dessus compile ; Remarquez qu'il fait no dites HashSet<object> .

15voto

11voto

Drew Noakes Points 69288

Voici une mise en œuvre simple pour C# 6 et plus :

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);
}

Ou une version générique qui garantit qu'elle n'est utilisable qu'avec des types de référence :

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

    public bool Equals(T x, T y) => ReferenceEquals(x, y);
    public int GetHashCode(T obj) => RuntimeHelpers.GetHashCode(obj);
}

0voto

renouve Points 84

Microsoft fournit ObjectReferenceEqualityComparer en System.Data.Entity.Infrastructure . Il suffit d'utiliser ObjectReferenceEqualityComparer.Default en tant que comparateur.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X