Einzigartige Möglichkeiten zur Verwendung des Null Coalescing-Operators [geschlossen]


164

Ich weiß, dass die Standardmethode für die Verwendung des Null-Koaleszenzoperators in C # darin besteht, Standardwerte festzulegen.

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

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

Aber wofür kann ??man sonst noch verwenden? Es scheint nicht so nützlich zu sein wie der ternäre Operator, abgesehen davon, dass es prägnanter und leichter zu lesen ist als:

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

Angesichts der Tatsache, dass weniger über den Null-Koaleszenz-Operator Bescheid wissen ...

  • Hast du ??für etwas anderes verwendet?

  • Ist ??notwendig, oder sollten Sie nur den ternären Operator verwenden (mit dem die meisten vertraut sind)

Antworten:


216

Zunächst einmal ist es viel einfacher zu verketten als das Standard-Ternär:

string anybody = parm1 ?? localDefault ?? globalDefault;

vs.

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

Es funktioniert auch gut, wenn das null-mögliche Objekt keine Variable ist:

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

vs.

string anybody = (Parameters["Name"] != null ? Parameters["Name"] 
                 : (Settings["Name"] != null) ? Settings["Name"]
                 :  GlobalSetting["Name"];

12
Die Verkettung ist ein großes Plus für den Bediener, entfernt eine Reihe redundanter IFs
Chakrit

1
Ich habe es heute nur verwendet, um einen einfachen IF-Block zu ersetzen, den ich geschrieben habe, bevor ich etwas über den ternären oder den Null-Koaleszenz-Operator wusste. Der wahre und der falsche Zweig der ursprünglichen IF-Anweisung haben dieselbe Methode aufgerufen und eines ihrer Argumente durch einen anderen Wert ersetzt, wenn eine bestimmte Eingabe NULL ist. Mit dem Null-Koaleszenz-Operator ist es ein Aufruf. Dies ist sehr effektiv, wenn Sie eine Methode haben, die zwei oder mehr solcher Substitutionen benötigt!
David A. Gray

177

Ich habe es als Lazy Load One-Liner verwendet:

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

Lesbar? Entscheide dich selbst.


6
Hmmm, Sie haben ein Gegenbeispiel zu "Warum sollte jemand es als verschleierte WENN verwenden wollen" gefunden ... das ist für mich eigentlich sehr lesbar.
Godeke

6
Ich vermisse vielleicht etwas (ich benutze meistens Java), aber gibt es dort keine Rennbedingungen?
Justin K

9
@Justin K - Es gibt nur eine Race-Bedingung, wenn mehrere Threads auf die LazyProp-Eigenschaft desselben Objekts zugreifen. Es kann leicht mit einer Sperre repariert werden, wenn die Thread-Sicherheit jeder Instanz erforderlich ist. In diesem Beispiel ist dies eindeutig nicht erforderlich.
Jeffrey L Whitledge

5
@ Jeffrey: Wenn es klar wäre, hätte ich die Frage nicht gestellt. :) Als ich dieses Beispiel sah, dachte ich sofort an ein Singleton-Mitglied, und da ich den größten Teil meiner Codierung in einer Multithread-Umgebung mache ... Aber ja, wenn wir davon ausgehen, dass der Code korrekt ist, ist nichts Zusätzliches unnötig.
Justin K

7
Es muss kein Singleton sein, um eine Rennbedingung zu haben. Nur eine gemeinsam genutzte Instanz der Klasse, die LazyProp enthält, und mehrere Threads, die auf LazyProp zugreifen. Lazy <T> ist eine bessere Möglichkeit, dies zu tun, und standardmäßig threadsicher (Sie können die Thread-Sicherheit von Lazy <T> ändern).
Niall Connaughton

52

Ich habe es auf zwei "etwas merkwürdige" Arten nützlich gefunden:

  • Als Alternative für einen outParameter beim Schreiben von TryParseRoutinen (dh Rückgabe des Nullwerts, wenn die Analyse fehlschlägt)
  • Als "Weiß nicht" -Darstellung für Vergleiche

