Como faço para verificar se há nulos em uma sobrecarga de operador '==' sem recursão infinita?

113

O seguinte causará recursão infinita no método de sobrecarga do operador ==

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

Como faço para verificar se há nulos?

Andrew Jones
fonte

Respostas:

138

Use 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
fonte
Esta solução não funciona paraAssert.IsFalse(foo2 == foo1);
FIL
E o que foo1.Equals(foo2)significa se, por exemplo, eu quero foo1 == foo2apenas se foo1.x == foo2.x && foo1.y == foo2.y? Não é esta resposta ignorando o caso onde foo1 != nullmas foo2 == null?
Daniel
Nota: A mesma solução com sintaxe mais simples:if (foo1 is null) return foo2 is null;
Rem
20

Cast para objeto no método de sobrecarga:

public static bool operator ==(Foo foo1, Foo foo2) {
    if ((object) foo1 == null) return (object) foo2 == null;
    return foo1.Equals(foo2);
}
Andrew Jones
fonte
1
Exatamente. Ambos (object)foo1 == nullou foo1 == (object)nullirão para a sobrecarga embutida ==(object, object)e não para a sobrecarga definida pelo usuário ==(Foo, Foo). É como a resolução de sobrecarga nos métodos.
Jeppe Stig Nielsen
2
Para futuros visitantes - a resposta aceita é uma função, que executa o == do objeto. Essa é basicamente a mesma resposta aceita, com uma desvantagem: é preciso gesso. A resposta aceita é, portanto, superior.
Mafii
1
@Mafii O elenco é puramente uma operação de tempo de compilação. Como o compilador sabe que o elenco não pode falhar, ele não precisa verificar nada em tempo de execução. As diferenças entre os métodos são totalmente estéticas.
Servy 05 de
8

Use ReferenceEquals. Dos fóruns do 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
fonte
4

Experimentar Object.ReferenceEquals(foo1, null)

De qualquer forma, não recomendo sobrecarregar a ==operadora; deve ser usado para comparar referências e Equalspara comparações "semânticas".

Santiago Palladino
fonte
4

Se eu tiver substituído bool Equals(object obj)e eu quero que o operador ==e Foo.Equals(object obj)para retornar o mesmo valor, eu costumo implementar o !=operador como este:

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

O operador == , então, após fazer todas as verificações de nulos para mim, acabará chamando o foo1.Equals(foo2)que eu substituí para fazer a verificação real se os dois são iguais.

Hallgrim
fonte
Isso parece muito apropriado; olhando para a implementação de Object.Equals(Object, Object)lado a lado Object.ReferenceEquals(Object, Object), é bastante claro que Object.Equals(Object, Object)faz tudo conforme sugerido nas outras respostas fora da caixa. Por que não usar?
tne
@tne Porque não adianta sobrecarregar o ==operador se tudo o que você deseja é o comportamento padrão. Você só deve sobrecarregar quando precisar implementar lógica de comparação personalizada, ou seja, algo mais do que uma verificação de igualdade de referência.
Dan Bechard
@Dan Tenho certeza de que você entendeu mal minha observação; em um contexto onde já está estabelecido que a sobrecarga ==é desejável (a questão implica isso), estou simplesmente apoiando esta resposta, sugerindo que Object.Equals(Object, Object)torna ReferenceEqualsdesnecessários outros truques, como o uso ou conversão explícita (portanto, "por que não usar?", "isso" sendo Equals(Object, Object)). Mesmo que não relacionado, seu ponto também está correto, e eu iria além: apenas sobrecarga ==para objetos que podemos classificar como "objetos de valor".
tne
@tne A principal diferença é que, Object.Equals(Object, Object)por sua vez, chama Object.Equals (Object), que é um método virtual que Foo provavelmente substitui. O fato de você ter introduzido uma chamada virtual em sua verificação de igualdade pode afetar a capacidade do compilador de otimizar (por exemplo, em linha) essas chamadas. Isso provavelmente é insignificante para a maioria dos propósitos, mas em certos casos um pequeno custo em um operador de igualdade pode significar um custo enorme para loops ou estruturas de dados ordenadas.
Dan Bechard
@tne Para obter mais informações sobre os meandros da otimização de chamadas de métodos virtuais, consulte stackoverflow.com/questions/530799/… .
Dan Bechard
3

