Co dwa znaki zapytania razem oznaczają w języku C #?

1629

Przebiegł przez ten wiersz kodu:

FormsAuth = formsAuth ?? new FormsAuthenticationWrapper();

Co oznaczają dwa znaki zapytania, czy jest to jakiś trójskładnikowy operator? W Google trudno jest szukać.

Edward Tanguay
źródło
50
Zdecydowanie nie jest operatorem trójskładnikowym - ma tylko dwa operandy! To trochę jak operator warunkowy (który jest trójskładnikowy), ale zerowy operator koalescencyjny jest operatorem binarnym.
Jon Skeet
90
Wyjaśniłem to w wywiadzie, w którym potencjalny pracodawca wcześniej wyraził wątpliwości co do moich umiejętności C #, ponieważ od pewnego czasu profesjonalnie korzystałem z Javy. Nie słyszeli o tym wcześniej i nie kwestionowali mojej znajomości C # :)
Jon Skeet
77
@Jon Skeet Nie było tak epickiego niepowodzenia w rozpoznawaniu umiejętności od czasu gościa, który odrzucił Beatlesów. :-) Od tej chwili po prostu wyślij im kopię swojej książki z linkiem do Twojego profilu SO zapisanego na wewnętrznej okładce.
Iain Holder
6
IainMH: Na co warto, ja nie dość zaczął pisać jeszcze książkę. (A może właśnie pracowałem nad rozdziałem 1 - coś w tym rodzaju.) Wprawdzie poszukiwanie mnie szybko znalazłoby mój blog + artykuły itp.
Jon Skeet
7
Re: ostatnie zdanie w q - na przyszłość, SymbolHound jest świetny do tego rodzaju rzeczy, np. Symbolhound.com/?q=%3F%3F&l=&e=&n=&u= [dla każdego podejrzanego - nie jestem związany w każdym razie, tak jak dobre narzędzie, kiedy znajdę jedno ...]
Steve Chambers

Odpowiedzi:

2153

Jest to zerowy operator koalescencyjny i całkiem podobny do operatora trójskładnikowego (natychmiast-jeśli). Zobacz także ? Operator - MSDN .

FormsAuth = formsAuth ?? new FormsAuthenticationWrapper();

rozwija się do:

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

co dalej rozwija się do:

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

W języku angielskim oznacza to: „Jeśli cokolwiek jest po lewej stronie, nie jest zerowe, użyj tego, w przeciwnym razie użyj tego, co jest po prawej stronie”.

Pamiętaj, że możesz użyć dowolnej ich liczby po kolei. Poniższe oświadczenie przydzieli pierwsze niezerowe Answer#do Answer(jeśli wszystkie odpowiedzi są nieważne, to Answerjest null):

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

Warto również wspomnieć, podczas gdy powyższe rozwinięcie jest koncepcyjnie równoważne, wynik każdego wyrażenia jest oceniany tylko raz. Jest to ważne, jeśli na przykład wyrażenie jest wywołaniem metody z efektami ubocznymi. (Podziękowania dla @Joey za zwrócenie na to uwagi.)

lc.
źródło
17
Potencjalnie niebezpieczne może być ich połączenie
Coops
6
@CodeBlend jak to zrobić?
lc.
68
@CodeBlend, to nie jest niebezpieczne. Jeśli miałbyś się rozwinąć, miałbyś po prostu szereg zagnieżdżonych instrukcji if / else. Składnia jest po prostu dziwna, ponieważ nie jesteś przyzwyczajony do jej oglądania.
joelmdev
31
Byłby
boskim posłaniem,
14
@Gusdor ??jest skojarzony, więc a ?? b ?? c ?? djest równoważny z ((a ?? b) ?? c ) ?? d. „Operatory przypisania i operator trójskładnikowy (? :) są prawymi asocjacjami. Wszystkie pozostałe operatory binarne pozostają asocjatywne”. Źródło: msdn.microsoft.com/en-us/library/ms173145.aspx
Mark E. Haase
284

Tylko dlatego, że nikt inny nie wypowiedział jeszcze magicznych słów: to zerowy operator koalescencyjny . Jest zdefiniowany w sekcji 7.12 specyfikacji języka C # 3.0 .

Jest bardzo przydatny, szczególnie ze względu na sposób działania, gdy jest używany wielokrotnie w wyrażeniu. Wyrażenie formy:

a ?? b ?? c ?? d

poda wynik wyrażenia, ajeśli nie ma wartości null, w przeciwnym razie spróbuj b, w przeciwnym razie spróbuj c, w przeciwnym razie spróbuj d. W każdym punkcie powoduje zwarcie.

Ponadto, jeśli typ dnie ma wartości zerowej, typ całego wyrażenia również nie ma wartości zerowej.

