Ці два рядки однакові, '? ...: 'проти' ?? '?


80

Чи є різниця між цими двома рядками?

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

або

MyName = s.MyName ?? string.Empty

8
Перевірте ІЛ з них, таким чином, ви будете краще
уявляти,

12
@manman Якщо хто - то не розуміє код високого рівня , що дає вам враження , що код IL буде більш читабельним?
Серві

11
@Servy Коли комусь цікаво дізнатися різницю, а всі інші висвітлюють дискусії на високому рівні, непогано вказати на інші способи зрозуміти різницю, і якщо йому дуже цікаво, він може піти і просто порівняти результат, ні потрібно прочитати всю ІЛ
людину

6
Різниця лише в тому, оцінюєте ви s.MyNameодин або два рази.
Тім С.

2
@Servy, тому що вам не потрібно розуміти код IL, ви просто повинні diffце зробити
wchargin

Відповіді:


166

ОНОВЛЕННЯ: Я написав допис у блозі, де детальніше розглядається ця тема. http://www.codeducky.org/properties-fields-and-methods-oh-my/


Як правило, вони повернуть той самий результат. Однак є кілька випадків, коли ви відчуєте помітні відмінності, колиMyName є властивістю, оскільки MyNameв першому прикладі геттер буде виконано двічі, а у другому - лише один раз.

Наприклад, у вас можуть виникнути відмінності в продуктивності від виконання MyName двічі:

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

Або ви можете отримати різні результати від виконання MyNameдвічі, якщо MyNameце має статус:

private bool _MyNameHasBeenRead = false;

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

Або ви можете отримати різні результати від виконання MyNameдвічі, якщо MyNameїх можна змінити в іншому потоці:

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

Ось як складається фактичний код. Спочатку фрагмент із потрійним виразом:

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

і ось шматок з оператором нульового злиття:

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

Як бачите, скомпільований код для тернарного оператора зробить два виклики, щоб отримати значення властивості, тоді як оператор нульового злиття зробить лише 1.


3
MyName = s.MyName ?? string.Emptyне буде виконувати геттер двічі, якщо MyNameє властивістю. Отже, якщо ви не хочете виконувати геттер двічі, то слід використати другий рядок.
Steven Wexler

2
Я взяв на себе свободу додавати приклади ІЛ для двох фрагментів коду. Повний приклад LINQPad можна знайти на dropbox.com/s/x6zqdsjlkosxchf/SO21052437.linq
Лассе В. Карлсен,

1
Я думаю, ви пропустили "s" в останній частині коду IL; в даний час він читає et_MyName, що повинно бути set_MyName.
AJMansfield

2
Крім того, довга версія з ?:може насправді призвести nullз вищевказаних причин. Це може бути більш наочним, якби "Value from other thread"насправді це був nullрядок. Тоді це показало б, що результат може бути нульовим. Те саме може статися з об'єктом, що містить статус, якщо об'єкт вирішить повернути екземпляр рядка за першого виклику getaccessoor і повернути null лише для другого виклику.
Jeppe Stig Nielsen

2
Також зверніть увагу, що специфікації мови C # 4.0 гарантують (у §7.14), що MyValue дійсно двічі оцінюється, що заважає джиттеру оптимізувати цей подвійний дзвінок (як я спочатку думав)
BlackBear

26

Якщо властивість більше, ніж простий геттер, можливо, ви виконуєте функцію двічі у ненульовому випадку для першого.

Якщо властивість знаходиться у об’єкті, що містить статус, тоді другий виклик властивості може повернути інший результат:

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

Крім того, у нестроковому випадку клас може перевантажити ==, щоб зробити щось інше, ніж тернарний оператор. Я не вірю, що потрійний оператор може бути перевантажений.


Тернарного оператора однозначно не можна перевантажувати. Навіть C ++, який дозволяє перевантажувати практично кожного іншого оператора, не може перевантажити ?:. Я не бачив жодної мови, яка б це робила.
Даррел Хоффман

@DarrelHoffman Завжди є Scala, де ти можеш
прокрутити

9

Різниця лише в тому, оцінюєте ви s.MyNameдвічі чи один раз. Перший зробить це двічі у випадку, якщо s.MyNameне є нульовим значенням, другий лише колись оцінить це один раз.

У більшості випадків ця різниця не має значення, і я б погодився з другим, оскільки він є більш чітким і стислим.


5

Так, обидва є однаковими, і це оператор нульового злиття .

Він повертає лівий операнд, якщо операнд не є нульовим; в іншому випадку він повертає правий операнд.

Якщо говорити про ефективність, то

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

Якщо я використовую дисемблер, то я бачу, що для першого оператора компілятор повинен виконати 19 операторів, тоді як для другого оператора потрібно було виконати лише 12 операторів .


Дякуємо, що згадали це ім’я. Я хотів подивитися, чи є у Java це також ?? оператора. А це не так, тому я дотримуватимусь потрійного оператора для обох.
developerwjk


0

Вони виконують одне і те ж завдання.

Єдина різниця полягала б у читабельності щодо того, чи розуміють ваш синтаксис ваші співробітники чи хтось, хто читає код.

РЕДАГУВАТИ: Крім того, перший варіант може оцінити властивість MyNameдвічі.


-1

Ні. Обидва роблять одне і те ж. Другий - ефективний. Що повертає фактичне значення, якщо воно не є нульовим. В іншому випадку буде повернуто значення правої сторони.

Зверніться до цього http://msdn.microsoft.com/en-us/library/ms173224.aspx

Сподіваюся, це допомагає.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.