Що означають два знаки запитання разом у C #?


1629

Дійшов до цього рядка коду:

FormsAuth = formsAuth ?? new FormsAuthenticationWrapper();

Що означають два знаки запитання, це якийсь потрійний оператор? Важко шукати в Google.


50
Це точно не потрійний оператор - у нього є лише два операнди! Це трохи схоже на умовний оператор (який є потрійним), але нульовий оператор злиття є двійковим оператором.
Джон Скіт

90
Я пояснив це в інтерв'ю, де потенційний роботодавець раніше висловлював сумніви щодо моїх здібностей C #, так як я раніше професійно використовував Java. Вони раніше про це не чули і не ставили під сумнів моє знайомство з C # після цього :)
Джон Скіт

77
@Jon Skeet Не було такого епічного невпізнання майстерності з тих пір, як хлопець, який відбив «Бітлз». :-) Відтепер просто надсилайте їм копію вашої книги із URL-посиланням на ваш профіль SO, написаний на внутрішній обкладинці.
Ієн Холдер

6
IainMH: Що того, що варто, я ще не зовсім почав писати книгу. (А може, я просто працював над главою 1 - щось подібне.) Напевно, пошук мене швидко знайшов би мій блог + статті тощо.
Джон Скіт

7
Re: останнє речення в питанні - для майбутніх посилань, SymbolHound чудово підходить для подібних речей, напр. Symbolhound.com/?q=%3F%3F&l=&e=&n=&u= [для когось підозрілого - я не пов'язаний з у будь-якому випадку, як хороший інструмент, коли я знайду його ...]
Стів Чемберс

Відповіді:


2154

Це нульовий оператор злиття, і дуже схожий на потрійний (негайний) оператор. Дивіться також ?? Оператор - MSDN .

FormsAuth = formsAuth ?? new FormsAuthenticationWrapper();

розширюється до:

FormsAuth = formsAuth != null ? formsAuth : new FormsAuthenticationWrapper();

яка далі розширюється до:

if(formsAuth != null)
    FormsAuth = formsAuth;
else
    FormsAuth = new FormsAuthenticationWrapper();

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

Зауважте, що ви можете використовувати будь-яку кількість цих послідовностей. Наступний оператор привласнює перший ненульовими Answer#до Answer(якщо всі відповіді дорівнюють нулю , то Answerдорівнює нулю):

string Answer = Answer1 ?? Answer2 ?? Answer3 ?? Answer4;

Також варто згадати, коли розширення вище є концептуально рівнозначним, результат кожного виразу оцінюється лише один раз. Це важливо, якщо, наприклад, вираз є викликом методу з побічними ефектами. (Подяку @Joey за вказівку на це.)


17
Потенційно небезпечні для ланцюга це може бути
Coops

6
@CodeBlend як так?
лк.

68
@CodeBlend, це не небезпечно. Якщо ви розширюєте, ви просто мали б ряд вкладених операторів if / else. Синтаксис просто дивний, тому що ви не звикли його бачити.
joelmdev

31
Було б бог послати, якби він працював і для порожніх рядків :)
Zyphrax

14
@Gusdor ??ліворуч асоціативний, тому a ?? b ?? c ?? dеквівалентний ((a ?? b) ?? c ) ?? d. "Оператори присвоєння та потрійний оператор (? :) є правими асоціативними. Усі інші бінарні оператори залишаються лівими асоціативними." Джерело: msdn.microsoft.com/en-us/library/ms173145.aspx
Марк Е. Хааз

284

Просто тому, що ніхто ще не сказав магічних слів: це оператор з’єднання нуля . Це визначено в розділі 7.12 специфікації мови C # 3.0 .

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

a ?? b ?? c ?? d

дасть результат вираження, aякщо він не є нульовим, інакше спробуйте b, інакше спробуйте c, інакше спробуйте d. Це коротке замикання в кожній точці.

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


2
Мені подобається, як ви спростили його значення "нульовому оператору зв'язання". Це все, що мені було потрібно.
FrankO

2
Тангенціальна, але пов’язана з цим: тепер у Swift, але це зовсім інакше: "Nil Coalescing Operator" developer.apple.com/library/ios/documentation/Swift/Conceptual/…
Dan Rosenstark

69

Це нульовий оператор злиття.

http://msdn.microsoft.com/en-us/library/ms173224.aspx