Letzteres benötigt etwas mehr Informationen. Wenn Sie einen Vergleich mit mehreren Elementen erstellen, müssen Sie normalerweise prüfen, ob der erste Teil des Vergleichs (z. B. Alter) eine endgültige Antwort liefert, und der nächste Teil (z. B. Name) nur, wenn der erste Teil nicht geholfen hat. Wenn Sie den Null-Koaleszenz-Operator verwenden, können Sie ziemlich einfache Vergleiche schreiben (ob zur Reihenfolge oder zur Gleichheit). Verwenden Sie beispielsweise einige Hilfsklassen in 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;
}

Zugegeben, ich habe jetzt ProjectionComparer in MiscUtil, zusammen mit einigen Erweiterungen, die so etwas noch einfacher machen - aber es ist immer noch ordentlich.

Dasselbe kann für die Überprüfung der Referenzgleichheit (oder Nullheit) zu Beginn der Implementierung von Gleichheit durchgeführt werden.


Ich mag, was Sie mit dem PartialComparer gemacht haben, habe aber nach Fällen gesucht, in denen ich die ausgewerteten Ausdrucksvariablen behalten muss. Ich bin nicht mit Lambdas und Erweiterungen vertraut. Könnten Sie also sehen, ob das Folgende einem ähnlichen Muster entspricht (dh funktioniert es)? stackoverflow.com/questions/1234263/#1241780
maxwellb

33

Ein weiterer Vorteil besteht darin, dass der ternäre Operator eine doppelte Auswertung oder eine temporäre Variable benötigt.

Betrachten Sie dies zum Beispiel:

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

Während Sie mit dem ternären Operator arbeiten, bleibt Ihnen entweder Folgendes übrig:

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

das MyMethod zweimal aufruft, oder:

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

In beiden Fällen ist der Null-Koaleszenz-Operator sauberer und effizienter.


1
+1. Dies ist ein wichtiger Grund, warum ich den Null-Koaleszenz-Operator mag. Dies ist besonders nützlich, wenn Anrufe MyMethod()irgendwelche Nebenwirkungen haben.
Ein CVn

Wenn MyMethod()sich außerhalb der Rückgabe eines Werts keine Auswirkungen ergeben, kann der Compiler ihn nicht zweimal aufrufen, sodass Sie sich hier in den meisten Fällen wirklich keine Gedanken über die Effizienz machen müssen.
TinyTimZamboni

Es hält die Dinge auch IMHO besser lesbar, wenn MyMethod()es sich um eine verkettete Folge von gepunkteten Objekten handelt. Beispiel:myObject.getThing().getSecondThing().getThirdThing()
xdhmoore

@TinyTimZamboni, haben Sie eine Referenz für dieses Verhalten des Compilers?
Kuba Wyrostek

@KubaWyrostek Ich habe keine Kenntnisse über die spezifischen Funktionen des C # -Compilers, aber ich habe einige Erfahrungen mit der statischen Compilertheorie mit llvm. Es gibt eine Reihe von Ansätzen, mit denen ein Compiler einen solchen Aufruf optimieren kann. Bei der globalen Wertnummerierung wird festgestellt, dass die beiden Aufrufe MyMethodin diesem Zusammenhang identisch sind, sofern es sich MyMethodum eine Pure-Funktion handelt. Eine andere Option ist das automatische Speichern oder das Schließen der Funktion im Cache. Auf der anderen Seite: en.wikipedia.org/wiki/Global_value_numbering
TinyTimZamboni

23

Eine andere zu berücksichtigende Sache ist, dass der Koaleszenzoperator die get-Methode einer Eigenschaft nicht zweimal aufruft, wie dies beim ternären Operator der Fall ist.

Es gibt also Szenarien, in denen Sie ternär nicht verwenden sollten, zum Beispiel:

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

Wenn du benutzt:

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

Der Getter wird zweimal aufgerufen und die countVariable ist gleich 2, und wenn Sie verwenden:

var b = a.Prop ?? 0

Die countVariable ist wie gewünscht gleich 1.


4
Dies verdient mehr Gegenstimmen. Ich habe sooo viele Male gelesen , das ??ist äquivalent zu ?:.
Kuba Wyrostek

1
Gültiger Punkt, wenn ein Getter zweimal aufgerufen wird. Aber in diesem Beispiel würde ich ein schlechtes Designmuster als einen solchen irreführend benannten Getter betrachten, um tatsächlich Änderungen am Objekt vorzunehmen.
Linas

15

Der größte Vorteil, den ich für den ??Operator finde, besteht darin, dass Sie nullbare Werttypen leicht in nicht nullbare Typen konvertieren können:

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

