Maneiras exclusivas de usar o operador Null Coalescing [fechado]

164

Eu sei que a maneira padrão de usar o operador coalescente nulo em C # é definir valores padrão.

string nobody = null;
string somebody = "Bob Saget";
string anybody = "";

anybody = nobody   ?? "Mr. T"; // returns Mr. T
anybody = somebody ?? "Mr. T"; // returns "Bob Saget"

Mas o que mais pode ??ser usado? Não parece tão útil quanto o operador ternário, além de ser mais conciso e fácil de ler do que:

nobody = null;
anybody = nobody == null ? "Bob Saget" : nobody; // returns Bob Saget

Portanto, dado que menos sabem sobre o operador coalescente nulo ...

  • Você já usou ??para outra coisa?

  • É ??necessário ou você deve apenas usar o operador ternário (com o qual a maioria está familiarizada)

Armstrongest
fonte

Respostas:

216

Bem, primeiro de tudo, é muito mais fácil encadear do que o ternário padrão:

string anybody = parm1 ?? localDefault ?? globalDefault;

vs.

string anyboby = (parm1 != null) ? parm1 
               : ((localDefault != null) ? localDefault 
               : globalDefault);

Também funciona bem se o objeto nulo possível não for uma variável:

string anybody = Parameters["Name"] 
              ?? Settings["Name"] 
              ?? GlobalSetting["Name"];

vs.

string anybody = (Parameters["Name"] != null ? Parameters["Name"] 
                 : (Settings["Name"] != null) ? Settings["Name"]
                 :  GlobalSetting["Name"];
James Curran
fonte
12
O encadeamento é uma grande vantagem para o operador, remove um monte de IFs redundantes
chakrit
1
Eu apenas o usei hoje para substituir um bloco IF simples que escrevi antes de conhecer um operador coalescente ternário ou nulo. As ramificações true e false da instrução IF original chamavam o mesmo método, substituindo um de seus argumentos por um valor diferente se uma determinada entrada for NULL. Com o operador coalescente nulo, é uma ligação. Isso é realmente poderoso quando você tem um método que precisa de duas ou mais dessas substituições!
David A. Gray
177

Eu usei-o como uma linha de carga lenta:

public MyClass LazyProp
{
    get { return lazyField ?? (lazyField = new MyClass()); }
}

Legível? Decida por si mesmo.

Cristian Libardo
fonte
6
Hmmm, você encontrou um contra-exemplo para "por que alguém iria querer usá-lo como um IF ofuscado" ... que é realmente muito legível para mim.
Godeke
6
Talvez esteja faltando alguma coisa (eu uso principalmente Java), mas não há uma condição de corrida lá?
Justin K
9
@ Justin K - Existe apenas uma condição de corrida se vários threads estiverem acessando a propriedade LazyProp do mesmo objeto. É facilmente corrigível com um bloqueio, se a segurança de thread de cada instância for necessária. Claramente neste exemplo, não é necessário.
Jeffrey L Whitledge
5
@ Jeffrey: Se estivesse claro, eu não teria feito a pergunta. :) Quando vi esse exemplo, pensei imediatamente em um membro singleton e, como por acaso faço a maior parte da minha codificação em um ambiente multithread ... Mas, sim, se assumimos que o código está correto, qualquer coisa extra é desnecessária.
Justin K
7
Não precisa ser um Singleton para ter uma condição de corrida. Apenas uma instância compartilhada da classe que contém LazyProp e vários threads que acessam o LazyProp. Lazy <T> é a melhor maneira de fazer esse tipo de coisa e é seguro para threads por padrão (você pode alterar a segurança de thread do Lazy <T>).
Niall Connaughton
52

Eu achei útil de duas maneiras "um pouco estranhas":

  • Como alternativa para ter um outparâmetro ao escrever TryParserotinas (ou seja, retornar o valor nulo se a análise falhar)
  • Como uma representação "não sei" para comparações

Este último precisa de um pouco mais de informação. Normalmente, quando você cria uma comparação com vários elementos, é necessário verificar se a primeira parte da comparação (por exemplo, idade) fornece uma resposta definitiva e a próxima parte (por exemplo, nome) apenas se a primeira parte não ajudar. Usar o operador coalescente nulo significa que você pode escrever comparações bastante simples (seja para pedidos ou igualdade). Por exemplo, usando algumas classes auxiliares no MiscUtil :

