Gibt es ein "Gegenteil" zum Null-Koaleszenz-Operator? (… In irgendeiner Sprache?)

92

Null-Koaleszenz bedeutet ungefähr return x, unless it is null, in which case return y

Ich brauche oft return null if x is null, otherwise return x.y

ich kann nutzen return x == null ? null : x.y;

Nicht schlecht, aber das nullin der Mitte stört mich immer - es scheint überflüssig. Ich würde so etwas bevorzugen return x :: x.y;, wo das, was folgt ::, nur ausgewertet wird, wenn das, was davor steht, nicht ausgewertet wird null.

Ich sehe dies fast als das Gegenteil von Null-Koaleszenz, gemischt mit einem knappen Inline-Null-Check, aber ich bin [ fast] ] sicher, dass es in C # keinen solchen Operator gibt.

Gibt es andere Sprachen, die einen solchen Operator haben? Wenn ja, wie heißt es?

(Ich weiß, dass ich eine Methode dafür in C # schreiben kann; ich verwende return NullOrValue.of(x, () => x.y);, aber wenn Sie etwas Besseres haben, würde ich das auch gerne sehen.)

Jay
quelle
Einige haben in C # nach so etwas wie x? .Y gefragt, aber nichts dergleichen existiert.
Anthony Pegram
1
@ Anthony Oh, das wäre schön. Vielen Dank.
Jay
2
In c ++ wäre das leicht auszudrücken return x ? x.y : NULL. Yay für die Konvertierung von Zeigertypen in Boolesche Werte!
Phil Miller
1
@ Novocrat, das ist eines der Dinge, die mich in C # am meisten irritieren, ist, dass sie dem C nicht gefolgt sind, wenn if (irgendetwas) = ​​true ist, außer wenn es if ist (0, false, null)
Chris Marisic
2
@ Chris: Das ist keine genaue Aussage über C. Wenn Sie eine nicht skalare Variable (z. B. eine Struktur) haben, können Sie diese nicht in einer Bedingung verwenden.
Phil Miller

Antworten:

62

Es gibt den nullsicheren DereferenzierungsoperatorIn Groovy (?.) ... Ich denke, das ist es, wonach Sie suchen.

(Es wird auch als sicherer Navigationsoperator bezeichnet .)

Beispielsweise:

homePostcode = person?.homeAddress?.postcode

Dies ergibt null, wenn person,person.homeAddress oder person.homeAddress.postcodeist null.

(Dies ist jetzt in C # 6.0 verfügbar, jedoch nicht in früheren Versionen.)

Jon Skeet
quelle
1
Groovy hat auch den "Elvis-Operator", der andere Standardwerte erlaubt als null:def bar = foo ?: "<null>"
Craig Stuntz
28
Ich nominiere diese Funktion für C # 5.0. Ich weiß oder kümmere mich nicht darum, was Groovy eigentlich ist, aber das ist intuitiv genug, um es in jeder Sprache zu verwenden.
György Andrasek
Es wird jedoch jetzt zu C # 6 hinzugefügt, hurra.
Jeff Mercado
1
Die sichere Navigation funktioniert für das triviale Beispiel, aber Sie müssen immer noch den ternären Operator verwenden, wenn Sie den Wert ungleich Null in einem Funktionsaufruf verwenden möchten, zum Beispiel : Decimal? post = pre == null ? null : SomeMethod( pre );. Es wäre schön, wenn ich es auf "Decimal? Post = pre :: SomeMethod (pre);" reduzieren könnte.
Dai
@Dai: Angesichts der Anzahl der Permutationen, die Sie sich wünschen könnten, bin ich mit dem, was wir haben, zufrieden genug.
Jon Skeet
36

Wir haben darüber nachgedacht,? zu C # 4. Es hat den Schnitt nicht geschafft; Es ist eine "nice to have" -Funktion, keine "gotta have" -Funktion. Wir werden es für hypothetische zukünftige Versionen der Sprache noch einmal in Betracht ziehen, aber ich würde nicht den Atem anhalten und warten, wenn ich Sie wäre. Mit der Zeit wird es wahrscheinlich nicht mehr entscheidend. :-)

Eric Lippert
quelle
Spielt die einfache Implementierung eines Features bei der Entscheidung, es hinzuzufügen, eine Rolle? Ich frage dies, weil die Implementierung dieses Operators eine ziemlich einfache Ergänzung der Sprache zu sein scheint. Ich bin kein Experte (nur ein Absolvent mit großer Liebe zu C #), bitte korrigieren Sie mich!
Nick Strupat
14
@nick: Sicher, die Implementierung von Lexer und Parser dauert fünf Minuten. Dann machen Sie sich Sorgen über Dinge wie "Geht die IntelliSense-Engine gut damit um?" und "Wie geht das Fehlerbehebungssystem damit um, wenn Sie das eingegeben haben? aber noch nicht das.?" und wie viele verschiedene Dinge macht "." meine in C # sowieso, und wie viele von ihnen verdienen es, eine zu haben? davor und wie sollten die Fehlermeldungen lauten, wenn Sie etwas falsch machen, und wie viel Test wird diese Funktion benötigen (Hinweis: viel). Und wie werden wir sie dokumentieren und die Änderung kommunizieren und ...
Eric Lippert
3
@nick: Um Ihre Frage zu beantworten - ja, die Implementierungskosten sind ein Faktor, aber ein kleiner. Auf der Ebene, auf der wir arbeiten, gibt es keine billigen Funktionen, nur mehr oder weniger teure Funktionen. Entwicklungsarbeit im Wert von fünf Dollar, damit der Parser im Fall des richtigen Codes funktioniert, kann leicht zu Design, Implementierung, Test, Dokumentation und Schulung im Wert von Zehntausenden von Dollar führen.
Eric Lippert
11
@EricLippert Ich denke, dies wäre eines der wichtigsten "nice to have", die C # so erfolgreich gemacht haben und auch perfekt dazu dienen würden, Code so präzise und ausdrucksstark wie möglich zu gestalten!
Konstantin
Ich würde gerne sehen, dass dies hinzugefügt wird. Was ich WIRKLICH möchte, ist der elvis-Operator: stackoverflow.com/questions/2929836/…
Chris Marisic
16

Wenn Sie eine spezielle Art von boolescher Kurzschlusslogik haben, können Sie dies tun (Javascript-Beispiel):

return x && x.y;

Wenn xnull ist, wird es nicht ausgewertet x.y.

Eric
quelle
2
Abgesehen davon schließt auch kurz auf 0, "" und NaN, also ist es nicht das Gegenteil von ??. Es ist das Gegenteil von ||.
Ritaj
7

Es fühlte sich einfach richtig an, dies als Antwort hinzuzufügen.

Ich denke, der Grund, warum es so etwas in C # nicht gibt, liegt darin, dass die umgekehrte Operation im Gegensatz zum Koaleszenzoperator (der nur für Referenztypen gültig ist) entweder einen Referenz- oder einen Werttyp (dh class xmit Mitglied) ergeben könnteint y - daher wäre dies leider der Fall in vielen Situationen unbrauchbar.

Ich sage jedoch nicht, dass ich es nicht sehen möchte!

Eine mögliche Lösung für dieses Problem wäre, dass der Bediener einen Werttypausdruck auf der rechten Seite automatisch auf null setzt. Aber dann haben Sie das Problem, dass, x.ywenn y ein int ist, tatsächlich ein zurückgegeben wirdint? was ein Schmerz wäre.

Eine andere, wahrscheinlich bessere Lösung wäre, dass der Operator den Standardwert (dh null oder null) für den Typ auf der rechten Seite zurückgibt, wenn der Ausdruck auf der linken Seite null ist. Aber dann haben Sie Probleme bei der Unterscheidung von Szenarien, aus denen tatsächlich eine Null / Null gelesen wurde x.yoder ob sie vom Operator für sicheren Zugriff bereitgestellt wurde.

Andras Zoltan
quelle
Als ich die Frage des OP zum ersten Mal las, tauchte genau dieses Problem in meinem Kopf auf, aber ich konnte nicht genau sagen, wie ich es artikulieren sollte. Es ist ein sehr ernstes Problem. +1
rmeador
Es ist kein ernstes Problem. Verwenden Sie einfach int?als Standardrückgabewert, und der Benutzer kann ihn in ein int ändern, wenn er möchte. Zum Beispiel, wenn ich eine Methode Lookupmit einem Standardwert von -1 aufrufen möchte, falls eine meiner Referenzen lautet null:foo?.Bar?.Lookup(baz) ?? -1
Qwertie
Wie wäre es ?. :mit einem ternären Operator, bei dem die rechte Seite vom gleichen Typ sein muss wie das entsprechende Element in der Mitte?
Supercat
6

Delphi hat den Operator: (anstatt.), Der nullsicher ist.

Sie dachten darüber nach, ein? Operator auf C # 4.0, um dasselbe zu tun, aber das hat den Hackklotz bekommen.

In der Zwischenzeit gibt es IfNotNull (), welche Art von Kratzern juckt . Es ist sicherlich größer als? oder :, aber Sie können damit eine Kette von Operationen erstellen, die keine NullReferenceException bei Ihnen verursachen, wenn eines der Mitglieder null ist.

48klocs
quelle
Können Sie bitte ein Beispiel für die Verwendung dieses Operators geben?
Max Carroll
1
Das ?.ist eine C # 6.0-Sprachfunktion, und ja, es macht genau das, was das OP hier verlangt hat. Besser spät als nie ^^
T_D
3

In Haskell können Sie den >>Operator verwenden:

  • Nothing >> Nothing ist Nothing
  • Nothing >> Just 1 ist Nothing
  • Just 2 >> Nothing ist Nothing
  • Just 2 >> Just 1 ist Just 1
dave4420
quelle
3

Haskell hat fmap, was in diesem Fall meiner Meinung nach gleichwertig ist Data.Maybe.map. Haskell ist rein funktional, also was Sie suchen, wäre

fmap select_y x

Wenn xist Nothing, diese zurückkehrt Nothing. Wenn xist Just object, diese zurückkehrt Just (select_y object). Nicht so hübsch wie die Punktnotation, aber da es sich um eine funktionale Sprache handelt, sind die Stile unterschiedlich.

Norman Ramsey
quelle
2

Mit PowerShell können Sie Eigenschaften (aber keine Aufrufmethoden) für eine Nullreferenz referenzieren. Wenn die Instanz null ist, wird null zurückgegeben. Sie können dies in jeder Tiefe tun. Ich hatte gehofft, dass die dynamische Funktion von C # 4 dies unterstützen würde, aber dies ist nicht der Fall.

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

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

Es ist nicht schön, aber Sie könnten eine Erweiterungsmethode hinzufügen, die so funktioniert, wie Sie es beschreiben.

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
quelle
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; }
}

Ich brauche diese Logik oft für 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"
J Bryan Price
quelle
1

Erstellen Sie irgendwo eine statische Instanz Ihrer Klasse mit den richtigen Standardwerten für die Mitglieder.

Beispielsweise:

z = new Thingy { y=null };

dann statt deiner

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

Du kannst schreiben

return (x ?? z).y;
Nick
quelle
Ich verwende diese Technik manchmal (z. B. (x ?? "") .ToString ()), aber sie ist nur für einige Datentypen wie PODs und Strings praktisch.
Qwertie
1

Der sogenannte "nullbedingte Operator" wurde in C # 6.0 und in Visual Basic 14 eingeführt.
In vielen Situationen kann er als genaues Gegenteil des nullkoaleszierenden Operators verwendet werden:

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
quelle