Come posso verificare la presenza di valori nulli in un sovraccarico di operatori "==" senza ricorsione infinita?


113

Quanto segue causerà una ricorsione infinita sul metodo di sovraccarico dell'operatore ==

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

Come posso verificare la presenza di null?

Risposte:


138

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

Questa soluzione non funziona perAssert.IsFalse(foo2 == foo1);
FIL

E cosa foo1.Equals(foo2)significa se, ad esempio, voglio foo1 == foo2solo se foo1.x == foo2.x && foo1.y == foo2.y? Non è questo rispondere ignorando il caso dove foo1 != nullma foo2 == null?
Daniel

Nota: la stessa soluzione con una sintassi più semplice:if (foo1 is null) return foo2 is null;
Rem

20

Trasmetti all'oggetto nel metodo di sovraccarico:

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

1
Esattamente. Entrambi (object)foo1 == nullo foo1 == (object)nullandranno al ==(object, object)sovraccarico predefinito e non al sovraccarico definito dall'utente ==(Foo, Foo). È proprio come la risoluzione del sovraccarico sui metodi.
Jeppe Stig Nielsen

2
Per i futuri visitatori: la risposta accettata è una funzione che esegue il == dell'oggetto. Questa è fondamentalmente la stessa della risposta accettata, con uno svantaggio: ha bisogno di un cast. La risposta accettata è quindi superiore.
Mafii

1
@Mafii Il cast è puramente un'operazione in fase di compilazione. Poiché il compilatore sa che il cast non può fallire, non è necessario controllare nulla in fase di esecuzione. Le differenze tra i metodi sono completamente estetiche.
Servizio

8

Usa ReferenceEquals. Dai forum 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;
}

4

Provare Object.ReferenceEquals(foo1, null)

Comunque, non consiglierei di sovraccaricare l' ==operatore; dovrebbe essere usato per confrontare i riferimenti e usare Equalsper i confronti "semantici".


4

Se ho sovrascritto bool Equals(object obj)e voglio che l'operatore ==e Foo.Equals(object obj)restituisca lo stesso valore, di solito implemento l' !=operatore in questo modo:

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'operatore ==quindi, dopo aver eseguito tutti i controlli nulli per me, finirà per chiamare foo1.Equals(foo2)che ho sovrascritto per eseguire il controllo effettivo se i due sono uguali.


Questo sembra molto appropriato; guardando l'implementazione di Object.Equals(Object, Object)fianco a fianco Object.ReferenceEquals(Object, Object), è abbastanza chiaro che Object.Equals(Object, Object)fa tutto come suggerito nelle altre risposte fuori dagli schemi. Perché non usarlo?
TNE

@tne Perché non ha senso sovraccaricare l' ==operatore se tutto quello che vuoi è il comportamento predefinito. Dovresti sovraccaricare solo quando devi implementare una logica di confronto personalizzata, ovvero qualcosa di più di un controllo di uguaglianza dei riferimenti.
Dan Bechard

@ Dan, sono sicuro che tu abbia frainteso la mia osservazione; in un contesto in cui è già stabilito che il sovraccarico ==è desiderabile (la domanda lo implica) sto semplicemente supportando questa risposta suggerendo che Object.Equals(Object, Object)non sono necessari altri trucchi come l'uso ReferenceEqualso cast espliciti (quindi "perché non usarlo?", "esso" essere Equals(Object, Object)). Anche se non correlato il tuo punto è corretto, e vorrei andare oltre: solo il sovraccarico ==per gli oggetti che possiamo classificare come "oggetti valore".
TNE

@tne La differenza principale è che Object.Equals(Object, Object)a sua volta chiama Object.Equals (Object) che è un metodo virtuale che Foo probabilmente sovrascrive. Il fatto che tu abbia introdotto una chiamata virtuale nel tuo controllo di uguaglianza potrebbe influenzare la capacità del compilatore di ottimizzare (ad esempio inline) queste chiamate. Questo è probabilmente trascurabile per la maggior parte degli scopi, ma in alcuni casi un piccolo costo in un operatore di uguaglianza può significare un costo enorme per loop o strutture di dati ordinate.
Dan Bechard

@tne Per ulteriori informazioni sulle complessità dell'ottimizzazione delle chiamate ai metodi virtuali, fare riferimento a stackoverflow.com/questions/530799/… .
Dan Bechard

3

Se si utilizza C # 7 o versioni successive, è possibile utilizzare la corrispondenza del modello costante null:

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

