Comment puis-je vérifier les valeurs nulles dans une surcharge d'opérateur '==' sans récursivité infinie?

113

Ce qui suit provoquera une récursion infinie sur la méthode de surcharge de l'opérateur ==

    Foo foo1 = null;
    Foo foo2 = new Foo();
    Assert.IsFalse(foo1 == foo2);

    public static bool operator ==(Foo foo1, Foo foo2) {
        if (foo1 == null) return foo2 == null;
        return foo1.Equals(foo2);
    }

Comment vérifier les valeurs nulles?

Andrew Jones
la source

Réponses:

138

Utilisez ReferenceEquals:

Foo foo1 = null;
Foo foo2 = new Foo();
Assert.IsFalse(foo1 == foo2);

public static bool operator ==(Foo foo1, Foo foo2) {
    if (object.ReferenceEquals(null, foo1))
        return object.ReferenceEquals(null, foo2);
    return foo1.Equals(foo2);
}
Abe Heidebrecht
la source
Cette solution ne fonctionne pas pourAssert.IsFalse(foo2 == foo1);
FIL
Et que foo1.Equals(foo2)signifie si, par exemple, je foo1 == foo2ne veux que si foo1.x == foo2.x && foo1.y == foo2.y? N'est-ce pas une réponse ignorant le cas où foo1 != nullmais foo2 == null?
Daniel
Remarque: La même solution avec une syntaxe plus simple:if (foo1 is null) return foo2 is null;
Rem
20

Cast en objet dans la méthode de surcharge:

public static bool operator ==(Foo foo1, Foo foo2) {
    if ((object) foo1 == null) return (object) foo2 == null;
    return foo1.Equals(foo2);
}
Andrew Jones
la source
1
Exactement. Les deux (object)foo1 == nullou foo1 == (object)nulliront à la surcharge intégrée ==(object, object)et non à la surcharge définie par l'utilisateur ==(Foo, Foo). C'est comme la résolution de surcharge sur les méthodes.
Jeppe Stig Nielsen
2
Aux futurs visiteurs - la réponse acceptée est une fonction, qui exécute le == de l'objet. C'est fondamentalement la même chose que la réponse acceptée, avec un inconvénient: il faut un casting. La réponse acceptée est donc supérieure.
Mafii
1
@Mafii Le cast est purement une opération de compilation. Puisque le compilateur sait que le cast ne peut pas échouer, il n'a pas besoin de vérifier quoi que ce soit au moment de l'exécution. Les différences entre les méthodes sont tout à fait esthétiques.
Servy
8

Utilisez ReferenceEquals. Depuis les forums MSDN :

public static bool operator ==(Foo foo1, Foo foo2) {
    if (ReferenceEquals(foo1, null)) return ReferenceEquals(foo2, null);
    if (ReferenceEquals(foo2, null)) return false;
    return foo1.field1 == foo2.field2;
}
Jon Adams
la source
4

Essayer Object.ReferenceEquals(foo1, null)

Quoi qu'il en soit, je ne recommanderais pas de surcharger l' ==opérateur; il doit être utilisé pour comparer des références et Equalspour des comparaisons «sémantiques».

Santiago Palladino
la source
4

Si je l' ai substituée bool Equals(object obj)et je veux l'opérateur ==et Foo.Equals(object obj)de retourner la même valeur, je mets en œuvre habituellement l' !=opérateur comme celui - ci:

public static bool operator ==(Foo foo1, Foo foo2) {
  return object.Equals(foo1, foo2);
}
public static bool operator !=(Foo foo1, Foo foo2) {
  return !object.Equals(foo1, foo2);
}

L'opérateur ==, après avoir effectué toutes les vérifications nulles pour moi, finira par appeler foo1.Equals(foo2)que j'ai remplacé pour faire la vérification réelle si les deux sont égaux.

