Sind diese beiden Zeilen gleich? …: 'Vs' ?? '?


80

Gibt es einen Unterschied zwischen diesen beiden Zeilen?

MyName = (s.MyName == null) ? string.Empty : s.MyName

oder

MyName = s.MyName ?? string.Empty

8
Überprüfen Sie IL von ihnen, auf diese Weise haben Sie eine bessere Idee, ob sie den gleichen Code produzieren oder sie unterschiedlich behandelt werden
manman

12
@manman Wenn jemand den High-Level-Code nicht versteht, wie entsteht dann der Eindruck, dass der IL-Code besser lesbar ist?
Servy

11
@Servy Wenn einige so neugierig sind, den Unterschied zu lernen, und alle anderen über die Diskussionen auf hoher Ebene berichten, ist es eine gute Idee, andere Wege aufzuzeigen, um den Unterschied zu verstehen, und wenn er sehr neugierig ist, kann er einfach das Ergebnis vergleichen, nein
Ich

6
Der einzige Unterschied besteht darin, ob Sie s.MyNameein- oder zweimal bewerten .
Tim S.

2
@Servy , weil Sie den IL - Code nicht verstehen müssen, müssen Sie nur diffes
wchargin

Antworten:


166

UPDATE: Ich habe einen Blog-Beitrag geschrieben, in dem dieses Thema ausführlicher behandelt wird. http://www.codeducky.org/properties-fields-and-methods-oh-my/


Im Allgemeinen geben sie das gleiche Ergebnis zurück. Es gibt jedoch einige Fälle, in denen Sie merkliche Unterschiede feststellen, wenn MyNamees sich um eine Eigenschaft handelt, da der MyNameGetter im ersten Beispiel zweimal und im zweiten Beispiel nur einmal ausgeführt wird.

Beispielsweise können bei MyNamezweimaliger Ausführung Leistungsunterschiede auftreten :

string MyName
{
    get 
    {
        Thread.Sleep(10000);
        return "HELLO";
    }
}

Oder Sie erhalten unterschiedliche Ergebnisse, MyNamewenn Sie zweimal ausführen, wenn dies MyNamezustandsbehaftet ist:

private bool _MyNameHasBeenRead = false;

string MyName
{
    get 
    {
        if(_MyNameHasBeenRead)
                throw new Exception("Can't read MyName twice");
        _MyNameHasBeenRead = true;
        Thread.Sleep(10000);
        return "HELLO";
    }
}

Oder Sie erhalten unterschiedliche Ergebnisse, MyNamewenn Sie zweimal ausgeführt werden, wenn MyNamedies in einem anderen Thread geändert werden kann:

void ChangeMyNameAsync()
{
    //MyName set to null in another thread which makes it 
    //possible for the first example to return null
    Task.Run(() => this.MyName = null);
}

string MyName { get; set; }  

So wird der eigentliche Code kompiliert. Zuerst das Stück mit dem ternären Ausdruck:

IL_0007:  ldloc.0     // s
IL_0008:  callvirt    s.get_MyName       <-- first call
IL_000D:  brfalse.s   IL_0017
IL_000F:  ldloc.0     // s
IL_0010:  callvirt    s.get_MyName       <-- second call
IL_0015:  br.s        IL_001C
IL_0017:  ldsfld      System.String.Empty
IL_001C:  call        set_MyName

und hier ist das Stück mit dem Null-Koaleszenz-Operator:

IL_0007:  ldloc.0     // s
IL_0008:  callvirt    s.get_MyName       <-- only call
IL_000D:  dup         
IL_000E:  brtrue.s    IL_0016
IL_0010:  pop         
IL_0011:  ldsfld      System.String.Empty
IL_0016:  call        s.set_MyName

Wie Sie sehen können, führt der kompilierte Code für den ternären Operator zwei Aufrufe durch, um den Eigenschaftswert abzurufen, während der Null-Koaleszenz-Operator nur 1 ausführt.


3
MyName = s.MyName ?? string.Emptyführt den Getter nicht zweimal aus, wenn MyNamees sich um eine Eigenschaft handelt. Wenn Sie den Getter also nicht zweimal ausführen möchten, sollten Sie die zweite Zeile verwenden.
Steven Wexler