Questo ti dà un codice leggermente più ordinato rispetto all'oggetto chiamante.ReferenceEquals (foo1, null)


2
oppurepublic static bool operator==( Foo foo1, Foo foo2 ) => foo1?.Equals( foo2 ) ?? foo2 is null;
Danko Durbić

3

In realtà c'è un modo più semplice per verificare nullin questo caso:

if (foo is null)

Questo è tutto!

Questa funzionalità è stata introdotta in C # 7


1

Il mio approccio è fare

(object)item == null

su cui mi affido al objectproprio operatore di uguaglianza che non può andare storto. O un metodo di estensione personalizzato (e un sovraccarico):

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

o per gestire più casi, può essere:

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

Il vincolo impedisce i IsNulltipi di valore. Ora è dolce come chiamare

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

il che significa che ho uno stile di controllo coerente / non soggetto a errori per tutto il tempo. Ho anche scoperto che (object)item == nullè molto molto molto leggermente più veloce diObject.ReferenceEquals(item, null) , ma solo se è importante (attualmente sto lavorando a qualcosa in cui devo micro-ottimizzare tutto!).

Per visualizzare una guida completa sull'implementazione dei controlli di uguaglianza, vedere Che cos'è la "best practice" per confrontare due istanze di un tipo di riferimento?


Nitpick: i lettori dovrebbero guardare le loro dipendenze prima di saltare su funzionalità come il confronto DbNull, IMO i casi in cui ciò non genererebbe problemi relativi a SRP sono piuttosto rari. Tuttavia, solo sottolineando l'odore del codice, potrebbe essere appropriato.
TNE

0

Il Equals(Object, Object)metodo statico indica se due oggetti objAe objBsono uguali. Consente inoltre di testare oggetti il ​​cui valore è nullper l'uguaglianza. Confronta objAe objBper l'uguaglianza come segue:

  • Determina se i due oggetti rappresentano lo stesso riferimento all'oggetto. Se lo fanno, il metodo ritorna true. Questo test equivale a chiamare il ReferenceEqualsmetodo. Inoltre, se entrambi objAe objBsono null, il metodo restituisce true.
  • Determina se objAo objBè null. Se è così, ritorna false. Se i due oggetti non rappresentano lo stesso riferimento all'oggetto e nessuno dei due lo è null, chiama objA.Equals(objB)e restituisce il risultato. Ciò significa che se objAsostituisce il Object.Equals(Object)metodo, viene chiamato questo override.

.

public static bool operator ==(Foo objA, Foo objB) {
    return Object.Equals(objA, objB);
}

0

rispondendo di più all'operatore che sovrascrive come confrontare con null che reindirizza qui come duplicato.

Nei casi in cui questo viene fatto per supportare oggetti valore, trovo la nuova notazione a portata di mano e mi piace assicurarmi che ci sia solo un punto in cui viene effettuato il confronto. Anche sfruttare Object.Equals (A, B) semplifica i controlli nulli.

Questo sovraccaricherà ==,! =, Equals e 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();

Per oggetti più complicati aggiungi ulteriori confronti in Equals e un GetHashCode più ricco.


0

Per una sintassi moderna e condensata:

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

-3

Un errore comune a sovraccarichi di operatore == è quello di utilizzare (a == b), (a ==null)o (b == null)controllare l'uguaglianza riferimento. Ciò si traduce invece in una chiamata all'operatore sovraccarico ==, che causa un infinite loop. Usa ReferenceEqualso esegui il cast del tipo su Object, per evitare il ciclo.

controlla questo

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

Linee guida di riferimento per il sovraccarico di Equals () e Operatore ==


1
Ci sono già più risposte con tutte queste informazioni. Non abbiamo bisogno di una settima copia della stessa risposta.
Servy

-5

Puoi provare a utilizzare una proprietà dell'oggetto e catturare la NullReferenceException risultante. Se la proprietà che provi è ereditata o sovrascritta da Object, allora funziona per qualsiasi 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;
}

Se hai molti oggetti nulli, la gestione delle eccezioni potrebbe essere un grosso sovraccarico.
Kasprzol

2
Haha, sono d'accordo che questo non è il metodo migliore. Dopo aver pubblicato questo metodo, ho immediatamente rivisto il mio progetto corrente per utilizzare invece ReferenceEquals. Tuttavia, nonostante sia non ottimale, funziona e quindi è una risposta valida alla domanda.
The Digital Gabeg
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.