Hallgrim
la source
Cela semble très approprié; en regardant la mise en œuvre de Object.Equals(Object, Object)côte à côte avec Object.ReferenceEquals(Object, Object), il est assez clair que Object.Equals(Object, Object)tout fait comme suggéré dans les autres réponses hors de la boîte. Pourquoi ne pas l'utiliser?
tne
@tne Parce qu'il ne sert à rien de surcharger l' ==opérateur si tout ce que vous voulez est le comportement par défaut. Vous ne devez surcharger que lorsque vous avez besoin d'implémenter une logique de comparaison personnalisée, c'est-à-dire quelque chose de plus qu'une vérification d'égalité des références.
Dan Bechard
@Dan Je suis convaincu que vous avez mal compris ma remarque; dans un contexte où il est déjà établi que la surcharge ==est souhaitable (la question l'implique), je soutiens simplement cette réponse en suggérant que d' Object.Equals(Object, Object)autres astuces comme l'utilisation ReferenceEqualsou les cast explicites sont inutiles (donc "pourquoi ne pas l'utiliser?", "ça" être Equals(Object, Object)). Même si sans rapport, votre point est également correct, et j'irais plus loin: seule surcharge ==pour les objets que nous pouvons classer comme "objets de valeur".
TNE
@tne La principale différence est qu'elle Object.Equals(Object, Object)appelle à son tour Object.Equals (Object) qui est une méthode virtuelle que Foo remplace probablement. Le fait que vous ayez introduit un appel virtuel dans votre vérification d'égalité peut affecter la capacité du compilateur à optimiser (par exemple en ligne) ces appels. C'est probablement négligeable dans la plupart des cas, mais dans certains cas, un petit coût dans un opérateur d'égalité peut signifier un coût énorme pour les boucles ou les structures de données triées.
Dan Bechard
@tne Pour plus d'informations sur les subtilités de l'optimisation des appels de méthode virtuelle, reportez-vous à stackoverflow.com/questions/530799/… .
Dan Bechard
3

Si vous utilisez C # 7 ou une version ultérieure, vous pouvez utiliser la correspondance de modèle à constante nulle:

public static bool operator==(Foo foo1, Foo foo2)
{
    if (foo1 is null)
        return foo2 is null;
    return foo1.Equals(foo2);
}

Cela vous donne un code légèrement plus net que celui de l'objet appelant.ReferenceEquals (foo1, null)

Jacekbe
la source
2
oupublic static bool operator==( Foo foo1, Foo foo2 ) => foo1?.Equals( foo2 ) ?? foo2 is null;
Danko Durbić
3

Il existe en fait un moyen plus simple de vérifier nulldans ce cas:

if (foo is null)

C'est tout!

Cette fonctionnalité a été introduite en C # 7

Reto Messerli
la source
1

Mon approche est de faire

(object)item == null

sur lequel je me fie à son objectpropre opérateur d'égalité qui ne peut pas aller mal. Ou une méthode d'extension personnalisée (et une surcharge):

public static bool IsNull<T>(this T obj) where T : class
{
    return (object)obj == null;
}

public static bool IsNull<T>(this T? obj) where T : struct
{
    return !obj.HasValue;
}

ou pour traiter plus de cas, peut être:

public static bool IsNull<T>(this T obj) where T : class
{
    return (object)obj == null || obj == DBNull.Value;
}

La contrainte empêche IsNullles types valeur. Maintenant c'est aussi doux que d'appeler

object obj = new object();
Guid? guid = null; 
bool b = obj.IsNull(); // false
b = guid.IsNull(); // true
2.IsNull(); // error

ce qui signifie que j'ai un style cohérent / non sujet aux erreurs de vérification des valeurs nulles partout. J'ai aussi trouvé que (object)item == nullc'est très très très légèrement plus rapide queObject.ReferenceEquals(item, null) , mais seulement si cela compte (je travaille actuellement sur quelque chose où je dois tout micro-optimiser!).

Pour consulter un guide complet sur la mise en œuvre des vérifications d'égalité, consultez Qu'est - ce que la «meilleure pratique» pour comparer deux instances d'un type de référence?

nawfal
la source
Nitpick: Les lecteurs doivent surveiller leurs dépendances avant de passer à des fonctionnalités telles que la comparaison DbNull, OMI les cas où cela ne générerait pas de problèmes liés à SRP sont assez rares. En soulignant simplement l'odeur du code, cela pourrait très bien être approprié.
tne le
0

La Equals(Object, Object)méthode statique indique si deux objets, objAet objB, sont égaux. Il vous permet également de tester des objets dont la valeur est nullpour l'égalité. Il compare objAet objBpour l'égalité comme suit:

  • Il détermine si les deux objets représentent la même référence d'objet. Si c'est le cas, la méthode retourne true. Ce test équivaut à appeler la ReferenceEqualsméthode. De plus, si les deux objAet objBsont null, la méthode retournetrue .
  • Il détermine si l'un objAou l' autre objBest null. Si tel est le cas, il revient false. Si les deux objets ne représentent pas la même référence d'objet et que ni l'un ni l'autre ne l'est null, il appelle objA.Equals(objB)et renvoie le résultat. Cela signifie que s'il objAremplace la Object.Equals(Object)méthode, ce remplacement est appelé.

.

public static bool operator ==(Foo objA, Foo objB) {
    return Object.Equals(objA, objB);
}
Zach Posten
la source
0

répondre plus à opérateur de remplacement comment comparer à null qui redirige ici comme un doublon.

Dans les cas où cela est fait pour prendre en charge les objets de valeur, je trouve la nouvelle notation pratique et j'aime m'assurer qu'il n'y a qu'un seul endroit où la comparaison est faite. Exploiter également Object.Equals (A, B) simplifie les vérifications nulles.

Cela surchargera ==,! =, Equals et GetHashCode

    public static bool operator !=(ValueObject self, ValueObject other) => !Equals(self, other);
    public static bool operator ==(ValueObject self, ValueObject other) => Equals(self, other);
    public override bool Equals(object other) => Equals(other as ValueObject );
    public bool Equals(ValueObject other) {
        return !(other is null) && 
               // Value comparisons
               _value == other._value;
    }
    public override int GetHashCode() => _value.GetHashCode();

Pour les objets plus compliqués, ajoutez des comparaisons supplémentaires dans Equals et un GetHashCode plus riche.

CCondron
la source
0

Pour une syntaxe moderne et condensée:

public static bool operator ==(Foo x, Foo y)
{
    return x is null ? y is null : x.Equals(y);
}

public static bool operator !=(Foo x, Foo y)
{
    return x is null ? !(y is null) : !x.Equals(y);
}
mr5
la source
-3

Une erreur commune de l' opérateur == surcharge est d'utiliser (a == b), (a ==null)ou (b == null)pour vérifier l'égalité de référence. Cela entraîne à la place un appel à l'opérateur surchargé ==, provoquant un infinite loop. Utilisez ReferenceEqualsou transtypez le type en Object pour éviter la boucle.

Regarde ça

// If both are null, or both are same instance, return true.
if (System.Object.ReferenceEquals(a, b))// using ReferenceEquals
{
    return true;
}

// If one is null, but not both, return false.
if (((object)a == null) || ((object)b == null))// using casting the type to Object
{
    return false;
}

Reference Guidelines for Overloading Equals () et Operator ==

Basheer AL-MOMANI
la source
1
Il y a déjà plusieurs réponses avec toutes ces informations. Nous n'avons pas besoin d'une 7e copie de la même réponse.
Servy
-5

Vous pouvez essayer d'utiliser une propriété d'objet et intercepter l'exception NullReferenceException résultante. Si la propriété que vous essayez est héritée ou remplacée par Object, cela fonctionne pour n'importe quelle classe.

public static bool operator ==(Foo foo1, Foo foo2)
{
    //  check if the left parameter is null
    bool LeftNull = false;
    try { Type temp = a_left.GetType(); }
    catch { LeftNull = true; }

    //  check if the right parameter is null
    bool RightNull = false;
    try { Type temp = a_right.GetType(); }
    catch { RightNull = true; }

    //  null checking results
    if (LeftNull && RightNull) return true;
    else if (LeftNull || RightNull) return false;
    else return foo1.field1 == foo2.field2;
}
Le Gabeg numérique
la source
Si vous avez de nombreux objets nuls, la gestion des exceptions peut être une surcharge.
Kasprzol
2
Haha, je suis d'accord que ce n'est pas la meilleure méthode. Après avoir publié cette méthode, j'ai immédiatement révisé mon projet actuel pour utiliser ReferenceEquals à la place. Cependant, bien qu'il soit sous-optimal, cela fonctionne et constitue donc une réponse valable à la question.
The Digital Gabeg