Operador de coalescência de propriedades para C #

9

O operador de coalescência nula em c # permite reduzir o código

  if (_mywidget == null)
     return new Widget();
  else
     return _mywidget;

Até:

  return _mywidget ?? new Widget();

Eu continuo achando que um operador útil que eu gostaria de ter em C # seria aquele que lhe permitisse retornar uma propriedade de um objeto ou algum outro valor se o objeto for nulo. Então eu gostaria de substituir

  if (_mywidget == null)
     return 5;
  else
     return _mywidget.Length;

Com:

  return _mywidget.Length ??! 5;

Não consigo deixar de pensar que deve haver alguma razão para esse operador não existir. É um cheiro de código? Existe alguma maneira melhor de escrever isso? (Estou ciente do padrão de objeto nulo, mas parece um exagero usá-lo para substituir essas quatro linhas de código.)

Ben Fulton
fonte
11
O operador condicional seria suficiente aqui?
Anon.
11
Alguém escreveu algo que lhe permite fazer algo assim: string location = employee.office.address.location ?? "Desconhecido"; . Isso definirá o local como "Desconhecido" quando um dos objetos ( funcionário , escritório , endereço ou local ) for nulo. Infelizmente, não me lembro quem o escreveu ou onde ele o postou. Se o encontrar novamente, vou publicá-lo aqui!
21411 Kristof Claes
11
Essa pergunta teria muito mais tração no StackOverflow.
Job
2
??!é um operador em C ++. :-)
James McNellis
3
Olá 2011, só liguei para dizer que c # é a obtenção de um operador de navegação segura
Nathan Cooper

Respostas:

5

Eu quero muito um recurso de linguagem C # que permita executar com segurança x.Prop.SomeOtherProp.ThirdProp sem precisar verificar nulo em cada um deles. Em uma base de código em que eu tive que fazer muito esses tipos de "percursos de propriedade profunda", escrevi um método de extensão chamado Navigate que engoliu NullReferenceExceptions e, em vez disso, retornou um valor padrão. Então eu poderia fazer algo assim:

var propVal = myThing.Navigate(x => x.PropOne.PropTwo.PropThree.PropFour, defaultValue);

A desvantagem disso é que ele tem um cheiro de código diferente: exceções engolidas. Se você quiser fazer algo assim "certo", poderá usar o lamdba como uma expressão e modificar a expressão rapidamente para adicionar as verificações nulas em torno de cada acessador de propriedade. Eu fui "rápido e sujo" e não o implementei dessa maneira "melhor". Mas talvez, quando eu tiver alguns ciclos de pensamento de reserva, revisite e publique em algum lugar.

Para responder mais diretamente à sua pergunta, acho que o motivo não é um recurso, porque o custo de implementação do recurso excede o benefício de ter o recurso. A equipe de C # precisa escolher quais recursos focar, e esse ainda não chegou ao topo. Apenas um palpite, embora eu não tenha nenhuma informação privilegiada.

RationalGeek
fonte
11
Exatamente o que eu pensava, mas o desempenho da maneira segura seria muito ruim, eu acho (por causa das muitas Expression.Compile ()) ... Pena que não foi implementada em C # (algo como Obj.?PropA.?Prop1)
Guillaume86
x.PropOne.PropTwo.PropThree.PropFour é uma má escolha de design, pois viola a Lei de Demeter. Redesenhe seus métodos / classes para que você não precise usá-lo.
Justin Shield
Evitar irremediavelmente o acesso profundo a propriedades à luz da Lei de Deméter é tão perigoso quanto normalizar irracionalmente um banco de dados. Você pode ir longe demais.
21414 Mir
3
No C # 6.0, isso é possível usando o operador nulo-condicional (também conhecido como operador Elvis) msdn.microsoft.com/en-us/magazine/dn802602.aspx
victorvartan
15

Acredito que você poderá fazer isso com o C # 6 agora:

return _mywidget?.Length ?? 5;