Jon Skeet
źródło
2
Podoba mi się, jak uprościłeś jego znaczenie do „zerowego operatora koalescencyjnego”. To wszystko, czego potrzebowałem.
FrankO
2
Styczne, ale powiązane: Jest teraz w Swift, ale jest zupełnie inaczej: „Nil Coalescing Operator” developer.apple.com/library/ios/documentation/Swift/Conceptual/…
Dan Rosenstark
69

To zerowy operator koalescencyjny.

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

Tak, prawie niemożliwe do wyszukania, chyba że wiesz, jak się nazywa! :-)

EDYCJA: I to jest fajna funkcja z innego pytania. Możesz je połączyć.

Ukryte funkcje C #?

Iain Holder
źródło
33
Przynajmniej teraz łatwo go wyszukać, nawet jeśli nie znasz nazwy, po prostu przejrzałem „podwójny znak zapytania c #”
stivlo
27

Dziękujemy wszystkim, oto najbardziej zwięzłe wyjaśnienie, które znalazłem na stronie MSDN:

// y = x, unless x is null, in which case y = -1.
int y = x ?? -1;
Edward Tanguay
źródło
4
Wskazuje to na ważny aspekt ?? operator - został wprowadzony, aby pomóc w pracy z typami zerowalnymi. W twoim przykładzie „x” jest typu „int?” (Nullable <int>).
Drew Noakes
9
@vitule nie, jeśli drugi operand zerowego operatora koalescencyjnego nie jest zerowalny, to wynik nie jest zerowalny (i -1jest po prostu zwykłą int, która nie jest zerowalna).
Suzanne Dupéron,
Naprawdę chciałbym, żeby to tak działało. Chcę to robić cały czas, ale nie mogę, ponieważ zmienna, której używam, nie jest typem zerowalnym. Działa w coffeescript y = x? -1
PandaWood,
@PandaWood Właściwie możesz. Jeśli xjest typu int?, ale yjest typu int, możesz napisać int y = (int)(x ?? -1). Będzie analizować xdo intjeśli nie jest null, lub przypisać -1do yjeżeli xjest null.
Johan Wintgens
21

??służy do podania wartości typu zerowalnego, gdy wartość jest równa null. Zatem jeśli formAuth ma wartość NULL, zwróci nową FormsAuthenticationWrapper ().

RedFilter
źródło
19

wprowadź opis zdjęcia tutaj

Dwa znaki zapytania (??) wskazują, że jest to operator koalescencyjny.

Operator koalescencji zwraca pierwszą wartość NON-NULL z łańcucha. Możesz obejrzeć ten film na youtube, który praktycznie pokazuje całą sprawę.

Ale pozwól mi dodać więcej do tego, co mówi film.

Jeśli widzisz angielskie znaczenie koalescencji, to mówi „konsoliduj razem”. Na przykład poniżej znajduje się prosty kod łączący cztery łańcuchy.

Więc jeśli str1jest nullto spróbować str2, jeśli str2to nullbędzie starał str3i tak dalej, aż znajdzie ciąg o wartości innej niż NULL.

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

Krótko mówiąc, operator koalescencyjny zwraca pierwszą wartość NON-NULL z łańcucha.

Shivprasad Koirala
źródło
Podoba mi się to wyjaśnienie, ponieważ ma schemat, a następnie ostatnie zdanie wyjaśnia je w tak prostych słowach! Ułatwia to zrozumienie i zwiększa moją pewność użytkowania. Dzięki!
Joshua K
14

To krótka ręka dla operatora trójskładnikowego.

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

Lub dla tych, którzy nie robią trójki:

if (formsAuth != null)
{
  FormsAuth = formsAuth;
}
else
{
  FormsAuth = new FormsAuthenticationWrapper();
}
Benjamin Autin
źródło
3
Poprawiłem tylko pisownię „trójskładnikowego”, ale tak naprawdę operator, o którym mówisz, jest operatorem warunkowym. Zdarza się, że jest to jedyny trójskładnikowy operator w języku C #, ale w pewnym momencie mogą dodać kolejny, w którym to momencie trójskładnikowy będzie dwuznaczny, ale „warunkowy” nie.
Jon Skeet
Jest to skrót od czegoś, co można zrobić za pomocą operatora trójskładnikowego (warunkowego). W twojej długiej formie zarówno test ( != null), jak i drugi formsAuth(po ?) mogą być zmienione; w zerowej formie koalescencji obydwa domyślnie przyjmują podane wartości.
Bob Sammers,
12

Jeśli znasz Ruby, ||=wydaje mi się , że jest podobny do C # ??. Oto niektóre 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"

I w C #:

