Czy istnieje „przeciwieństwo” zerowego operatora koalescencji? (… W jakimkolwiek języku?)

93

zerowa koalescencja przekłada się z grubsza na return x, unless it is null, in which case return y

Często potrzebuję return null if x is null, otherwise return x.y

mogę użyć return x == null ? null : x.y;

Nieźle, ale to nullw środku zawsze mi przeszkadza - wydaje się zbędne. Wolałbym coś takiego return x :: x.y;, w którym to, co następuje po tym, ::jest oceniane tylko wtedy, gdy to, co go poprzedza, nie jest null.

Widzę to jako prawie przeciwieństwo koalescencji zerowej, w pewnym sensie zmieszane ze zwięzłym, wbudowanym sprawdzaniem null, ale jestem [ prawie ] pewien, że nie ma takiego operatora w C #.

Czy są inne języki, które mają takiego operatora? Jeśli tak, jak to się nazywa?

(Wiem, że mogę napisać odpowiednią metodę w C #; używam return NullOrValue.of(x, () => x.y);, ale jeśli masz coś lepszego, też chciałbym to zobaczyć).

Sójka
źródło
Niektórzy prosili o coś takiego jak x? .Y w C #, ale nic takiego nie istnieje.
Anthony Pegram
1
@Anthony Och, byłoby pięknie. Dzięki.
Jay
2
W C ++ byłoby to łatwe do wyrażenia jako return x ? x.y : NULL. Hej za konwersję typów wskaźników na wartości logiczne!
Phil Miller
1
@Novelocrat to jedna z rzeczy, która najbardziej irytuje mnie w C #, to fakt, że nie stosowali się do C, jeśli (cokolwiek) = prawda, z wyjątkiem sytuacji, gdy jest (0, false, null)
Chris Marisic
2
@Chris: to nie jest dokładne stwierdzenie dotyczące C.Jeśli masz zmienną nieskalarną (taką jak struktura), nie możesz jej użyć w warunku.
Phil Miller

Odpowiedzi:

62

W Groovy jest operator dereferencji bezpieczny zerowo (?.) ... Myślę, że właśnie tego szukasz.

(Nazywa się to również operatorem bezpiecznej nawigacji ).

Na przykład:

homePostcode = person?.homeAddress?.postcode

To da null, jeśli person, person.homeAddresslub person.homeAddress.postcodejest null.

(Jest to teraz dostępne w C # 6.0, ale nie we wcześniejszych wersjach)

Jon Skeet
źródło
1
Groovy ma również „operator Elvisa”, który pozwala na wartości domyślne inne niż nullnp .:def bar = foo ?: "<null>"
Craig Stuntz
28
Nominację tej funkcji dla języka C # 5.0. Nie wiem lub nie obchodzi mnie, czym właściwie jest Groovy, ale jest to wystarczająco intuicyjne, aby używać go w dowolnym języku.
György Andrasek
Jednak teraz jest dodawany do C # 6, hura.
Jeff Mercado
1
Prace bezpiecznej nawigacji dla trywialny przykład pisał, ale nadal trzeba użyć operatora potrójny jeśli zamierzają wykorzystać wartość niezerową w wywołaniu funkcji, na przykład: Decimal? post = pre == null ? null : SomeMethod( pre );. Byłoby miło, gdybym mógł zredukować to do „Decimal? Post = pre :: SomeMethod (pre);”
Dai
@Dai: Biorąc pod uwagę liczbę permutacji, których możesz chcieć, jestem wystarczająco zadowolony z tego, co mamy.
Jon Skeet
36

Rozważaliśmy dodanie? do C # 4. Nie udało się; jest to funkcja „miło mieć”, a nie funkcja „trzeba mieć”. Rozważymy to ponownie w przypadku hipotetycznych przyszłych wersji języka, ale nie wstrzymywałbym oddechu, czekając, gdybym był tobą. W miarę upływu czasu prawdopodobnie nie stanie się to bardziej istotne. :-)

Eric Lippert
źródło
Czy łatwość implementacji funkcji wpływa na decyzję o jej dodaniu? Pytam o to, ponieważ implementacja tego operatora wydaje się być dość prostym, prostym dodatkiem do języka. Nie jestem ekspertem (tylko niedawnym absolwentem z wielką miłością do C #), więc popraw mnie!
Nick Strupat
14
@nick: Jasne, implementacja leksera i parsera to pięć minut pracy. Wtedy zaczynasz się martwić takimi rzeczami, jak „czy aparat IntelliSense sobie z tym radzi?” i „jak radzi sobie z tym system odzyskiwania po błędzie, gdy wpiszesz? ale nie. jeszcze?” i ile różnych rzeczy robi ”. znaczy w C # i tak, a ile z nich zasługuje na to? przed nim i jakie powinny być komunikaty o błędach, jeśli się pomylisz, i ile testów będzie potrzebować ta funkcja (wskazówka: dużo.) Oraz w jaki sposób zamierzamy to udokumentować i zakomunikować zmianę, i ...
Eric Lippert
3
@nick: Odpowiadając na twoje pytanie - tak, koszt wdrożenia jest czynnikiem, ale niewielkim. Nie ma tanich funkcji na poziomie, na którym pracujemy, są tylko mniej lub bardziej kosztowne funkcje. Praca programistów warta pięć dolarów, aby parser działał w przypadku poprawnego kodu, może z łatwością zmienić się w dziesiątki tysięcy dolarów na projektowanie, wdrażanie, testowanie, dokumentację i edukację.
Eric Lippert
11
@EricLippert Myślę, że byłby to jeden z MAJORSKICH „przyjemnych do posiadania”, które sprawiły, że C # odniósł taki sukces, a także doskonale spełniłby misję tworzenia kodu tak zwięzłego i wyrazistego, jak to tylko możliwe!
Konstantin
Chciałbym to dodać, NAPRAWDĘ chcę operatora elvisa: stackoverflow.com/questions/2929836/…
Chris Marisic
16

Jeśli masz specjalną logikę logiki zwarciowej, możesz to zrobić (przykład javascript):

return x && x.y;

Jeśli xjest null, nie zostanie oszacowany x.y.

Eric
źródło
2
Oprócz tego zwarcia na 0, "" i NaN, więc nie jest to przeciwieństwo ??. Jest przeciwieństwem ||.
ritaj
7

Po prostu wydawało się właściwe, aby dodać to jako odpowiedź.

Wydaje mi się, że powodem, dla którego nie ma czegoś takiego w C #, jest to, że w przeciwieństwie do operatora koalescencji (który jest ważny tylko dla typów referencyjnych), operacja odwrotna może dać typ referencyjny lub wartość (tj. class xZ elementem członkowskim int y- dlatego niestety byłoby bezużyteczne w wielu sytuacjach.

Nie mówię jednak, że nie chciałbym tego oglądać!

Potencjalne rozwiązanie tego problemu spowodowałoby, że operator automatycznie podniósłby wyrażenie typu wartości po prawej stronie do wartości null. Ale wtedy x.ypojawia się problem polegający na tym, że gdy y jest liczbą int, w rzeczywistości zwróci to, int?co byłoby uciążliwe.

Innym, prawdopodobnie lepszym rozwiązaniem byłoby zwrócenie przez operatora wartości domyślnej (tj. Null lub zero) dla typu po prawej stronie, jeśli wyrażenie po lewej stronie ma wartość null. Ale wtedy masz problemy z rozróżnieniem scenariuszy, w których faktycznie odczytano zero / null x.ylub czy został on dostarczony przez operatora bezpiecznego dostępu.

Andras Zoltan
źródło
kiedy po raz pierwszy przeczytałem pytanie OP, ten konkretny problem pojawił się w mojej głowie, ale nie mogłem określić, jak go wyrazić. To bardzo poważny problem. +1
rmeador
to nie jest poważny problem. Po prostu użyj int?jako domyślnej wartości zwracanej, a użytkownik może zmienić ją na int, jeśli chce. Na przykład, jeśli chciałbym wywołać jakąś metodę Lookupz domyślną wartością -1 w przypadku, gdy jedną z moich referencji jest null:foo?.Bar?.Lookup(baz) ?? -1
Qwertie
A co z ?. :operatorem trójskładnikowym, w którym prawa strona musi być tego samego typu, co odpowiedni element członkowski podany w środku?
supercat
6

Delphi ma operator: (zamiast.), Który jest bezpieczny dla wartości null.

Myśleli o dodaniu? operator do C # 4.0, aby zrobić to samo, ale to dostało blok do krojenia.

W międzyczasie istnieje IfNotNull (), który rodzaj zadrapań, które swędzi. Z pewnością jest większy niż? lub:, ale pozwala ci skomponować łańcuch operacji, który nie będzie miał wyjątku NullReferenceException, jeśli jeden z elementów członkowskich jest pusty.

48klocs
źródło
Czy możesz podać przykład użycia tego operatora?
Max Carroll
1
Jest ?.to funkcja języka C # 6.0 i tak, robi dokładnie to, o co poprosił tutaj OP. Lepiej późno niż wcale ^^
T_D
3

W Haskell możesz użyć >>operatora:

  • Nothing >> Nothing jest Nothing
  • Nothing >> Just 1 jest Nothing
  • Just 2 >> Nothing jest Nothing
  • Just 2 >> Just 1 jest Just 1
dave4420
źródło
3

Haskell tak zrobił fmap, co w tym przypadku wydaje mi się równoważne Data.Maybe.map. Haskell jest czysto funkcjonalny, więc to, czego szukasz, byłoby

fmap select_y x

Jeśli xtak Nothing, to zwraca Nothing. Jeśli xtak Just object, to zwraca Just (select_y object). Nie tak ładne jak notacja kropkowa, ale biorąc pod uwagę, że jest to język funkcjonalny, style są różne.

Norman Ramsey
źródło
2

PowerShell pozwala odwoływać się do właściwości (ale nie wywoływać metod) w odwołaniu o wartości null i zwróci wartość null, jeśli wystąpienie ma wartość null. Możesz to zrobić na dowolnej głębokości. Miałem nadzieję, że dynamiczna funkcja C # 4 będzie to obsługiwać, ale tak nie jest.

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

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

To nie jest ładne, ale możesz dodać metodę rozszerzenia, która będzie działać tak, jak opisujesz.

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);
Josh
źródło
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; }
}

Często potrzebuję tej logiki dla ciągów:

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"
J Bryan Price
źródło
1

Utwórz gdzieś statyczną instancję swojej klasy ze wszystkimi właściwymi wartościami domyślnymi dla członków.

Na przykład:

z = new Thingy { y=null };

to zamiast twojego

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

Możesz pisać

return (x ?? z).y;
Nacięcie
źródło
Czasami używam tej techniki (np. (X ?? "") .ToString ()), ale jest to praktyczne tylko w przypadku kilku typów danych, takich jak PODs i stringi.
Qwertie
1

Tak zwany „operator warunkowy zerowy” został wprowadzony w C # 6.0 i Visual Basic 14.
W wielu sytuacjach może być używany jako dokładne przeciwieństwo operatora koalescencji zerowej:

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

Jpsy
źródło