Так, майже неможливо шукати, якщо ви не знаєте, як це називається! :-)

EDIT: І це класна особливість іншого питання. Ви можете зав'язати їх ланцюгом.

Приховані особливості C #?


33
Принаймні, зараз це легко знайти, навіть якщо ви не знаєте прізвища, я просто гуглив "подвійний знак запитання c #"
stivlo

27

Дякую всім, ось найяскравіше пояснення, яке я знайшов на сайті MSDN:

// y = x, unless x is null, in which case y = -1.
int y = x ?? -1;

4
Це натякає на важливий аспект ?? оператор - він був введений для допомоги в роботі з змінними типами. У вашому прикладі "x" має тип "int?" (Зніміть <int>).
Дрю Ноакс

9
@vitule немає, якщо другий операнд нульового оператора злиття є ненульовим, то результат є ненульовим (і -1це просто звичайна int, яка не є нульовою).
Сюзанна Дюперон

Я дуже хочу, щоб це спрацювало так. Я хочу це робити постійно, але не можу, тому що змінна, яку я використовую, не є нульовим типом. Працює в coffeescript y = x? -1
PandaWood

@PandaWood Насправді можна. Якщо xє тип int?, але yмає тип int, ви можете написати int y = (int)(x ?? -1). Він буде розбирати xАня , intякщо це не null, або призначити -1в yразі xце null.
Йохан Вінтгенс

21

??є для надання значення для нульового типу, коли значення є нульовим. Отже, якщо формиAAA недійсні, він поверне нові FormsAuthenticationWrapper ().


19

введіть тут опис зображення

Два знаки запитання (??) вказують на те, що його оператор Coalescing.

Оператор злиття повертає перше значення NON-NULL з ланцюга. Ви можете побачити це відео на YouTube, яке практично демонструє все.

Але дозвольте додати більше до того, що йдеться у відео.

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

Так що, якщо str1це nullбуде намагатися str2, якщо str2це nullбуде намагатися str3і так далі до тих пір , поки не знайде рядок зі значенням ненульовим.

string final = str1 ?? str2 ?? str3 ?? str4;

Простими словами оператор Coalescing повертає перше значення NON-NULL з ланцюга.


Мені подобається це пояснення, оскільки воно має діаграму, а потім останнє речення пояснює це такими простими термінами! Це полегшує розуміння та підвищує мою впевненість у використанні. Дякую!
Джошуа К

14

Це коротка рука для потрійного оператора.

FormsAuth = (formsAuth != null) ? formsAuth : new FormsAuthenticationWrapper();

Або для тих, хто не займається трійкою:

if (formsAuth != null)
{
  FormsAuth = formsAuth;
}
else
{
  FormsAuth = new FormsAuthenticationWrapper();
}

3
Я лише виправив написання "тернар", але насправді оператор, який ви маєте на увазі, є умовним оператором. Це, мабуть, єдиний потрійний оператор в C #, але в якийсь момент вони можуть додати ще одного, і тоді "термінал" буде неоднозначним, але "умовний" не буде.
Джон Скіт

Це скорочення для того, що ви можете зробити з потрійним (умовним) оператором. У вашій тривалій формі і тест ( != null), і другий formsAuth(після ?) можуть бути змінені; у формі null coalesce обидва неявно приймають вказані вами значення.
Боб Саммерс

12

Якщо ви знайомі з Рубі, це ||=здається ??мені схожим на C # . Ось деякі Ruby:

irb(main):001:0> str1 = nil
=> nil
irb(main):002:0> str1 ||= "new value"
=> "new value"
irb(main):003:0> str2 = "old value"
=> "old value"
irb(main):004:0> str2 ||= "another new value"
=> "old value"
irb(main):005:0> str1
=> "new value"
irb(main):006:0> str2
=> "old value"

І в C #:

string str1 = null;
str1 = str1 ?? "new value";
string str2 = "old value";
str2 = str2 ?? "another new value";

4
x ||= ydesugars на щось подібне x = x || y, так ??насправді більше схожий на звичайний ||у Ruby.
qerub

6
Зверніть увагу , що ??тільки дбає про null, в той час як ||оператор в Ruby, як і в більшості мов, більше про null, falseабо що - небудь , що можна розглядати як логічне зі значенням false(наприклад , в деяких мовах, ""). Це не гарна чи погана річ, просто різниця.
Тім С.

11