Se você estiver usando C # 7 ou posterior, poderá usar correspondência de padrão constante nula:

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

Isso fornece um código um pouco mais limpo do que o objeto de chamada.ReferenceEquals (foo1, null)

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

Na verdade, há uma maneira mais simples de verificar nullneste caso:

if (foo is null)

É isso aí!

Este recurso foi introduzido no C # 7

Reto Messerli
fonte
1

Minha abordagem é fazer

(object)item == null

no qual estou contando com objecto próprio operador de igualdade, que não pode dar errado. Ou um método de extensão personalizada (e uma sobrecarga):

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 para lidar com mais casos, podem ser:

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

A restrição impede IsNulltipos de valor. Agora é tão doce quanto chamar

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

o que significa que sempre tenho um estilo consistente / não sujeito a erros de verificação de nulos. Eu também descobri que (object)item == nullé muito, muito ligeiramente mais rápido do queObject.ReferenceEquals(item, null) , mas apenas se for importante (atualmente estou trabalhando em algo onde devo micro-otimizar tudo!).

Para ver um guia completo sobre a implementação de verificações de igualdade, consulte O que é "prática recomendada" para comparar duas instâncias de um tipo de referência?

Nawfal
fonte
Nitpick: Os leitores devem observar suas dependências antes de pular em recursos como comparação DbNull, IMO - os casos em que isso não geraria problemas relacionados ao SRP são muito raros. Apenas apontar o cheiro do código, porém, pode muito bem ser apropriado.
tne
0

O Equals(Object, Object)método estático indica se dois objetos, objAe objB, são iguais. Ele também permite que você teste objetos cujo valor é nullpara igualdade. Ele compara objAe objBpara igualdade da seguinte forma:

  • Ele determina se os dois objetos representam a mesma referência de objeto. Se o fizerem, o método retorna true. Este teste é equivalente a chamar o ReferenceEqualsmétodo. Além disso, se objAe objBforem null, o método retorna true.
  • Ele determina se objAou objBé null. Nesse caso, ele retorna false. Se os dois objetos não representam a mesma referência de objeto e nenhum é null, ele chama objA.Equals(objB)e retorna o resultado. Isso significa que, se objAsubstituir o Object.Equals(Object)método, essa substituição será chamada.

.

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

respondendo mais ao operador de substituição como comparar com nulo que redireciona aqui como uma duplicata.

Nos casos em que isso está sendo feito para oferecer suporte a Objetos de Valor, considero a nova notação muito útil e gosto de garantir que haja apenas um lugar onde a comparação é feita. Também aproveitar Object.Equals (A, B) simplifica as verificações de nulos.

Isso sobrecarregará ==,! =, 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();

Para objetos mais complicados, adicione comparações adicionais em Equals e um GetHashCode mais rico.

CCondron
fonte
0

Para uma sintaxe moderna e condensada:

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
fonte
-3

Um erro comum em sobrecargas de operador == é usar (a == b), (a ==null)ou (b == null)para verificar se há igualdade de referência. Em vez disso, isso resulta em uma chamada para o operador sobrecarregado ==, causando um infinite loop. Use ReferenceEqualsou converta o tipo em Object, para evitar o loop.

Veja isso

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

Diretrizes de referência para Sobrecarga Equals () e Operador ==

Basheer AL-MOMANI
fonte
1
Já existem várias respostas com todas essas informações. Não precisamos de uma sétima cópia da mesma resposta.
Servy
-5

Você pode tentar usar uma propriedade de objeto e capturar a NullReferenceException resultante. Se a propriedade que você tentar for herdada ou substituída de Object, isso funcionará para qualquer 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;
}
The Digital Gabeg
fonte
Se você tiver muitos objetos nulos, o tratamento de exceções pode ser uma grande sobrecarga.
Kasprzol
2
Haha, concordo que este não é o melhor método. Depois de postar esse método, eu imediatamente revisei meu projeto atual para usar ReferenceEquals. No entanto, apesar de ser inferior ao ideal, ele funciona e, portanto, é uma resposta válida à pergunta.
The Digital Gabeg