Ich verwende dies häufig in Linq-Abfragen:

Dictionary<int, int?> PurchaseQuantities;
// PurchaseQuantities populated via ASP .NET MVC form.
var totalPurchased = PurchaseQuantities.Sum(kvp => kvp.Value ?? 0);
// totalPurchased is int, not int?

Ich bin hier vielleicht etwas spät dran, aber das zweite Beispiel wird werfen, wenn kvp == null. Und hat tatsächlich Nullable<T>eine GetValueOrDefaultMethode, die ich normalerweise benutze.
CompuChip

6
KeyValuePair ist ein Werttyp im .NET Framework. Wenn Sie also auf eine seiner Eigenschaften zugreifen, wird niemals eine Nullreferenzausnahme ausgelöst. msdn.microsoft.com/en-us/library/5tbh8a42(v=vs.110).aspx
Ryan

9

Ich habe verwendet? in meiner Implementierung von IDataErrorInfo:

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

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

Wenn sich eine einzelne Eigenschaft in einem "Fehler" -Zustand befindet, wird dieser Fehler angezeigt, andernfalls wird null angezeigt. Funktioniert sehr gut.


Interessant. Sie verwenden "dies" als Eigenschaft. Das habe ich noch nie gemacht.
Armstrongest

Ja, es ist Teil der Funktionsweise von IDataErrorInfo. Im Allgemeinen ist diese Syntax nur für Auflistungsklassen nützlich.
Matt Hamilton

4
Sie speichern Fehlermeldungen in this["Name"], this["Address"]etc ??
Andrew

7

Sie können den Null-Koaleszenz-Operator verwenden, um den Fall, in dem kein optionaler Parameter festgelegt ist, etwas sauberer zu gestalten:

public void Method(Arg arg = null)
{
    arg = arg ?? Arg.Default;
    ...

Wäre es nicht großartig, wenn diese Zeile so geschrieben werden könnte arg ?= Arg.Default?
Jesse de Wit

6

Ich verwende gerne den Null-Koaleszenz-Operator, um bestimmte Eigenschaften verzögert zu laden.

Ein sehr einfaches (und erfundenes) Beispiel, um meinen Standpunkt zu veranschaulichen:

public class StackOverflow
{
    private IEnumerable<string> _definitions;
    public IEnumerable<string> Definitions
    {
        get
        {
            return _definitions ?? (
                _definitions = new List<string>
                {
                    "definition 1",
                    "definition 2",
                    "definition 3"
                }
            );
        }
    } 
}

Resharper wird dies tatsächlich als Refaktor für eine "traditionelle" faule Ladung vorschlagen.
Arichards

5

Ist ?? notwendig, oder sollten Sie nur den ternären Operator verwenden (mit dem die meisten vertraut sind)

Tatsächlich ist meine Erfahrung, dass allzu wenige Menschen mit dem ternären Operator (oder genauer gesagt dem bedingten Operator) vertraut sind . Er ?:ist "ternär" im gleichen Sinne wie ||binär oder+ entweder unär oder binär ist; es ist jedoch zufällig der nur ternärer Operator in vielen Sprachen), so dass zumindest in dieser begrenzten Stichprobe Ihre Aussage genau dort fehlschlägt.

Wie bereits erwähnt, gibt es auch eine wichtige Situation, in der der Null-Koaleszenz-Operator sehr nützlich ist, und zwar immer dann, wenn der auszuwertende Ausdruck überhaupt Nebenwirkungen hat. In diesem Fall können Sie den bedingten Operator nicht verwenden, ohne entweder (a) eine temporäre Variable einzuführen oder (b) die tatsächliche Logik der Anwendung zu ändern. (b) ist eindeutig unter keinen Umständen angemessen, und obwohl es eine persönliche Präferenz ist, mag ich es nicht, den Deklarationsumfang mit vielen fremden, wenn auch kurzlebigen Variablen zu überladen, so dass (a) auch darin herauskommt bestimmtes Szenario.

Wenn Sie das Ergebnis mehrfach überprüfen müssen, ist der bedingte Operator oder eine Reihe von ifBlöcken wahrscheinlich das Werkzeug für den Job. Aber für einfache "Wenn dies null ist, verwenden Sie das, andernfalls verwenden Sie es", ist der Null-Koaleszenzoperator ??perfekt.


