Diferença entre 'x is null' e 'x == null'

Pattern matching em C#

As novas versões do C# trouxeram diversas novidades que melhoraram o nosso dia a dia escrevendo código.

A partir do C# 7 começaram a surgir recursos voltados para pattern matching. Um recurso importante são as expressões do tipo is com padrões. Esse tipo de expressão tem diversas variações, mas eu vejo uma em particular causar um pouco de confusão em pessoas que não estão tão acostumadas com C# moderno.

Atualmente, validações de nulabilidade podem ser feitas assim

1
2
if (x is null) 
    return;

ou assim

1
2
if (x == null)
    return;

Como tenho costume de escrever na primeira forma geralmente me perguntam qual a diferença entre as duas. Neste post tento explicar a diferença entre as duas comparações fazendo a análise do código IL gerado para os dois casos.

🛑 Spoiler (ou TL;DR): não há diferença nenhuma na maioria dos casos, o cenário muda quando é feita a sobrecarga dos operadores de igualdade em alguma classe.

Diferença entre as formas de comparação

Para analisar o IL gerado em ambos os casos, usei o código abaixo.

1
2
bool C1(object x) => x is null;    
bool C2(object x) => x == null;

O resultado em ambos os casos foi o mesmo.

1
2
3
4
IL_0000: ldarg.1
IL_0001: ldnull
IL_0002: ceq
IL_0004: ret

💡 Explicando (brevemente) as instruções

  • ldarg.1: carrega o primeiro argumento do método para a pilha
  • ldnull: carrega o valor null para a pilha
  • ceq: compara os valores da pilha e carrega 1 caso sejam iguais ou 0 caso contrário - esta instrução é a comparação propriamente dita

Com isso é possível concluir que não há diferença entre as duas formas de comparação, pelo menos na maioria dos casos.

Sobrecarregando o operador de igualdade

Caso o operador de igualdade (==) seja sobrecarregado em alguma classe para conter uma lógica própria, o cenário muda um pouco.

Considerando a classe Numero com o código abaixo

1
2
3
4
5
6
7
8
9
class Numero
{	
    public Numero(int valor) => Valor = valor;

    public int Valor { get; }

    public static bool operator ==(Numero n1, Numero n2) => true;
    public static bool operator !=(Numero n1, Numero n2) => true;	
}

E os métodos de teste C3 e C4

1
2
bool C3(Numero a) => a == null;    
bool C4(Numero a) => a is null;

Para o método C3 o IL gerado é diferente do anterior

1
2
3
4
IL_0000: ldarg.1
IL_0001: ldnull
IL_0002: call bool Numero::op_Equality(class Numero, class Numero)
IL_0007: ret

A linha IL_0002, como esperado, ao invés de usar a instrução ceq usa a sobrecarga definida na classe Numero.

Entretanto, no método C3 o código IL gerado é o mesmo dos exemplos anteriores.

1
2
3
4
IL_0000: ldarg.1
IL_0001: ldnull
IL_0002: ceq
IL_0004: ret

Concluindo

O is null, por bem ou por mal, não faz uso do operador sobrecarregado e, portanto, não está sujeito a possíveis erros de implementação ou mesmo uma implementação maliciosa. Embora estes casos sejam difíceis de serem encontrados no dia a dia, este é um efeito colateral interessante na minha opinião.

No código da classe de exemplo o operador de igualdade contém um código problemático sempre retornando true, desta forma, uma comparação do tipo == null vai retornar true mesmo para uma instância válida, enquanto o retorno da expressão is null será false para instâncias válidas.

1
2
3
4
5
6
var numero = new Numero(1);
Console.WriteLine(numero == null); // true
Console.WriteLine(numero is null); // false
        
Numero n2 = null; 
Console.WriteLine(n2 is null); // true

O código usado nos exemplos pode ser encontrado no SharpLab.

comments powered by Disqus
Mantido com ❤ por Jéf
Criado com Hugo
Tema Stack desenvolvido por Jimmy