Em C #, o que acontece quando você chama um método de extensão em um objeto nulo?

329

O método é chamado com um valor nulo ou fornece uma exceção de referência nula?

MyObject myObject = null;
myObject.MyExtensionMethod(); // <-- is this a null reference exception?

Se for esse o caso, nunca precisarei verificar se meu parâmetro 'this' é nulo?

tpower
fonte
A menos, é claro, que você esteja lidando com o ASP.NET MVC, que lançará esse erro Cannot perform runtime binding on a null reference.
Mrchief

Respostas:

387

Isso funcionará bem (sem exceção). Os métodos de extensão não usam chamadas virtuais (isto é, usam a instrução "call" il, não "callvirt"), portanto, não há verificação nula, a menos que você mesmo a escreva no método de extensão. Isso é realmente útil em alguns casos:

public static bool IsNullOrEmpty(this string value)
{
    return string.IsNullOrEmpty(value);
}
public static void ThrowIfNull<T>(this T obj, string parameterName)
        where T : class
{
    if(obj == null) throw new ArgumentNullException(parameterName);
}

etc

Fundamentalmente, chamadas para chamadas estáticas são muito literais - ou seja,

string s = ...
if(s.IsNullOrEmpty()) {...}

torna-se:

string s = ...
if(YourExtensionClass.IsNullOrEmpty(s)) {...}

onde obviamente não há verificação nula.

Marc Gravell
fonte
1
Marc, você está falando sobre chamadas "virtuais" - mas o mesmo vale para chamadas não virtuais em métodos de instância. Eu acho que a palavra "virtual" aqui está fora de lugar.
1111 Konrad Rudolph
6
@ Konrad: Depende do contexto. O compilador C # geralmente usa callvirt mesmo para métodos não virtuais, precisamente para obter uma verificação nula.
11119 Jon Skeet
Eu estava me referindo à diferença entre as instruções call e callvirt il. Em uma edição, na verdade, tentei refazer as duas páginas do Opcodes, mas o editor vomitou nos links ...
Marc Gravell
2
Não vejo como esse uso de métodos de extensão possa ser realmente útil. Só porque isso pode ser feito não significa que está certo e, como o Binary Worrier mencionou abaixo, parece-me mais uma aberração, para dizer o mínimo.
Armadilha
3
@Trap: esse recurso é ótimo se você gosta de programação de estilo funcional.
Roy Tinker
50

Além da resposta correta de Marc Gravell.

Você pode receber um aviso do compilador se for óbvio que o argumento this é nulo:

default(string).MyExtension();

Funciona bem em tempo de execução, mas produz o aviso "Expression will always cause a System.NullReferenceException, because the default value of string is null".

Stefan Steinegger
fonte
32
Por que o aviso "sempre causa uma System.NullReferenceException". Quando, de fato, nunca será?
Tpower
46
Felizmente, nós programadores se preocupamos apenas com erros, não avisos: p
JulianR
7
@ JulianR: Sim, alguns fazem, outros não. Em nossa configuração de compilação de versão, tratamos os avisos como erros. Então, simplesmente não funciona.
237 Stefan Steinegger #
8
Obrigado pela observação; Vou pegar isso no banco de dados de erros e veremos se podemos corrigi-lo para o C # 4.0. (Nenhuma promessa - uma vez que é um caso de canto irrealista e apenas uma advertência, podemos punt em corrigi-lo.)
Eric Lippert
3
@ Stefan: Como é um erro e não um aviso "verdadeiro", você pode usar uma instrução #pragma para suprimir o aviso e fazer com que o código passe na versão do build.
Martin RL
25

Como você já descobriu, como os métodos de extensão são simplesmente métodos estáticos glorificados, eles serão chamados com nullreferências passadas, sem NullReferenceExceptionserem lançadas. Mas, como eles parecem métodos de instância para o chamador, eles também devem se comportar como tal. Você deve, na maioria das vezes, verificar o thisparâmetro e lançar uma exceção, se for null. Não há problema em fazer isso se o método cuidar explicitamente dos nullvalores e seu nome indicar devidamente, como nos exemplos abaixo:

public static class StringNullExtensions { 
  public static bool IsNullOrEmpty(this string s) { 
    return string.IsNullOrEmpty(s); 
  } 
  public static bool IsNullOrBlank(this string s) { 
    return s == null || s.Trim().Length == 0; 
  } 
}