public int Compare(Person p1, Person p2)
{
    return PartialComparer.Compare(p1.Age, p2.Age)
        ?? PartialComparer.Compare(p1.Name, p2.Name)
        ?? PartialComparer.Compare(p1.Salary, p2.Salary)
        ?? 0;
}

É certo que agora tenho o ProjectionComparer no MiscUtil, junto com algumas extensões, que tornam esse tipo de coisa ainda mais fácil - mas ainda é legal.

O mesmo pode ser feito para verificar a igualdade de referência (ou a nulidade) no início da implementação de Equals.

Jon Skeet
fonte
Gostei do que você fez com o PartialComparer, mas estava procurando casos em que eu precisava manter as variáveis ​​de expressão avaliadas. Eu não sou versado em lambdas e extensões, então você pode ver se o seguinte segue um padrão semelhante (isto é, funciona)? stackoverflow.com/questions/1234263/#1241780
maxwellb
33

Outra vantagem é que o operador ternário exige uma avaliação dupla ou uma variável temporária.

Considere isso, por exemplo:

string result = MyMethod() ?? "default value";

enquanto no operador ternário, você fica com:

string result = (MyMethod () != null ? MyMethod () : "default value");

que chama MyMethod duas vezes ou:

string methodResult = MyMethod ();
string result = (methodResult != null ? methodResult : "default value");

De qualquer maneira, o operador coalescente nulo é mais limpo e, acho, mais eficiente.


fonte
1
+1. Esta é uma grande razão pela qual eu gosto do operador coalescente nulo. É particularmente útil quando a chamada MyMethod()tem algum tipo de efeitos colaterais.
um CVn
Se MyMethod()não tiver nenhum efeito fora do retorno de um valor, o compilador sabe que não deve chamá-lo duas vezes, então você realmente não precisa se preocupar com eficiência aqui na maioria dos casos.
TinyTimZamboni
Também mantém as coisas mais legíveis no IMHO quando MyMethod()há uma sequência encadeada de objetos pontilhados. Ex:myObject.getThing().getSecondThing().getThirdThing()
xdhmoore 26/03
@TinyTimZamboni, você tem uma referência para esse comportamento do compilador?
Kuba Wyrostek
@KubaWyrostek Não tenho conhecimento do funcionamento específico do compilador C #, mas tenho alguma experiência com a teoria do compilador estático com o llvm. Há várias abordagens que um compilador pode adotar para otimizar uma chamada como essa. A Global Value Numbering notará que as duas chamadas MyMethodsão idênticas nesse contexto, assumindo que MyMethodé uma função pura. Outra opção é a memorização automática ou apenas ter a função fechada no cache. Por outro lado: en.wikipedia.org/wiki/Global_value_numbering
TinyTimZamboni
23

Outra coisa a considerar é que o operador de coalescência não chama o método get de uma propriedade duas vezes, como o ternário.

Portanto, existem cenários em que você não deve usar ternários, por exemplo:

public class A
{
    var count = 0;
    private int? _prop = null;
    public int? Prop
    {
        get 
        {
            ++count;
            return _prop
        }
        set
        {
            _prop = value;
        }
    }
}

Se você usar:

var a = new A();
var b = a.Prop == null ? 0 : a.Prop;

o getter será chamado duas vezes e a countvariável será igual a 2, e se você usar:

var b = a.Prop ?? 0

a countvariável será igual a 1, como deveria.

Fabio Lima
fonte
4
Isso merece mais votos. Eu li tantas vezes que ??é equivalente a ?:.
Kuba Wyrostek
1
Ponto válido sobre um getter ser chamado duas vezes. Mas neste exemplo, eu consideraria um padrão de design ruim ter um getter enganosamente nomeado para realmente fazer alterações no objeto.
Linas
15

A maior vantagem que encontro para o ??operador é que você pode converter facilmente tipos de valor anuláveis ​​em tipos não anuláveis:

int? test = null;
var result = test ?? 0; // result is int, not int?

Eu freqüentemente uso isso em consultas Linq:

Dictionary<int, int?> PurchaseQuantities;
// PurchaseQuantities populated via ASP .NET MVC form.
var totalPurchased = PurchaseQuantities.Sum(kvp => kvp.Value ?? 0);
// totalPurchased is int, not int?
Ryan
fonte
Eu posso estar um pouco atrasado aqui, mas esse segundo exemplo jogará se kvp == null. E na verdade Nullable<T>tem um GetValueOrDefaultmétodo que eu normalmente uso.
CompuChip
6
KeyValuePair é um tipo de valor na estrutura .NET, portanto, acessar qualquer uma de suas propriedades nunca lançaria uma exceção de referência nula. msdn.microsoft.com/en-us/library/5tbh8a42(v=vs.110).aspx
Ryan
9

