Czy te dwie linie są takie same ”? … :' vs '??'?

80

Czy jest różnica między tymi dwiema liniami?

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

lub

MyName = s.MyName ?? string.Empty
user1307149
źródło
8
Sprawdź ich IL, w ten sposób będziesz miał lepszy pomysł, czy będą produkować ten sam kod, czy też są obsługiwane inaczej
manman
12
@manman Jeśli ktoś nie rozumie kodu wysokiego poziomu, co sprawia wrażenie, że kod IL będzie bardziej czytelny?
Servy
11
@Servy Kiedy niektórzy są bardzo ciekawi różnicy, a wszyscy inni omawiają dyskusje na wysokim szczeblu, dobrym pomysłem jest wskazanie innych sposobów zrozumienia różnicy, a jeśli jest bardzo ciekawy, może pójść i po prostu porównać wynik, nie trzeba przeczytać cały IL
manman
6
Jedyną różnicą jest to, czy oceniasz s.MyNameraz, czy dwa razy.
Tim S.
2
@Servy, ponieważ nie musisz rozumieć kodu IL, po prostu musisz to diffzrobić
wchargin

Odpowiedzi:

166

AKTUALIZACJA: Napisałem post na blogu, który bardziej szczegółowo omawia ten temat. http://www.codeducky.org/properties-fields-and-methods-oh-my/


Generalnie zwrócą ten sam wynik. Jest jednak kilka przypadków, w których zauważysz zauważalne różnice, gdy MyNamejest to właściwość, ponieważ MyNamemetoda pobierająca zostanie wykonana dwukrotnie w pierwszym przykładzie i tylko raz w drugim przykładzie.

Na przykład mogą wystąpić różnice w wydajności przy MyNamedwukrotnym wykonaniu :

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

Lub możesz uzyskać inne wyniki, wykonując MyNamedwa razy, jeśli MyNamejest stanowy:

private bool _MyNameHasBeenRead = false;

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

Lub możesz uzyskać inne wyniki po MyNamedwukrotnym wykonaniu, jeśli MyNamemożna to zmienić w innym wątku:

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

Oto, jak kompilowany jest rzeczywisty kod. Najpierw utwór z potrójnym wyrażeniem:

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

a oto fragment z operatorem koalescencji zerowej:

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

Jak widać, skompilowany kod dla operatora trójskładnikowego wykona dwa wywołania w celu uzyskania wartości właściwości, podczas gdy operator łączący wartość null wykona tylko 1.

Steven Wexler
źródło
3
MyName = s.MyName ?? string.Emptynie wykona metody pobierającej dwukrotnie, jeśli MyNamejest właściwością. Więc jeśli nie chcesz dwukrotnie wykonywać gettera, powinieneś użyć drugiej linii.
Steven Wexler,
2
Pozwoliłem sobie dodać przykłady IL dwóch fragmentów kodu. Pełny przykład LINQPad można znaleźć pod adresem dropbox.com/s/x6zqdsjlkosxchf/SO21052437.linq
Lasse V. Karlsen
1
Myślę, że przegapiłeś „s” w ostatniej części kodu IL; obecnie czyta et_MyName, co powinno być set_MyName.
AJMansfield,
2
Ponadto długa wersja z ?:może faktycznie skutkować nullz powyższych powodów. To mogłoby być bardziej obrazowe, gdyby "Value from other thread"powyżej był faktycznie nullciągiem. To pokazałoby, że wynik może być zerowy. To samo mogłoby się zdarzyć z obiektem stanowym, gdyby obiekt zdecydował się zwrócić wystąpienie ciągu znaków przy pierwszym wywołaniu getaccessoor i zwrócić wartość null tylko przy drugim wywołaniu.
Jeppe Stig Nielsen
2
Zwróć również uwagę, że specyfikacje języka C # 4.0 zapewniają (w §7.14), że MyValue zostanie rzeczywiście dwukrotnie ocenione, zapobiegając w ten sposób jitterowi optymalizacji tego podwójnego wywołania (tak jak myślałem na początku)
BlackBear
26

Jeśli właściwość jest czymś więcej niż prostym pobieraczem, możesz wykonywać funkcję dwukrotnie w przypadku niezerowym dla pierwszej.

Jeśli właściwość jest obiektem stanowym, drugie wywołanie właściwości może zwrócić inny wynik:

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

Ponadto w przypadku nie będącym łańcuchem klasa może przeciążać ==, aby zrobić coś innego niż operator trójargumentowy. Nie wierzę, że operator trójskładnikowy może być przeciążony.

Dan Gallagher
źródło
Operator trójargumentowy zdecydowanie nie może być przeciążony. Nawet C ++, który pozwala na przeciążenie praktycznie każdego innego operatora, nie ma możliwości przeciążenia ?:. Nie widziałem żadnego języka, który to robi.
Darrel Hoffman
@DarrelHoffman Jest zawsze Scala, w której możesz toczyć własną;)
jdphenix
9

Jedyną różnicą jest to, czy oceniasz s.MyNamedwukrotnie czy raz. Pierwsza zrobi to dwukrotnie w przypadku, gdy s.MyNamenie jest zerowa, druga tylko raz oceni to.

W większości przypadków ta różnica nie ma znaczenia i wybrałbym drugą, ponieważ jest bardziej przejrzysta i zwięzła.

Tim S.
źródło
5

Tak, oba są takie same i jest to operator łączenia zerowego .

Zwraca operand po lewej stronie, jeśli operand nie jest null; w przeciwnym razie zwraca operand po prawej stronie.

Jeśli mówimy wtedy o wydajności

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

Jeśli używam disemblera, to widzę, że pierwsza instrukcja wymaga 19 instrukcji do wykonania przez kompilator, podczas gdy druga wymaga tylko 12 instrukcji do wykonania.

Sachin
źródło
Dziękuję za podanie nazwiska. Chciałem sprawdzić, czy Java również to ma? operator. I tak nie jest, więc pozostanę przy operatorze trójskładnikowym dla obu.
developerwjk
0

Tak, robią to samo. ??jest skrótem do sprawdzania null.

Zdravko Danev
źródło
0

Wykonują to samo zadanie.

Jedyną różnicą byłaby czytelność, czy Twoi współpracownicy lub ktokolwiek czyta kod, rozumie składnię.

EDYCJA: Dodatkowo pierwsza opcja może MyNamedwukrotnie ocenić właściwość .

McAden
źródło