Qual é a diferença entre "x é nulo" e "x == nulo"?

277

Em C # 7, podemos usar

if (x is null) return;

ao invés de

if (x == null) return;

Existem vantagens em usar a nova maneira (exemplo anterior) em relação à maneira antiga?

A semântica é diferente?

É apenas uma questão de gosto? Caso contrário, quando devo usar um sobre o outro?

Referência: O que há de novo no C # 7.0 .

Maniero
fonte
4
esse é o link que eu estava olhando, no entanto, não fornece muitas informações, e é por isso que acho que o OP está fazendo a pergunta. A parte mais importante da página é este teste. Operador O operador "é" é usado para verificar se o tipo de tempo de execução de um objeto é compatível ou não com um determinado tipo. Em outras palavras, usamos o operador "is" para verificar se o tipo de um objeto é o que esperamos que seja. Vejamos sua sintaxe:
Simon Price
2
@ SimonPrice Trata-se da versão atual do C #: C # 6. Esta questão é sobre o C # 7, que possui correspondência de padrões .
Patrick Hofman
@bigown que tipo de detalhes você procura?
Patrick Hofman
@PatrickHofman o tipo de svick respondeu, por exemplo
Maniero

Respostas:

232

Atualização: o compilador Roslyn foi atualizado para tornar o comportamento dos dois operadores o mesmo quando não há um operador de igualdade sobrecarregado . Por favor, veja o código nos resultados atuais do compilador ( M1e M2no código) que mostra o que acontece quando não há comparador de igualdade sobrecarregado. Agora, ambos têm o ==comportamento com melhor desempenho . Se houver um comparador de igualdade sobrecarregado, o código ainda será diferente .

Veja para versões mais antigas do compilador Roslyn a análise abaixo.


Pois nullnão há diferença com o que estamos acostumados com o C # 6. No entanto, as coisas se tornam interessantes quando você muda nullpara outra constante.

Veja isso por exemplo:

Test(1);

public void Test(object o)
{
    if (o is 1) Console.WriteLine("a");
    else Console.WriteLine("b");
}

O teste cede a. Se você comparar isso com o o == (object)1que você escreveria normalmente, isso fará muita diferença. isleva em consideração o tipo do outro lado da comparação. Isso é legal!

Eu acho que o padrão == nullvs. is nullconstante é apenas algo que é muito familiar 'por acidente', em que a sintaxe do isoperador e do operador igual produz o mesmo resultado.


Como svick comentou, is nullchama System.Object::Equals(object, object)onde ==chamaceq .

IL para is:

IL_0000: ldarg.1              // Load argument 1 onto the stack
IL_0001: ldnull               // Push a null reference on the stack
IL_0002: call bool [mscorlib]System.Object::Equals(object, object) // Call method indicated on the stack with arguments
IL_0007: ret                  // Return from method, possibly with a value

IL para ==:

IL_0000: ldarg.1              // Load argument 1 onto the stack
IL_0001: ldnull               // Push a null reference on the stack
IL_0002: ceq                  // Push 1 (of type int32) if value1 equals value2, else push 0
IL_0004: ret                  // Return from method, possibly with a value

Como estamos falando null, não há diferença, pois isso só faz diferença nas instâncias . Isso pode mudar quando você sobrecarrega o operador de igualdade.

Patrick Hofman
fonte
16
@PatrickHofman Parece ischamadas object.Equals(x, null), enquanto ==compila como ceq. Mas o resultado deve ser o mesmo, como você disse.
svick
17
Lembre-se sempre de que ==é um operador sobrecarregável. Você pode ter qualquer comportamento que quiser com ele. Por exemplo, isso estranhamente implementado== não informa se sua instância é realmente nula. is nullpor outro lado, sempre retornará true para referências nulas verdadeiras :) Além disso, se você tiver ReferenceEqualsem seu código, as lâmpadas do VS 2017 sugerem que você mude para is null, não == null(corretamente).
Nawfal 19/04
2
@PatrickHofman @svick as duas verificações nulas agora são compiladas da mesma maneira, portanto, isnão há mais a sobrecarga de uma chamada de função quando usada para verificar a nula. Para prova, veja o link postado por @svick nos comentários.
usar o seguinte
1
@ AndreasBjørnHassingNielsen Atualizei minha resposta.
Patrick Hofman 12/06
2
@PatrickHofman as IL não devem ser o contrário? == chama System.Object :: Equals (Object, Object) e é chamadas nula ceq
Zbigniew Ledwoń
68

Operador igual sobrecarregado

De fato, há uma diferença na semântica entre as duas comparações quando você está comparando nullcom um tipo que sobrecarregou o ==operador. foo is nullusará comparação direta de referência para determinar o resultado, enquanto, foo == nullé claro, executará o ==operador sobrecarregado , se ele existir.

Neste exemplo, introduzi um "bug" no ==operador sobrecarregado , fazendo com que ele sempre lança uma exceção se o segundo argumento for null:

void Main()
{
    Foo foo = null;

    if (foo is null) Console.WriteLine("foo is null"); // This condition is met
    if (foo == null) Console.WriteLine("foo == null"); // This will throw an exception
}

public class Foo
{
    public static bool operator ==(Foo foo1, Foo foo2)
    {
        if (object.Equals(foo2, null)) throw new Exception("oops");
        return object.Equals(foo1, foo2);
    }

    // ...
}

O código IL para foo is nullusa a ceqinstrução para executar uma comparação direta de referência:

IL_0003:  ldloc.0     // foo
IL_0004:  ldnull      
IL_0005:  ceq

O código IL para foo == nullusa uma chamada para o operador sobrecarregado:

IL_0016:  ldloc.0     // foo
IL_0017:  ldnull      
IL_0018:  call        UserQuery+Foo.op_Equality

Portanto, a diferença é que, se você usar, ==corre o risco de executar o código do usuário (que pode potencialmente ter problemas inesperados de comportamento ou desempenho).

Restrição de genéricos

O uso da is nullconstrução restringe o tipo a um tipo de referência. O compilador garante isso, o que significa que você não pode usar is nullem um tipo de valor. Se você tiver um método genérico, não poderá usá-lo is null, a menos que o tipo genérico seja restrito a ser um tipo de referência.

bool IsNull<T>(T item) => item is null;                  // Compile error: CS0403
bool IsNull<T>(T item) => item == null;                  // Works
bool IsNull<T>(T item) where T : class => item is null;  // Works

Agradecemos a David Augusto Villa por apontar isso.

Thorkil Holm-Jacobsen
fonte
2
Além disso, a observação (x é nulo) requer uma restrição de classe se x é um tipo genérico, enquanto (x == nulo) e object.ReferenceEquals (x, nulo) não.
David Augusto Villa