Também escrevi um post sobre isso há algum tempo.

Jordão
fonte
3
Votei isso porque é correto e faz sentido para mim (e bem escrito), enquanto eu também prefiro o uso descrito na resposta por @Marc Gravell.
Qxotk
17

Um nulo será passado para o método de extensão.

Se o método tentar acessar o objeto sem verificar se ele é nulo, sim, ele lançará uma exceção.

Um cara aqui escreveu os métodos de extensão "IsNull" e "IsNotNull" que verificam se a referência passou nula ou não. Pessoalmente, acho que isso é uma aberração e não deveria ter visto a luz do dia, mas é perfeitamente válido c #.

Preocupação binária
fonte
18
De fato, para mim é como perguntar a um cadáver "Você está vivo" e obter uma resposta de "não". Um cadáver não pode responder a nenhuma pergunta, nem você pode "chamar" um método em um objeto nulo.
Worrier binário
14
Não concordo com a lógica de Binary Worrier, como é útil ser capaz de chamar extensões sem se preocupar com refs nulos, mas um para o valor de comédia analogia :-)
Tim Abell
10
Na verdade, às vezes você não sabe se alguém está morto, assim você ainda perguntar, ea pessoa pode responder: "Não, apenas descansando com os olhos fechados"
nurchi
7
Quando você precisar encadear várias operações (digamos 3+), poderá (assumindo que não haja efeitos colaterais) transformar várias linhas de código de verificação nula chato em um alinhamento elegante e encadeado com métodos de extensão "null-safe". (Semelhante ao operador ".?" - sugerido, mas reconhecidamente não tão elegante.) Se não for óbvio que uma extensão é "nula-segura", geralmente prefixo o método com "Seguro", portanto, por exemplo, é uma cópia. método, seu nome poderia ser "SafeCopy" e retornaria nulo se o argumento fosse nulo.
AnorZaken
3
Eu ri tanto com a resposta @BinaryWorrier hahahaha eu me vi chutando um corpo para verificar se estava morto ou não hahaha Então, na minha imaginação, quem checou se o corpo estava morto ou não era eu e não o próprio corpo, a implementação para o cheque estava em mim, ativamente chutando para ver se ele se move. Para que um corpo não saiba se está morto ou não, a OMS verifica, sabe, agora você pode argumentar que poderia "encaixar" no corpo uma maneira de dizer se está morto ou não e que, na minha opinião, é o que uma extensão é para.
Zorkind 22/06/19
7

Como outros apontaram, chamar um método de extensão com referência nula faz com que este argumento seja nulo e nada mais especial acontecerá. Isso dá origem a uma idéia de usar métodos de extensão para escrever cláusulas de guarda.

Você pode ler este artigo para obter exemplos: Como reduzir a complexidade ciclomática: cláusula de guarda A versão curta é a seguinte:

public static class StringExtensions
{
    public static void AssertNonEmpty(this string value, string paramName)
    {
        if (string.IsNullOrEmpty(value))
            throw new ArgumentException("Value must be a non-empty string.", paramName);
    }
}

Este é o método de extensão de classe de cadeia que pode ser chamado na referência nula:

((string)null).AssertNonEmpty("null");

A chamada funciona bem apenas porque o tempo de execução chama com êxito o método de extensão na referência nula. Então você pode usar este método de extensão para implementar cláusulas de guarda sem sintaxe bagunçada:

    public IRegisteredUser RegisterUser(string userName, string referrerName)
    {

        userName.AssertNonEmpty("userName");
        referrerName.AssertNonEmpty("referrerName");

        ...

    }
Zoran Horvat
fonte
3

O método de extensão é estático, portanto, se você não fizer nada a este MyObject, isso não deve ser um problema, um teste rápido deve verificar isso :)

Fredrik Leijon
fonte
-1

Existem poucas regras de ouro quando você deseja que seja legível e vertical.

  • alguém que vale a pena dizer de Eiffel diz que o código específico encapsulado em um método deve funcionar com alguma entrada; esse código é viável se forem atendidas algumas condições prévias e garantir uma saída esperada

No seu caso - DesignByContract está quebrado ... você executará alguma lógica em uma instância nula.

Rússia
fonte