Observe o operador condicional nulo ?.no lado esquerdo. _mywidget?.Lengthretorna nulo se _mywidgetfor nulo.

Um operador ternário simples pode ser mais fácil de ler, como outros sugeriram.

Chris Nolet
fonte
9

Na verdade, é apenas especulação sobre o motivo de não terem feito isso. Afinal, eu não acredito? estava na primeira versão do C #.

De qualquer forma, eu usaria o operador condicional e o chamaria de dia:

return (_mywidget != null) ? _mywidget.Length : 5;

O ?? é apenas um atalho para o operador condicional.

whatsisname
fonte
5
Pessoalmente, isso é tão ruim (imho) quanto ter um operador para fazer isso em primeiro lugar. Você quer algo de um objeto ... esse objeto pode não estar lá ... então vamos fornecer 5a resposta, caso não esteja lá. Você precisa observar o seu design antes de começar a solicitar operadores especiais.
Moo-Juice
11
@ Moo-Juice Estou apenas imaginando a pessoa que mantém esse código: 'Por que isso está me dando 5? Hummm. O que?!'
msarchet
4

Parece com o operador ternário:

return (_mywidget != NULL) ? _mywidget : new Widget();
Martin York
fonte
3

Pessoalmente, acho que isso se deve à legibilidade. No primeiro exemplo, o ??traduz muito bem como "Você está aí? Não, vamos fazer isso".

No segundo exemplo, ??!é claramente WTF e não significa nada para o programador .... o objeto não existe, então não consigo chegar à propriedade, então retornarei 5. O que isso significa? O que é 5? Como chegamos à conclusão de que 5 é um bom valor?

Em suma, o primeiro exemplo faz sentido ... não existe, é novo . No segundo, está aberto ao debate.

Moo-Juice
fonte
2
Bem, você tem que ignorar o fato de que ?! não existe. Imagine se ??! era comum e, se fosse usado, toma decisões com base nisso.
whatsisname 26/01
3
Isso me lembra o chamado "operador WTF" em C ++ (isto realmente funciona: (foo() != ERROR)??!??! cerr << "Error occurred" << endl;.)
Tamás Szelei
@whatsisname, foi mais uma reflexão sobre o fato de ser apropriado para a decisão que está sendo tomada. Criar um objeto porque não estava lá faz sentido. Atribuir um valor arbitrário que não significa nada para o leitor, porque você não pode obter uma propriedade de algo, é realmente um cenário da WTF.
Moo-Juice
Eu acho que você está sendo pego no meu exemplo. Talvez 0 seja melhor que 5. Talvez a propriedade seja um objeto, por isso, se _mywidget for nulo, você deseja retornar um novo foodle (). Eu discordo completamente disso? é mais legível que ??! embora :)
Ben Fulton
@ Ben, embora eu concorde que o foco no próprio operador não empresta muito à sua pergunta, estou traçando um paralelo com a percepção do programador e isso arbitrariamente 5. Eu realmente acho que há mais um problema que você precisa fazer assim, enquanto no seu primeiro exemplo, a necessidade é bastante óbvia, especialmente em coisas como gerenciadores de cache.
Moo-Juice
2

Eu acho que as pessoas estão muito envolvidas no "5" que você está retornando ... talvez 0 fosse melhor :)

Enfim, acho que o problema é que na ??!verdade não é um operador independente. Considere o que isso significa:

var s = myString ??! "";

Nesse caso, não faz sentido: só faz sentido se o operando do lado esquerdo for um acessador de propriedades. Ou então:

var s = Foo(myWidget.Length) ??! 0;

Há um acessador de propriedades lá, mas ainda acho que não faz sentido (se myWidgeté null, isso significa que não ligamos Foo()?), Ou isso é apenas um erro?

Eu acho que o problema é que simplesmente não se encaixa tão naturalmente na língua quanto o ??faz.

Dean Harding
fonte
1

Alguém criou uma classe de utilidade que faria isso por você. Mas não consigo encontrar. O que encontrei foi semelhante nos fóruns do MSDN (veja a segunda resposta).

