Chamada de método se não for nula em C #

106

É possível encurtar de alguma forma esta afirmação?

if (obj != null)
    obj.SomeMethod();

porque escrevo muito isso e fica muito chato. A única coisa que posso pensar é implementar o padrão Null Object , mas isso não é o que posso fazer todas as vezes e certamente não é uma solução para encurtar a sintaxe.

E problema semelhante com eventos, onde

public event Func<string> MyEvent;

e então invocar

if (MyEvent != null)
    MyEvent.Invoke();
Jakub Arnold
fonte
41
Consideramos adicionar um novo operador ao C # 4: "obj.?SomeMethod ()" significaria "chamar SomeMethod se obj não for nulo, caso contrário, retorne nulo". Infelizmente, não cabia em nosso orçamento, por isso nunca o implementamos.
Eric Lippert
@Eric: Este comentário ainda é válido? Já vi em algum lugar que está disponível com 4.0?
CharithJ
@CharithJ: Não. Nunca foi implementado.
Eric Lippert
3
@CharithJ: Estou ciente da existência do operador de coalescência nula. Não faz o que Darth deseja; ele quer um operador de acesso de membro elevado a nulo. (E, a propósito, seu comentário anterior fornece uma caracterização incorreta do operador de coalescência nulo. Você quis dizer "v = m == null? Y: m.Value pode ser escrito v = m ?? y".)
Eric Lippert
4
Para leitores mais novos: C # 6.0 implementa?., Então x? .Y? .Z? .ToString () retornará nulo se x, y ou z forem nulos ou retornará z.ToString () se nenhum deles for nulo.
David,

Respostas:

162

Do C # 6 em diante, você pode apenas usar:

MyEvent?.Invoke();

ou:

obj?.SomeMethod();

O ?.é o operador de propagação nula e causará um .Invoke()curto-circuito quando o operando for null. O operando é acessado apenas uma vez, não havendo risco do problema de "mudança de valor entre checar e invocar".

===

Antes do C # 6, não: não há mágica de segurança nula, com uma exceção; métodos de extensão - por exemplo:

public static void SafeInvoke(this Action action) {
    if(action != null) action();
}

agora isso é válido:

Action act = null;
act.SafeInvoke(); // does nothing
act = delegate {Console.WriteLine("hi");}
act.SafeInvoke(); // writes "hi"

No caso de eventos, tem a vantagem de remover também a condição de corrida, ou seja, você não precisa de uma variável temporária. Normalmente, você precisa de:

var handler = SomeEvent;
if(handler != null) handler(this, EventArgs.Empty);

mas com:

public static void SafeInvoke(this EventHandler handler, object sender) {
    if(handler != null) handler(sender, EventArgs.Empty);
}

podemos usar simplesmente:

SomeEvent.SafeInvoke(this); // no race condition, no null risk
Marc Gravell
fonte
1
estou um pouco em conflito com isso. No caso de uma ação ou manipulador de eventos - que normalmente é mais independente - isso faz algum sentido. Eu não quebraria o encapsulamento de um método regular, no entanto. Isso implica a criação do método em uma classe estática separada e não acho que perder o encapsulamento e degradar a legibilidade / organização do código em geral valha a pequena melhoria na legibilidade local
tvanfosson
@tvanfosson - de fato; mas o que quero dizer é que este é o único caso que sei onde isso funcionará. E a própria questão levanta o tópico de delegados / eventos.
Marc Gravell
Esse código acaba gerando um método anônimo em algum lugar, o que realmente confunde o rastreamento de pilha de qualquer exceção. É possível dar nomes de métodos anônimos? ;)
sisve
Errado de acordo com 2015 / C # 6.0 ...? ele é solção. ReferenceTHatMayBeNull? .CallMethod () não chamará o método quando nulo.
TomTom
1
@mercu deveria ser ?.- em VB14 e superior
Marc Gravell
27

O que você está procurando é o Null condicional (e não "coalescência") operador: ?.. Ele está disponível a partir de C # 6.

Seu exemplo seria obj?.SomeMethod();. Se obj for nulo, nada acontece. Quando o método tem argumentos, por exemplo, obj?.SomeMethod(new Foo(), GetBar());os argumentos não são avaliados se objfor nulo, o que importa se a avaliação dos argumentos tiver efeitos colaterais.

E o encadeamento é possível: myObject?.Items?[0]?.DoSomething()

Vimes
fonte
1
Isto é fantástico. É importante notar que este é um recurso do C # 6 ... (o que estaria implícito em sua instrução VS2015, mas ainda assim digno de nota). :)
Kyle Goode
10

Um método de extensão rápido:

    public static void IfNotNull<T>(this T obj, Action<T> action, Action actionIfNull = null) where T : class {
        if(obj != null) {
            action(obj);
        } else if ( actionIfNull != null ) {
            actionIfNull();
        }
    }

exemplo:

  string str = null;
  str.IfNotNull(s => Console.Write(s.Length));
  str.IfNotNull(s => Console.Write(s.Length), () => Console.Write("null"));

ou alternativamente:

    public static TR IfNotNull<T, TR>(this T obj, Func<T, TR> func, Func<TR> ifNull = null) where T : class {
        return obj != null ? func(obj) : (ifNull != null ? ifNull() : default(TR));
    }