Sehr später Kommentar von mir - aber erfreut zu sehen, dass jemand darüber berichtet, dass ein ternärer Operator ein Operator mit drei Argumenten ist (von denen es jetzt mehr als eines in C # gibt).
Steve Kidd

5

Eine Sache, die ich in letzter Zeit viel getan habe, ist die Verwendung von Null-Koaleszenz für Backups as. Beispielsweise:

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

Console.WriteLine(i); // Prints 4

Es ist auch nützlich, um lange Ketten zu sichern, von ?.denen jede fehlschlagen könnte

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

4

Das einzige Problem ist, dass der Null-Coalesce-Operator keine leeren Zeichenfolgen erkennt.


dh

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

string result2 = null ?? "coalesced!";

AUSGABE:

result1 = ""

result2 = coalesced!

Ich bin gerade dabei, das ?? Betreiber, um dies zu umgehen. Es wäre sicher praktisch, dies in das Framework zu integrieren.

Gedanken?


Sie können dies mit Erweiterungsmethoden tun, aber ich stimme zu, es wäre eine schöne Ergänzung zum Code und in einem Webkontext sehr nützlich.
Armstrongest

Ja, dies ist ein häufiges Szenario ... es gibt sogar eine spezielle Methode String.IsNullOrEmpty (String) ...
Max Galkin

12
"Der Null-Koaleszenz-Operator erkennt keine leeren Zeichenfolgen." Nun, es ist der Null- Koaleszenz-Operator, nicht der NullOrEmpty- Koaleszenz-Operator. Und ich persönlich verachte es, Null- und Leerwerte in Sprachen zu mischen, die zwischen beiden unterscheiden, was die Schnittstelle zu Dingen erschwert, die nicht ganz ärgerlich sind. Und ich bin ein bisschen zwanghaft, daher ärgert es mich, wenn Sprachen / Implementierungen sowieso nicht zwischen den beiden unterscheiden, selbst wenn ich die Gründe dafür verstehe (wie in [den meisten Implementierungen von?] SQL).
JAB

3
??kann nicht überladen werden: msdn.microsoft.com/en-us/library/8edha89s(v=vs.100).aspx - das wäre allerdings großartig, wenn man es überladen könnte. Ich verwende eine Kombination: s1.Nullify() ?? s2.Nullify()Dabei wird string Nullify(this s)a zurückgegeben null, wenn die Zeichenfolge leer ist.
Kit

Das einzige Problem? Ich wollte gerade ?? = und fand diesen Thread, während ich sah, ob es einen Weg gab, es zu tun. (Situation: Der erste Durchgang hat die Ausnahmefälle geladen, jetzt möchte ich zurückgehen und Standardwerte in alles laden, was noch nicht geladen wurde.)
Loren Pechtel

3

Ist ?? notwendig, oder sollten Sie nur den ternären Operator verwenden (mit dem die meisten vertraut sind)

Sie sollten das verwenden, was Ihre Absicht am besten ausdrückt. Da ist ein Null - coalesce Operator, es verwenden .

Andererseits denke ich, da es so spezialisiert ist, dass es keine anderen Verwendungszwecke hat. Ich hätte eine angemessene Überlastung des ||Operators vorgezogen , wie es andere Sprachen tun. Dies wäre im Sprachdesign sparsamer. Aber gut …


3

Cool! Zählen Sie mich als jemanden, der nichts über den Null-Koaleszenz-Operator wusste - das ist ziemlich raffiniertes Zeug.

Ich finde es viel einfacher zu lesen als der ternäre Operator.

Der erste Ort, an dem ich daran denke, wo ich es verwenden könnte, ist, alle meine Standardparameter an einem einzigen Ort zu speichern.

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
}

2

Ein seltsamer Anwendungsfall, aber ich hatte eine Methode, bei der ein IDisposableObjekt möglicherweise als Argument übergeben wird (und daher vom übergeordneten Element entsorgt wird), aber auch null sein kann (und daher in einer lokalen Methode erstellt und entsorgt werden sollte).

Um es zu verwenden, sah der Code entweder so aus

Channel channel;
Authentication authentication;

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

Aber mit einer Nullverschmelzung wird viel ordentlicher

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

0

Ich habe so verwendet:

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; 
            }
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.