string str1 = null;
str1 = str1 ?? "new value";
string str2 = "old value";
str2 = str2 ?? "another new value";
Sarah Vessels
źródło
4
x ||= ysprowadza się do czegoś podobnego x = x || y, więc w ??rzeczywistości jest bardziej podobny do zwykłego ||w Ruby.
qerub
6
Należy pamiętać, że ??tylko dba o null, podczas gdy ||operator w Ruby, jak w większości języków, to bardziej null, falselub coś, co można uznać za logiczną o wartości false(na przykład w niektórych językach ""). To nie jest ani dobra ani zła rzecz, tylko różnica.
Tim S.
11

operator koalescencyjny

jest to równoważne z

FormsAuth = formsAUth == null ? new FormsAuthenticationWrapper() : formsAuth
aku
źródło
11

Nie ma w tym nic niebezpiecznego. W rzeczywistości jest piękny. Możesz dodać wartość domyślną, jeśli jest to pożądane, na przykład:

KOD

int x = x1 ?? x2 ?? x3 ?? x4 ?? 0;
dqninh
źródło
1
Więc x1, x2, x3 i x4 mogą być typami zerowalnymi, przykład: int? x1 = null;Czy to prawda
Kevin Meredith
1
@KevinMeredith x1- x4MUSI być typami zerowalnymi: nie ma sensu mówić skutecznie: „wynikiem jest, 0jeśli x4jest wartością, której nie byłby w stanie przyjąć” ( null). „Typ dopuszczający wartości zerowe” obejmuje tutaj zarówno typy dopuszczające wartości zerowe, jak i typy referencyjne. Jest to błąd czasu kompilacji, jeśli jedna lub więcej zmiennych łańcuchowych (oprócz ostatniej) nie ma wartości zerowej.
Bob Sammers,
11