Com um pouco de trabalho, você pode estendê-lo para avaliar chamadas de método e outras expressões que a amostra não suporta. Você também pode estendê-lo para aceitar um valor padrão.

Michael Brown
fonte
1

Eu entendo de onde você está vindo. Parece que todo mundo está se envolvendo no eixo com o exemplo que você forneceu. Embora eu concorde que os números mágicos sejam uma péssima idéia, seria usado o equivalente a um operador de coalescência nulo para determinadas propriedades.

Por exemplo, você tem objetos vinculados a um mapa e deseja que eles estejam na elevação adequada. Os mapas DTED podem ter buracos em seus dados, portanto, é possível ter um valor nulo, além de valores na faixa de algo entre -100 a ~ 8900 metros. Você pode querer algo como:

mapObject.Altitude = mapObject.Coordinates.DtedAltitude ?? DEFAULT_ALTITUDE;

O operador de coalescência nula nesse caso preencheria a altitude padrão se as coordenadas ainda não estivessem definidas ou o objeto Coordenadas não pudesse carregar dados DTED para esse local.

Eu vejo isso como muito valioso, mas minha especulação sobre o motivo de não ter sido feito é limitada à complexidade do compilador. Pode haver alguns casos em que o comportamento não seria tão previsível.

Berin Loritsch
fonte
1

Quando não estou lidando com aninhamentos profundos, usei isso (antes do operador coalescente nulo introduzido no C # 6). Para mim, é bem claro, mas talvez seja porque eu estou acostumado, outras pessoas podem achar isso confuso.

return ( _mywidget ?? new MyWidget() {length = defaultLength}).Length;

Obviamente, isso nem sempre é aplicável, pois às vezes a propriedade que você precisa acessar não pode ser definida diretamente ou quando a construção do objeto em si é cara, entre outros motivos.

Outra alternativa é usar o padrão NullObject . Você define uma única instância estática da classe com valores padrão sensíveis e usa-a.

return ( _mywidget ?? myWidget.NullInstance).Length;
andyroschy
fonte
0

Números mágicos são geralmente um cheiro ruim. Se o 5 for um número arbitrário, é melhor explicá-lo para que seja documentado com mais clareza. Uma exceção na minha opinião seria se você tiver uma coleção de valores que estão atuando como valores padrão em um contexto específico.

Se você tiver um conjunto de valores padrão para um objeto, poderá subclassificá-lo para criar uma instância singleton padrão.

Algo como: return (myobj ?? default_obj) .Length

Chris Quenelle
fonte
0

A propriedade Length geralmente é um tipo de valor, portanto, não pode ser nulo; portanto, o operador de coalescência nula não faz sentido aqui.

Mas você pode fazer isso com uma propriedade que é um tipo de referência, por exemplo: var window = App.Current.MainWindow ?? new Window();

peancor
fonte
O exemplo está tentando considerar o que aconteceria na sua situação se Current fosse nulo. Seu exemplo seria travado ... mas ter um operador que possa se unir em qualquer lugar da cadeia de direção incorreta seria útil.
Mir
-1

Eu já vi alguém com uma idéia semelhante antes, mas na maioria das vezes você também deseja atribuir o novo valor ao campo nulo, algo como:

return _obj ?? (_obj = new Class());

então a ideia era combinar a atribuição ??e =em:

return _obj ??= new Class();

Eu acho que isso faz mais sentido do que usar um ponto de exclamação.

Essa ideia não é minha, mas eu realmente gosto :)

chakrit
fonte
11
Você é vítima do pobre exemplo? Seu exemplo pode ser abreviado para que return _obj ?? new Class();não seja necessário o ??=aqui.
Matt Ellen
@ Matt Ellen, você entendeu errado. A atribuição é pretendida . Estou sugerindo uma alternativa diferente. Não é exatamente como o que o OP pediu, mas acredito que será igualmente útil.
Página
2
por que você deseja a tarefa se está prestes a retornar um valor?
Matt Ellen
inicialização lenta?
chakrit