оператор злиття

це рівнозначно

FormsAuth = formsAUth == null ? new FormsAuthenticationWrapper() : formsAuth

11

Нічого небезпечного в цьому немає. Насправді це красиво. Ви можете додати значення за замовчуванням, якщо це бажано, наприклад:

КОД

int x = x1 ?? x2 ?? x3 ?? x4 ?? 0;

1
Тож x1, x2, x3 та x4 можуть бути типів Nullable, наприклад: int? x1 = null;Правильно
Кевін Мередіт

1
@KevinMeredith x1- x4повинен бути обнуляється типами: це не має ніякого сенсу говорити, по суті, «результат , 0якщо x4цього значення , яке воно не може прийняти» ( null). "Нульовий тип" сюди включає , звичайно, і типи нульових значень, і референтні типи. Це помилка часу компіляції, якщо одна чи більше ланцюгових змінних (крім останньої) не зводиться до нуля.
Боб Саммерс

11

Як правильно зазначається в численних відповідях, це "оператор зв'язання нуля" ( ?? ), говорячи про який, ви також можете перевірити його двоюрідного брата "Нульового умовного оператора" ( ?. Або ? [ ), Який є оператором, який багато разів він використовується спільно з ??

Нульовий умовний оператор

Використовується для тестування на null перед виконанням операції доступу члена ( ?. ) Або індексу ( ? [ ). Ці оператори допомагають вам писати менше коду для обробки нульових перевірок, особливо для спуску в структури даних.

Наприклад:

// if 'customers' or 'Order' property or 'Price' property  is null,
// dollarAmount will be 0 
// otherwise dollarAmount will be equal to 'customers.Order.Price'

int dollarAmount = customers?.Order?.Price ?? 0; 

старий шлях без ?. і ?? робити це є

int dollarAmount = customers != null 
                   && customers.Order!=null
                   && customers.Order.Price!=null 
                    ? customers.Order.Price : 0; 

яка більш багатослівна і громіздка.


10

Для вашого розваги (знаючи, що ви всі C # хлопці ;-).

Я думаю, що вона виникла в Smalltalk, де вона існує вже багато років. Там він визначається як:

в Об'єкт:

? anArgument
    ^ self

у UndefinedObject (клас aka nil):

? anArgument
    ^ anArgument

Існують як оцінювальні (?), Так і неоцінювальні версії (??) цього.
Він часто зустрічається в методах getter для ліниво-ініціалізованих приватних змінних (екземпляр), які залишаються нульовими, поки дійсно не знадобляться.


звучить як загортання ViewState з властивістю на UserControl. Ініціалізуйте лише при першому отриманні, якщо воно не встановлено раніше. =)
Сейті

9

Деякі з прикладів отримання значень за допомогою злиття неефективні.

Те, що ти насправді хочеш:

return _formsAuthWrapper = _formsAuthWrapper ?? new FormsAuthenticationWrapper();

або

return _formsAuthWrapper ?? (_formsAuthWrapper = new FormsAuthenticationWrapper());

Це запобігає відновленню об'єкта кожного разу. Замість того, щоб приватна змінна залишалася нульовою та створювався новий об'єкт при кожному запиті, це забезпечує присвоєння приватної змінної, якщо новий об’єкт створений.


Чи не ??оцінюється ярлик? new FormsAuthenticationWrapper();оцінюється, якщо і лише тоді, коли _formsAuthWrapper нуль.
MSalters

Так, у цьому вся суть. Ви хочете викликати метод лише в тому випадку, якщо змінна є нульовою. @MSalters
KingOfHypocrites

8

Примітка:

Я прочитав цілу нитку та багато інших, але не можу знайти такої ґрунтовної відповіді, як це.

За допомогою якого я повністю зрозумів "навіщо користуватися і коли використовувати та як користуватися".

Джерело:

Фонд комунікацій з Windows, розв'язаний Крейгом Макмюртрі ISBN 0-672-32948-4

Типи нульового значення

Є дві загальні обставини, за яких хотілося б дізнатися, чи присвоєно значення екземпляру типу значення. Перший - це коли екземпляр представляє значення в базі даних. У такому випадку хотілося б мати можливість досліджувати екземпляр, щоб з’ясувати, чи є значення дійсно в базі даних. Інша обставина, яка більше стосується теми цієї книги, - це коли екземпляр представляє елемент даних, отриманий з якогось віддаленого джерела. Знову ж таки, хотілося б визначити з примірника, чи було отримано значення для цього елемента даних.

.NET Framework 2.0 включає в себе визначення загального типу, яке передбачає такі випадки, в яких хочеться призначити null екземпляру типу значення і перевірити, чи значення примірника є нульовим. Це загальне визначення типу - System.Nullable, що обмежує аргументи загального типу, які можуть бути замінені на T значеннями. Екземплярам типів, побудованих із System.Nullable, може бути призначене значення null; Дійсно, їх значення за замовчуванням є нульовими. Таким чином, типи, побудовані з System.Nullable, можуть називатися типами нульового значення. System.Nullable має властивість Value, за допомогою якої може бути отримане значення, присвоєне екземпляру типу, побудованого з нього, якщо значення екземпляра не є нульовим. Тому можна написати:

System.Nullable<int> myNullableInteger = null;
myNullableInteger = 1;
if (myNullableInteger != null)
{
Console.WriteLine(myNullableInteger.Value);
}

Мова програмування C # пропонує скорочений синтаксис для оголошення типів, побудованих із System.Nullable. Цей синтаксис дозволяє скоротити:

System.Nullable<int> myNullableInteger;

до

int? myNullableInteger;

Компілятор запобіжить спробі присвоїти значення типу нульового значення звичайному типу значення таким чином:

int? myNullableInteger = null;
int myInteger = myNullableInteger;

Це заважає робити це тому, що тип нульового значення може мати значення null, яке воно насправді було б у цьому випадку, і це значення не може бути присвоєно звичайному типу значення. Хоча компілятор дозволив би цей код,

int? myNullableInteger = null;
int myInteger = myNullableInteger.Value;

Другий оператор спричинить викид виключення, оскільки будь-яка спроба отримати доступ до властивості System.Nullable.Value є недійсною операцією, якщо типу, побудованому з System.Nullable, не було призначено дійсне значення T, чого в цьому не відбулося справа.

Висновок:

Один правильний спосіб присвоїти значення типу нульового значення звичайному типу значення - це використовувати властивість System.Nullable.HasValue, щоб встановити, чи присвоєно дійсне значення T для типу нульового значення:

int? myNullableInteger = null;
if (myNullableInteger.HasValue)
{
int myInteger = myNullableInteger.Value;
}

Іншим варіантом є використання цього синтаксису:

int? myNullableInteger = null;
int myInteger = myNullableInteger ?? -1;

За допомогою якого звичайному цілому цілому myInteger присвоюється значення нульового цілого числа "myNullableInteger", якщо останньому було призначено дійсне ціле число; в іншому випадку моєму Integer присвоюється значення -1.


1

Це нульовий оператор злиття, який працює аналогічно потрійному оператору.

    a ?? b  => a !=null ? a : b 

Ще один цікавий момент для цього полягає в тому, що "зведений тип може містити значення, або воно може бути невизначеним" . Отже, якщо ви спробуєте призначити тип нульового значення ненульовому типу значення, ви отримаєте помилку часу компіляції.

int? x = null; // x is nullable value type
int z = 0; // z is non-nullable value type
z = x; // compile error will be there.

Отже, щоб зробити це, використовуючи ?? оператор:

z = x ?? 1; // with ?? operator there are no issues

1
FormsAuth = formsAuth ?? new FormsAuthenticationWrapper();

еквівалентно

FormsAuth = formsAuth != null ? formsAuth : new FormsAuthenticationWrapper();

Але класна річ у тому, що ви можете зав'язати їх ланцюгом, як казали інші люди. Той тонкий, який не торкався, - це те, що ви можете насправді використовувати його, щоб кинути виняток.

A = A ?? B ?? throw new Exception("A and B are both NULL");

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

1

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

int? variable1 = null;
int variable2  = variable1 ?? 100;

Встановити variable2значення variable1, якщо variable1НЕ нульове; в іншому випадку, якщо variable1 == null, встановити variable2100.


0

Інші Null Coalescing Operatorдосить добре описали . Для тих, хто цікавиться, є скорочений синтаксис, де це (питання ТА):

FormsAuth = formsAuth ?? new FormsAuthenticationWrapper();

еквівалентно цьому:

FormsAuth ??= new FormsAuthenticationWrapper();

Деякі вважають його більш зрозумілим і стислим.

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