Eu usei ?? na minha implementação do IDataErrorInfo:

public string Error
{
    get
    {
        return this["Name"] ?? this["Address"] ?? this["Phone"];
    }
}

public string this[string columnName]
{
    get { ... }
}

Se alguma propriedade individual estiver no estado "erro", eu recebo esse erro, caso contrário, fico nulo. Funciona muito bem.

Matt Hamilton
fonte
Interessante. Você está usando "this" como uma propriedade. Eu nunca fiz isso.
Armstrongest
Sim, é parte de como o IDataErrorInfo funciona. Geralmente essa sintaxe é útil apenas em classes de coleção.
Matt Hamilton
4
Você está armazenando mensagens de erro em this["Name"], this["Address"], etc ??
Andrew
7

Você pode usar o operador coalescente nulo para torná-lo um pouco mais limpo para lidar com o caso em que um parâmetro opcional não está definido:

public void Method(Arg arg = null)
{
    arg = arg ?? Arg.Default;
    ...
Niall Connaughton
fonte
Não seria ótimo se essa linha pudesse ser escrita como arg ?= Arg.Default?
Jesse de Wit
6

Eu gosto de usar o operador de coalescência nula para carregar preguiçosamente determinadas propriedades.

Um exemplo muito simples (e artificial) apenas para ilustrar meu argumento:

public class StackOverflow
{
    private IEnumerable<string> _definitions;
    public IEnumerable<string> Definitions
    {
        get
        {
            return _definitions ?? (
                _definitions = new List<string>
                {
                    "definition 1",
                    "definition 2",
                    "definition 3"
                }
            );
        }
    } 
}
mlnyc
fonte
O Recarregador na verdade sugere isso como um refator para uma carga lenta "tradicional".
Arichards 28/08/19
5

É ?? necessário, ou você deve apenas usar o operador ternário (com o qual a maioria está familiarizada)

Na verdade, minha experiência é que poucas pessoas estão familiarizadas com o operador ternário (ou mais corretamente, o operador condicional ; ?:é "ternário" no mesmo sentido que ||é binário ou +é unário ou binário; no entanto, é o único operador ternário em vários idiomas); portanto, pelo menos nessa amostra limitada, sua declaração falhará ali.

Além disso, como mencionado anteriormente, há uma situação importante em que o operador coalescente nulo é muito útil e é sempre que a expressão a ser avaliada tem algum efeito colateral. Nesse caso, você não pode usar o operador condicional sem (a) introduzir uma variável temporária ou (b) alterar a lógica real do aplicativo. (b) claramente não é apropriado em nenhuma circunstância e, embora seja uma preferência pessoal, não gosto de bagunçar o escopo da declaração com muitas variáveis ​​estranhas, mesmo que de curta duração; cenário particular.

Obviamente, se você precisar fazer várias verificações no resultado, o operador condicional ou um conjunto de ifblocos provavelmente será a ferramenta para o trabalho. Mas, para simples "se isso for nulo, use isso, caso contrário, use-o", o operador coalescente nulo ??é perfeito.

um CVn
fonte
Comentário muito tardio de minha parte - mas feliz por ver alguém cobrir que um operador ternário é um operador com três argumentos (dos quais agora há mais de um em C #).
26418 Steve Kidd
5

Uma coisa que venho fazendo muito ultimamente é usar coalescência nula para backups as. Por exemplo:

object boxed = 4;
int i = (boxed as int?) ?? 99;

Console.WriteLine(i); // Prints 4

Também é útil para fazer backup de longas cadeias ?.que podem falhar

int result = MyObj?.Prop?.Foo?.Val ?? 4;
string other = (MyObj?.Prop?.Foo?.Name as string)?.ToLower() ?? "not there";
Blue0500
fonte
4

O único problema é que o operador de coalescência nula não detecta cadeias vazias.


ie

string result1 = string.empty ?? "dead code!";

string result2 = null ?? "coalesced!";

RESULTADO:

result1 = ""

result2 = coalesced!

Atualmente, estou olhando para substituir o ?? operador para contornar isso. Seria útil que isso fosse incorporado ao Framework.

Pensamentos?

Kevin Panko
fonte
Você pode fazer isso com os métodos de extensão, mas eu concordo, seria uma boa adição ao código e muito útil em um contexto da web.
Armstrongest
Sim, este é um cenário freqüente ... há ainda um método especial String.IsNullOrEmpty (string) ...
Max Galkin
12
"o operador de coalescência nula não detecta cadeias vazias." Bem, é o operador null- coalescing, não o operador nullOrEmpty- coalal. E, pessoalmente, detesto misturar valores nulos e vazios em linguagens que distinguem as duas, o que torna a interface com coisas que não são muito irritantes. E eu sou um pouco obsessivo-compulsivo, então me irrita quando as linguagens / implementações não distinguem as duas de qualquer maneira, mesmo que eu entenda o motivo (como na [maioria das implementações de?] SQL).
JAB
3
??não pode ser sobrecarregado: msdn.microsoft.com/en-us/library/8edha89s(v=vs.100).aspx - isso seria ótimo para ser sobrecarregado. Eu uso uma combinação: s1.Nullify() ?? s2.Nullify()where string Nullify(this s)retorna a nullnos casos em que a string está vazia.
Kit
O único problema? Acabei de encontrar-me querendo ?? = e encontrei esta discussão ao ver se havia uma maneira de fazê-lo. (Situação: A primeira passagem carregado os casos de exceção, agora eu quero voltar completamente e carregar o padrão valores em qualquer coisa que já não são carregados.)
Loren Pechtel
3

É ?? necessário, ou você deve apenas usar o operador ternário (com o qual a maioria está familiarizada)

Você deve usar o que melhor expressa sua intenção. Uma vez que não é um operador de coalesce nulo, usá-lo .

Por outro lado, como é tão especializado, não acho que tenha outros usos. Eu teria preferido uma sobrecarga apropriada do ||operador, como outros idiomas. Isso seria mais parcimonioso no design da linguagem. Mas, bem …

Konrad Rudolph
fonte
3

Legal! Conte-me como alguém que não sabia sobre o operador coalescente nulo - isso é uma coisa bem bacana.

Acho muito mais fácil ler do que o operador ternário.

O primeiro lugar que me vem à mente onde posso usá-lo é manter todos os meus parâmetros padrão em um único local.

public void someMethod( object parm2, ArrayList parm3 )
{ 
  someMethod( null, parm2, parm3 );
}
public void someMethod( string parm1, ArrayList parm3 )
{
  someMethod( parm1, null, parm3 );
}
public void someMethod( string parm1, object parm2, )
{
  someMethod( parm1, parm2, null );
}
public void someMethod( string parm1 )
{
  someMethod( parm1, null, null );
}
public void someMethod( object parm2 )
{
  someMethod( null, parm2, null );
}
public void someMethod( ArrayList parm3 )
{
  someMethod( null, null, parm3 );
}
public void someMethod( string parm1, object parm2, ArrayList parm3 )
{
  // Set your default parameters here rather than scattered through the above function overloads
  parm1 = parm1 ?? "Default User Name";
  parm2 = parm2 ?? GetCurrentUserObj();
  parm3 = parm3 ?? DefaultCustomerList;

  // Do the rest of the stuff here
}
HanClinto
fonte
2

Um caso de uso estranho, mas eu tinha um método em que um IDisposableobjeto é potencialmente passado como um arg (e, portanto, descartado pelo pai), mas também poderia ser nulo (e, portanto, deve ser criado e descartado no método local)

Para usá-lo, o código parecia

Channel channel;
Authentication authentication;

if (entities == null)
{
    using (entities = Entities.GetEntities())
    {
        channel = entities.GetChannelById(googleShoppingChannelCredential.ChannelId);
        [...]
    }
}
else
{
    channel = entities.GetChannelById(googleShoppingChannelCredential.ChannelId);
    [...]
}

Mas com uma coalescência nula torna-se muito mais limpo

using (entities ?? Entities.GetEntities())
{
    channel = entities.GetChannelById(googleShoppingChannelCredential.ChannelId);
    [...]
}
PaulG
fonte
0

Eu usei assim:

for (int i = 0; i < result.Count; i++)
            {
                object[] atom = result[i];

                atom[3] = atom[3] ?? 0;
                atom[4] = atom[4] != null ? "Test" : string.Empty;
                atom[5] = atom[5] ?? "";
                atom[6] = atom[6] ?? "";
                atom[7] = atom[7] ?? "";
                atom[8] = atom[8] ?? "";
                atom[9] = atom[9] ?? "";
                atom[10] = atom[10] ?? "";
                atom[12] = atom[12] ?? false; 
            }
Muhammad Awais
fonte