Jak słusznie wskazano w wielu odpowiedziach, że jest to „zerowy operator koalescencyjny” ( ?? ), mówiąc o którym możesz również chcieć sprawdzić jego kuzyna „zerowy operator warunkowy” ( ?. Lub ? [ ), Który jest operatorem, który wiele razy jest używany w połączeniu z ??

Operator nieważny

Służy do testowania wartości null przed wykonaniem operacji dostępu do elementu ( ?. ) Lub indeksu ( ? [ ). Te operatory pomagają pisać mniej kodu do obsługi kontroli zerowych, szczególnie w przypadku schodzenia do struktur danych.

Na przykład:

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

stary sposób bez ? i ?? robienia tego jest

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

co jest bardziej pełne i kłopotliwe.

brakeroo
źródło
10

Tylko dla twojej rozrywki (wiedząc, że wszyscy jesteście facetami z C # ;-).

Myślę, że powstał w Smalltalk, gdzie istnieje od wielu lat. Jest tam zdefiniowany jako:

w obiekcie:

? anArgument
    ^ self

w UndefinedObject (aka nil's class):

? anArgument
    ^ anArgument

Istnieją zarówno wersje oceniające (?), Jak i nieoceniające (??) tego.
Często można go znaleźć w metodach pobierających zmienne prywatne (instancja) zainicjowane z opóźnieniem, które są zerowe, dopóki nie są naprawdę potrzebne.

blabla999
źródło
brzmi jak zawijanie ViewState z właściwością na UserControl. Zainicjuj tylko przy pierwszym pobraniu, jeśli nie zostało wcześniej ustawione. =)
Seiti
9

Niektóre przykłady uzyskiwania wartości przy użyciu koalescencji są nieefektywne.

Naprawdę chcesz:

return _formsAuthWrapper = _formsAuthWrapper ?? new FormsAuthenticationWrapper();

lub

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

Zapobiega to odtwarzaniu obiektu za każdym razem. Zamiast zmiennej prywatnej pozostającej pustej i przy każdym żądaniu tworzony jest nowy obiekt, zapewnia to przypisanie zmiennej prywatnej, jeśli nowy obiekt zostanie utworzony.

KingOfHypocrites
źródło
Czy ??skrót nie jest oceniany? new FormsAuthenticationWrapper();jest oceniany wtedy i tylko wtedy, gdy _formsAuthWrapper jest zerowy.
MSalters
Tak, o to właśnie chodzi. Chcesz wywołać metodę tylko wtedy, gdy zmienna ma wartość NULL. @MSalters
KingOfHypocrites
8

Uwaga:

Przeczytałem cały ten wątek i wiele innych, ale nie mogę znaleźć tak dokładnej odpowiedzi, jak to jest.

Przez co całkowicie zrozumiałem „dlaczego używać? I kiedy używać? I jak korzystać?”.

Źródło:

Fundacja komunikacji Windows uwolniona przez Craiga McMurtry'ego ISBN 0-672-32948-4

Typy dopuszczające wartości zerowe

Istnieją dwie typowe okoliczności, w których chciałoby się dowiedzieć, czy wartość została przypisana do instancji typu wartości. Po pierwsze, instancja reprezentuje wartość w bazie danych. W takim przypadku chcielibyśmy mieć możliwość zbadania instancji w celu ustalenia, czy wartość rzeczywiście znajduje się w bazie danych. Inną okolicznością, bardziej związaną z przedmiotem tej książki, jest sytuacja, gdy instancja reprezentuje element danych otrzymany z jakiegoś zdalnego źródła. Ponownie chciałoby się ustalić z instancji, czy otrzymano wartość dla tego elementu danych.

.NET Framework 2.0 zawiera ogólną definicję typu, która zapewnia przypadki takie jak te, w których chce się przypisać wartość null do instancji typu wartości i przetestować, czy wartość instancji jest pusta. Ta ogólna definicja typu to System.Nullable, która ogranicza ogólne argumenty typu, które mogą być zastąpione T dla typów wartości. Instancjom typów zbudowanych z System.Nullable można przypisać wartość null; w rzeczywistości ich wartości są domyślnie zerowe. Zatem typy skonstruowane z System.Nullable można nazwać typami wartości zerowalnych. System.Nullable ma właściwość Value, dzięki której można przypisać wartość przypisaną do instancji typu zbudowanego na jej podstawie, jeśli wartość instancji nie jest równa null. Dlatego można napisać:

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

Język programowania C # zapewnia skróconą składnię do deklarowania typów zbudowanych z System.Nullable. Ta składnia pozwala na skrócenie:

System.Nullable<int> myNullableInteger;

do

int? myNullableInteger;

Kompilator zapobiegnie próbie przypisania wartości typu wartości zerowej do zwykłego typu wartości w następujący sposób:

int? myNullableInteger = null;
int myInteger = myNullableInteger;

Uniemożliwia to zrobienie tego, ponieważ dopuszczalny typ wartości może mieć wartość null, co w rzeczywistości miałby w tym przypadku, a tej wartości nie można przypisać do zwykłego typu wartości. Chociaż kompilator zezwala na ten kod,

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

Druga instrukcja spowodowałaby zgłoszenie wyjątku, ponieważ każda próba dostępu do właściwości System.Nullable.Value jest niepoprawną operacją, jeśli typowi zbudowanemu z System.Nullable nie przypisano prawidłowej wartości T, co nie miało miejsca w tym przypadku walizka.

Wniosek:

Jednym z prawidłowych sposobów przypisania wartości typu zerowalnego wartości do zwykłego typu wartości jest użycie właściwości System.Nullable.HasValue w celu ustalenia, czy do typu wartości dopuszczalnej zerowej została przypisana poprawna wartość T:

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

Inną opcją jest użycie tej składni:

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

Przez co zwykła liczba całkowita myInteger jest przypisywana wartości zerowalnej liczby całkowitej „myNullableInteger”, jeśli ta ostatnia ma przypisaną prawidłową wartość całkowitą; w przeciwnym razie myInteger zostanie przypisana wartość -1.

Muhammad Waqas Dilawar
źródło
1

Jest zerowym operatorem koalescencyjnym, który działa podobnie do operatora trójskładnikowego.

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

Innym interesującym punktem tego jest: „Typ zerowalny może zawierać wartość lub może być niezdefiniowany” . Jeśli więc spróbujesz przypisać typ wartości dopuszczającej wartości zerowe do typu wartości nie dopuszczającej wartości zerowej, otrzymasz błąd czasu kompilacji.

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

Więc zrobić to za pomocą? operator:

z = x ?? 1; // with ?? operator there are no issues
Ashish Mishra
źródło
1
FormsAuth = formsAuth ?? new FormsAuthenticationWrapper();

jest równa

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

Ale fajną rzeczą jest to, że możesz je połączyć, jak mówili inni ludzie. Jedyną nie poruszoną kwestią jest to, że można go użyć do zgłoszenia wyjątku.

A = A ?? B ?? throw new Exception("A and B are both NULL");
NolePTR
źródło
To naprawdę wspaniałe, że umieściłeś przykłady w swoim poście, chociaż pytanie szukało wyjaśnienia, czym jest operator lub co robi.
TGP1994
1

??Operatora nazywana jest operator null koalescencji. Zwraca operand po lewej stronie, jeśli operand nie jest pusty; w przeciwnym razie zwraca operand po prawej stronie.

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

Ustaw variable2na wartość variable1, jeśli variable1NIE jest pusta; w przeciwnym razie variable1 == null, ustaw variable2na 100.

Jubayer Hossain
źródło
0

Inni opisali Null Coalescing Operatorcałkiem dobrze. Dla zainteresowanych istnieje skrócona składnia, w której to (pytanie SO):

FormsAuth = formsAuth ?? new FormsAuthenticationWrapper();

jest równoważne z tym:

FormsAuth ??= new FormsAuthenticationWrapper();

Niektórzy uważają to za bardziej czytelne i zwięzłe.

Thane Plummer
źródło