Existe um “oposto” ao operador de coalescência nulo? (... em qualquer idioma?)


92

coalescência nula se traduz aproximadamente em return x, unless it is null, in which case return y

Eu frequentemente preciso return null if x is null, otherwise return x.y

Eu posso usar return x == null ? null : x.y;

Não é ruim, mas isso nullno meio sempre me incomoda - parece supérfluo. Eu prefiro algo como return x :: x.y;, onde o que vem depois de ::é avaliado apenas se o que vem antes não é null.

Eu vejo isso como quase o oposto da coalescência nula, meio que misturada com uma verificação nula em linha e concisa, mas estou [ quase ] certo de que não existe tal operador em C #.

Existem outras linguagens que possuem esse operador? Se sim, como é chamado?

(Eu sei que posso escrever um método para ele em C #; eu uso return NullOrValue.of(x, () => x.y);, mas se você tiver algo melhor, gostaria de ver isso também.)


Alguns pediram algo como x? .Y em C #, mas não existe nada parecido.
Anthony Pegram

1
@Anthony Oh, isso seria lindo. Obrigado.
Jay

2
Em c ++, isso seria fácil de expressar como return x ? x.y : NULL. Oba por converter tipos de ponteiro em booleanos!
Phil Miller

1
@Novelocrat é uma das coisas que mais me irrita em C # é que eles não seguiram o C se if (qualquer coisa) = verdadeiro exceto quando for if (0, falso, nulo)
Chris Marisic

2
@Chris: essa não é uma afirmação precisa sobre C. Se você tiver uma variável não escalar (como uma estrutura), não poderá usá-la em uma condição.
Phil Miller

Respostas:


62

Existe o operador de desreferenciação null-safe (?.) No Groovy ... Acho que é isso que você quer.

(Também é chamado de operador de navegação segura .)

Por exemplo:

homePostcode = person?.homeAddress?.postcode

Isso dará nulo se person, person.homeAddressou person.homeAddress.postcodefor nulo.

(Isso agora está disponível em C # 6.0, mas não em versões anteriores)


1
Groovy também tem o "operador de Elvis", que permite valores padrão diferentes de null, por exemplo:def bar = foo ?: "<null>"
Craig Stuntz

28
Eu indico esse recurso para C # 5.0. Não sei ou não me importo com o que Groovy realmente é, mas isso é intuitivo o suficiente para ser usado em qualquer idioma.
György Andrasek

No entanto, ele está sendo adicionado ao C # 6 agora, hooray.
Jeff Mercado

1
Obras Safe-navegação para o exemplo trivial publicado, mas você ainda precisa usar o operador ternário, se você pretende usar o valor não nulo em uma chamada de função, por exemplo: Decimal? post = pre == null ? null : SomeMethod( pre );. Seria bom se eu pudesse reduzi-lo para "Decimal? Post = pre :: SomeMethod (pre);"
Dai

@Dai: Dado o número de permutações que você pode querer, estou bastante feliz com o que temos.
Jon Skeet

36

Nós consideramos adicionar?. para C # 4. Não fez o corte; é um recurso "bom ter", não um recurso "preciso ter". Vamos considerá-lo novamente para hipotéticas versões futuras do idioma, mas eu não prenderia a respiração esperando se fosse você. Não é provável que se torne mais crucial com o passar do tempo. :-)


A facilidade de implementação de um recurso influencia a decisão de adicioná-lo? Eu pergunto isso porque implementar esse operador parece uma adição bastante direta e simples à linguagem. Não sou nenhum especialista (apenas um recém-formado com grande amor por C #), então me corrija!
Nick Strupat

14
@nick: Claro, implementar o lexer e o analisador leva cinco minutos. Então você começa a se preocupar com coisas como "o mecanismo IntelliSense lida bem com isso?" e "como o sistema de recuperação de erros lida com isso quando você digitou? mas não. ainda?" e quantas coisas diferentes faz "." significa em C # de qualquer maneira, e quantos deles merecem ter um? antes dele e quais devem ser as mensagens de erro se você errar, e de quantos testes esse recurso vai precisar (dica: muitos). E como vamos documentá-lo e comunicar a mudança, e ...
Eric Lippert,

3
@nick: Para responder à sua pergunta - sim, o custo de implementação é um fator, mas pequeno. Não existem recursos baratos no nível em que trabalhamos, apenas recursos mais ou menos caros. Cinco dólares de trabalho de desenvolvimento para fazer o analisador funcionar no caso do código correto podem facilmente se transformar em dezenas de milhares de dólares em design, implementação, teste, documentação e educação.
Eric Lippert,

11
@EricLippert Acho que este seria um dos PRINCIPAIS "bons de ter", que tornou o C # tão bem-sucedido e também serviria perfeitamente para a missão de tornar o código o mais conciso e expressivo possível!
Konstantin,

Eu gostaria de ver adicionado, o que eu REALMENTE quero é o operador Elvis: stackoverflow.com/questions/2929836/…
Chris Marisic

16

Se você tiver um tipo especial de lógica booleana de curto-circuito, poderá fazer isso (exemplo de javascript):

return x && x.y;

Se xfor nulo, não será avaliado x.y.


2
Exceto que isso também causa curto-circuito em 0, "" e NaN, então não é o oposto de ??. É o oposto de ||.
ritaj

7

Parecia certo adicionar isso como uma resposta.

Eu acho que a razão pela qual não existe tal coisa em C # é porque, ao contrário do operador de coalescência (que é válido apenas para tipos de referência), a operação reversa pode render uma referência ou um tipo de valor (ou seja, class xcom membro int y- portanto, infelizmente seria inutilizável em muitas situações.

Não estou dizendo, porém, que não gostaria de ver isso!

Uma solução potencial para esse problema seria o operador elevar automaticamente uma expressão de tipo de valor no lado direito para um valor anulável. Mas então você tem o problema de que x.yonde y é um int, na verdade retornará um int?que seria uma dor.

Outra solução, provavelmente melhor, seria o operador retornar o valor padrão (ou seja, nulo ou zero) para o tipo do lado direito se a expressão à esquerda for nula. Mas então você tem problemas para distinguir cenários onde um zero / nulo foi realmente lido x.you se foi fornecido pelo operador de acesso seguro.


quando li a pergunta do OP pela primeira vez, esse problema exato surgiu na minha cabeça, mas eu não conseguia definir como articulá-lo. É um problema muito sério. +1
rmeador

não é um problema sério. Basta usar int?como valor de retorno padrão e o usuário pode alterá-lo para um int, se desejar. Por exemplo, se eu gostaria de chamar algum método Lookupcom um valor padrão de -1 no caso de uma das minhas referências ser null:foo?.Bar?.Lookup(baz) ?? -1
Qwertie

Que tal ?. :ser um operador ternário, onde o lado direito deve ser do mesmo tipo que o membro apropriado dado no meio?
supercat de

6

O Delphi tem o operador: (em vez de.), Que é seguro para nulos.

Eles estavam pensando em adicionar um?. operador para C # 4.0 para fazer o mesmo, mas isso pegou o bloco de corte.

Nesse ínterim, há IfNotNull () que tipo de coça essa coceira. Certamente é maior do que?. ou:, mas permite que você componha uma cadeia de operações que não exigirá uma NullReferenceException para você se um dos membros for nulo.


Você pode dar um exemplo de como usar este operador, por favor?
Max Carroll de

1
O ?.é um recurso da linguagem C # 6.0 e, sim, faz exatamente o que o OP pediu aqui. Melhor tarde do que nunca ^^
T_D

3

Em Haskell, você pode usar o >>operador:

  • Nothing >> Nothing é Nothing
  • Nothing >> Just 1 é Nothing
  • Just 2 >> Nothing é Nothing
  • Just 2 >> Just 1 é Just 1

3

Haskell tem fmap, que neste caso eu acho que é equivalente a Data.Maybe.map. Haskell é puramente funcional, então o que você está procurando seria

fmap select_y x

Se xfor Nothing, isso retorna Nothing. Se xfor Just object, isso retorna Just (select_y object). Não é tão bonito quanto a notação de ponto, mas por ser uma linguagem funcional, os estilos são diferentes.


2

O PowerShell permite que você faça referência a propriedades (mas não a métodos de chamada) em uma referência nula e retornará nulo se a instância for nula. Você pode fazer isso em qualquer profundidade. Eu esperava que o recurso dinâmico do C # 4 fosse compatível com isso, mas não é.

$x = $null
$result = $x.y  # $result is null

$x = New-Object PSObject
$x | Add-Member NoteProperty y 'test'
$result = $x.y  # $result is 'test'

Não é bonito, mas você pode adicionar um método de extensão que funcione da maneira que você descreve.

public static TResult SafeGet<T, TResult>(this T obj, Func<T, TResult> selector) {
    if (obj == null) { return default(TResult); }
    else { return selector(obj); }
}

var myClass = new MyClass();
var result = myClass.SafeGet(x=>x.SomeProp);

2
public class ok<T> {
    T s;
    public static implicit operator ok<T>(T s) { return new ok<T> { s = s }; }
    public static implicit operator T(ok<T> _) { return _.s; }

    public static bool operator true(ok<T> _) { return _.s != null; }
    public static bool operator false(ok<T> _) { return _.s == null; }
    public static ok<T> operator &(ok<T> x, ok<T> y) { return y; }
}

Freqüentemente preciso desta lógica para strings:

using ok = ok<string>;

...

string bob = null;
string joe = "joe";

string name = (ok)bob && bob.ToUpper();   // name == null, no error thrown
string boss = (ok)joe && joe.ToUpper();   // boss == "JOE"

1

Crie uma instância estática de sua classe em algum lugar com todos os valores padrão corretos para os membros.

Por exemplo:

z = new Thingy { y=null };

então em vez de seu

return x != null ? x.y : null;

você pode escrever

return (x ?? z).y;

Eu uso essa técnica às vezes (por exemplo, (x ?? "") .ToString ()), mas ela só é prática em alguns tipos de dados, como PODs e strings.
Qwertie


1

O chamado "operador nulo-condicional" foi introduzido no C # 6.0 e no Visual Basic 14.
Em muitas situações, ele pode ser usado como o oposto exato do operador nulo-coalescente:

int? length = customers?.Length; // null if customers is null   
Customer first = customers?[0];  // null if customers is null  
int? count = customers?[0]?.Orders?.Count();  // null if customers, the first customer, or Orders is null

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/null-conditional-operators

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.