exemplo:

    string str = null;
    Console.Write(str.IfNotNull(s => s.Length.ToString());
    Console.Write(str.IfNotNull(s => s.Length.ToString(), () =>  "null"));
Katbyte
fonte
Eu tentei fazer isso com métodos de extensão e acabei com praticamente o mesmo código. No entanto, o problema com essa implementação é que ela não funcionará com expressões que retornam um tipo de valor. Então, tinha que haver um segundo método para isso.
orad
4

Os eventos podem ser inicializados com um delegado padrão vazio que nunca é removido:

public event EventHandler MyEvent = delegate { };

Nenhuma verificação nula necessária.

[ Atualização , obrigado a Bevan por apontar isso]

Porém, esteja ciente do possível impacto no desempenho. Um rápido micro benchmark que fiz indica que lidar com um evento sem assinantes é 2 a 3 vezes mais lento ao usar o padrão de "delegado padrão". (No meu laptop dual core de 2,5 GHz, isso significa 279 ms: 785 ms para gerar 50 milhões de eventos não inscritos.). Para pontos de acesso de aplicativos, isso pode ser um problema a ser considerado.

Sven Künzler
fonte
1
Então, você evita uma verificação de nulo invocando um delegado vazio ... um sacrifício mensurável de memória e tempo para economizar alguns toques no teclado? YMMV, mas para mim, um mau negócio.
Bevan
Também vi benchmarks que mostram que é consideravelmente mais caro invocar um evento com vários delegados inscritos nele do que com apenas um.
Greg D
2

Este artigo de Ian Griffiths apresenta duas soluções diferentes para o problema, que ele conclui serem truques bacanas que você não deve usar.

Darrel Miller
fonte
3
E falando como autor desse artigo, eu acrescentaria que você definitivamente não deveria usá-lo agora que o C # 6 resolve isso fora da caixa com os operadores condicionais nulos (?. E. []).
Ian Griffiths
2

O método de extensão cerating como o sugerido não resolve realmente problemas com condições de corrida, mas sim os esconde.

public static void SafeInvoke(this EventHandler handler, object sender)
{
    if (handler != null) handler(sender, EventArgs.Empty);
}

Conforme declarado, este código é o elegante equivalente à solução com variável temporária, mas ...

O problema com ambos é que é possível que o assinante do evento seja chamado DEPOIS de ter cancelado a inscrição do evento . Isso é possível porque o cancelamento da assinatura pode acontecer depois que a instância do delegado é copiada para a variável temporária (ou passada como parâmetro no método acima), mas antes que o delegado seja invocado.

Em geral, o comportamento do código do cliente é imprevisível nesse caso: o estado do componente já não podia permitir o tratamento da notificação de eventos. É possível escrever o código do cliente da maneira de lidar com isso, mas colocaria uma responsabilidade desnecessária para o cliente.

A única maneira conhecida de garantir a segurança do encadeamento é usar a instrução de bloqueio para o remetente do evento. Isso garante que todas as assinaturas \ unsubscriptions \ invocation sejam serializadas.

Para ser mais preciso, o bloqueio deve ser aplicado ao mesmo objeto de sincronização usado nos métodos de acesso adicionar / remover eventos, que é o padrão 'this'.

andrey.tsykunov
fonte
Esta não é a condição de corrida à qual se refere. A condição de corrida é if (MyEvent! = Null) // MyEvent não é nulo agora MyEvent.Invoke (); // MyEvent é nulo agora, coisas ruins acontecem. Portanto, com essa condição de corrida, não posso escrever um manipulador de eventos assíncrono que funcione com garantia. No entanto, com sua 'condição de corrida', posso escrever um manipulador de eventos assíncrono com garantia de funcionamento. É claro que as chamadas para assinante e não assinante precisam ser síncronas ou o código de bloqueio é necessário.
jyoung
De fato. Minha análise desse problema está postada aqui: blogs.msdn.com/ericlippert/archive/2009/04/29/…
Eric Lippert
2

Concordo com a resposta de Kenny Eliasson. Vá com métodos de extensão. Aqui está uma breve visão geral dos métodos de extensão e seu método IfNotNull obrigatório.

Métodos de extensão (método IfNotNull)

Rohit
fonte
1

Talvez não seja melhor, mas na minha opinião mais legível é criar um método de extensão

public static bool IsNull(this object obj) {
 return obj == null;
}
Kenny Eliasson
fonte
1
o que isso return obj == nullsignifica. O que isso vai retornar
Hammad Khan
1
Isso significa que se objfor nullo método retornará true, eu acho.
Joel
1
Como isso responde à pergunta?
Kugel
Resposta tardia, mas tem certeza de que isso funcionaria? Você pode chamar um método de extensão em um objeto que é null? Tenho certeza de que isso não funciona com os métodos do próprio tipo, então duvido que funcione com métodos de extensão também. Acredito que a melhor maneira de verificar se um objeto está null é obj is null. Infelizmente, para verificar se um objeto não o é, é nullnecessário colocá-lo entre parênteses, o que é uma pena.
natiiix