2
Ich habe mir erlaubt, IL-Beispiele für die beiden Codeteile hinzuzufügen. Ein vollständiges LINQPad- Beispiel finden Sie unter dropbox.com/s/x6zqdsjlkosxchf/SO21052437.linq
Lasse V. Karlsen

1
Ich denke, Sie haben ein "s" im letzten Teil des IL-Codes verpasst. Derzeit liest es et_MyName, was sein sollte set_MyName.
AJMansfield

2
Auch die lange Version mit ?:könnte nullaus den oben genannten Gründen tatsächlich dazu führen . Es könnte anschaulicher sein, wenn "Value from other thread"oben tatsächlich eine nullZeichenfolge wäre. Dann zeigt , dass würde , dass das Ergebnis könnte null sein. Dasselbe kann mit dem statusbehafteten Objekt passieren, wenn das Objekt beim ersten Aufruf des getZugriffs eine Zeichenfolgeninstanz zurückgibt und beim zweiten Aufruf nur null zurückgibt.
Jeppe Stig Nielsen

2
Beachten Sie auch, dass die C # 4.0-Sprachspezifikationen (in §7.14) sicherstellen, dass MyValue tatsächlich zweimal ausgewertet wird, wodurch verhindert wird, dass der Jitter diesen doppelten Aufruf optimiert (wie ich es zuerst gedacht hatte)
BlackBear

26

Wenn die Eigenschaft mehr als ein einfacher Getter ist, führen Sie möglicherweise eine Funktion zweimal im Nicht-Null-Fall für die erste aus.

Befindet sich die Eigenschaft in einem statusbehafteten Objekt, gibt der zweite Aufruf der Eigenschaft möglicherweise ein anderes Ergebnis zurück:

class MyClass
{
    private IEnumerator<string> _next = Next();

    public MyClass()
    {
        this._next.MoveNext();
    }

    public string MyName
    {
        get
        {
            var n = this._next.Current;
            this._next.MoveNext();
            return n;
        }
    }


    public static IEnumerator<string> Next()
    {
        yield return "foo";
        yield return "bar";
    }
}

Im Fall ohne Zeichenfolge kann die Klasse außerdem == überladen, um etwas anderes als den ternären Operator zu tun. Ich glaube nicht, dass der ternäre Operator überlastet werden kann.


Der ternäre Operator kann definitiv nicht überlastet werden. Selbst C ++, mit dem Sie praktisch jeden anderen Operator überladen können, kann nicht überladen werden ?:. Ich habe keine Sprache gesehen, die das tut.
Darrel Hoffman

@DarrelHoffman Es gibt immer Scala, wo Sie Ihre eigenen rollen können;)
jdphenix

9

Der einzige Unterschied besteht darin, ob Sie s.MyNamezweimal oder einmal bewerten . Der erste wird es zweimal tun, wenn s.MyNamees nicht null ist, der zweite wird es immer nur einmal auswerten.

In den meisten Fällen spielt dieser Unterschied keine Rolle, und ich würde mich für den zweiten entscheiden, weil er klarer und prägnanter ist.


5

Ja, beide sind gleich und es ist der Null-Koaleszenz-Operator .

Es gibt den linken Operanden zurück, wenn der Operand nicht null ist. Andernfalls wird der rechte Operand zurückgegeben.

Wenn wir dann über Effizienz sprechen

string MyName = (s.MyName == null) ? string.Empty : s.MyName;
string MyName2 = s.MyName ?? string.Empty;

Wenn ich einen Dissembler verwende, kann ich sehen, dass für die erste Anweisung 19 Anweisungen vom Compiler ausgeführt werden müssen, während für die zweite Anweisung nur 12 Anweisungen ausgeführt werden müssen.


Vielen Dank für die Erwähnung des Namens. Ich wollte sehen, ob Java dies auch hat? Operator. Und das tut es nicht, also bleibe ich für beide beim ternären Operator.
Entwicklerwjk

0

Ja, sie machen das Gleiche. ??ist eine Abkürzung für die Überprüfung auf Null.


0

Sie erfüllen die gleiche Aufgabe.

Der einzige Unterschied wäre die Lesbarkeit, ob Ihre Mitarbeiter oder wer auch immer den Code liest, die Syntax versteht.

BEARBEITEN: Zusätzlich kann die erste Option die Eigenschaft MyNamezweimal auswerten .


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.