12 wyrażeń
12.1 Ogólne
Wyrażenie to sekwencja operatorów i operandów. Ta klauzula definiuje składnię, kolejność obliczania operandów i operatorów oraz znaczenie wyrażeń.
12.2 Klasyfikacje wyrażeń
12.2.1 Ogólne
Wynik wyrażenia jest klasyfikowany jako jeden z następujących:
- Wartość. Każda wartość ma skojarzony typ.
- Zmienna. Jeśli nie określono inaczej, zmienna jest jawnie typowana i ma skojarzony typ, czyli zadeklarowany typ zmiennej. Niejawnie typizowana zmienna nie ma skojarzonego typu.
- Literał o wartości null. Wyrażenie z tą klasyfikacją można niejawnie przekonwertować na typ odwołania lub typ wartości nullowalnej.
- Funkcja anonimowa. Wyrażenie z tą klasyfikacją można niejawnie przekonwertować na zgodny typ delegata lub typ drzewa wyrażeń.
- Krotka. Każda krotka ma stałą liczbę elementów, z których każda ma wyrażenie i opcjonalną nazwę elementu krotki.
- Dostęp do właściwości. Każdy dostęp do właściwości ma skojarzony typ, czyli typ właściwości. Ponadto dostęp do właściwości może mieć skojarzone wyrażenie obiektu. Kiedy zostanie wywołany akcesor dostępu do właściwości instancji, wynik oceny wyrażenia instancji staje się instancją reprezentowaną przez
this
(§12.8.14). - Dostęp do indeksatora. Każdy dostęp indeksatora ma skojarzony typ, a mianowicie typ elementu indeksatora. Ponadto dostęp indeksatora ma skojarzoną listę argumentów i skojarzone wyrażenie wystąpienia. Po wywołaniu akcesora indeksatora, wynik oceny wyrażenia instancji staje się instancją reprezentowaną przez
this
(§12.8.14), a wynik oceny listy argumentów staje się listą parametrów wywołania. - Nic. Dzieje się tak, gdy wyrażenie jest wywołaniem metody z zwracanym typem
void
. Wyrażenie sklasyfikowane jako nic nie jest prawidłowe tylko w kontekście statement_expression (§13.7) lub jako treść lambda_expression (§12.19).
W przypadku wyrażeń, które występują jako podwyrażenia większych wyrażeń, z uwzględnionymi ograniczeniami, wynik może być również sklasyfikowany jako jeden z następujących:
- Przestrzeń nazw. Wyrażenie z tą klasyfikacją może pojawić się tylko po lewej stronie member_access (§12.8.7). W jakimkolwiek innym kontekście wyrażenie zaklasyfikowane jako przestrzeń nazw powoduje błąd czasu kompilacji.
- Typ. Wyrażenie z tą klasyfikacją może pojawić się tylko po lewej stronie member_access (§12.8.7). W każdym innym kontekście wyrażenie sklasyfikowane jako typ powoduje błąd czasu kompilacji.
- Grupa metod, która jest zestawem przeciążonych metod wynikających z wyszukiwania składowego (§12.5). Grupa metod może mieć skojarzone wyrażenie wystąpienia i skojarzoną listę argumentów typu. Po wywołaniu metody wystąpienia wynik oceny wyrażenia wystąpienia staje się wystąpieniem reprezentowanym przez
this
(§12.8.14). Grupa metod jest dozwolona w invocation_expression (§12.8.10) lub delegate_creation_expression (§12.8.17.6) i może być niejawnie przekonwertowana na zgodny typ delegata (§10.8). W każdym innym kontekście wyrażenie sklasyfikowane jako grupa metod powoduje błąd czasu kompilacji. - Dostęp do wydarzenia. Każdy dostęp do zdarzenia jest powiązany z określonym typem, czyli typem tego zdarzenia. Ponadto dostęp do zdarzenia może mieć skojarzone wyrażenie wystąpienia. Dostęp do zdarzeń może występować jako lewy operand operatorów
+=
i-=
(§12.21.5). W każdym innym kontekście wyrażenie sklasyfikowane jako dostęp do zdarzenia powoduje błąd czasu kompilacji. Gdy zostanie wywołany dostępnik dostępu do zdarzenia instancji, wynik oceny wyrażenia instancji staje się instancją reprezentowaną przezthis
(§12.8.14). - Wyrażenie throw może być używane w różnych kontekstach do zgłaszania wyjątku w wyrażeniu. Wyrażenie rzutu może zostać przekonwertowane przez niejawną konwersję na dowolny typ.
Dostęp do właściwości lub dostęp indeksatora jest zawsze ponownie klasyfikowany jako wartość, wykonując wywołanie metody get lub metody set. Określona metoda dostępu jest determinowana przez kontekst właściwości lub dostępu indeksatora: Jeśli dostęp jest celem przypisania, wywoływany jest akcesor set w celu przypisania nowej wartości (§12.21.2). W przeciwnym razie, akcesor pobierający jest wywoływany, aby uzyskać bieżącą wartość (§12.2.2).
wystąpienie dostępu to dostęp do właściwości w wystąpieniu, dostęp do zdarzenia w wystąpieniu lub dostęp do indeksatora.
12.2.2 Wartości wyrażeń
Większość konstrukcji, które obejmują wyrażenie, ostatecznie wymaga wyrażenia, aby oznaczyć wartość . W takich przypadkach, jeśli rzeczywiste wyrażenie oznacza przestrzeń nazw, typ, grupę metod lub nic, wystąpi błąd czasu kompilacji. Jeśli jednak wyrażenie oznacza dostęp do właściwości, dostęp indeksatora lub zmienną, wartość właściwości, indeksatora lub zmiennej jest niejawnie zastępowana:
- Wartość zmiennej jest po prostu wartością przechowywaną obecnie w lokalizacji przechowywania identyfikowanej przez zmienną. Zmienna jest uznawana za zdecydowanie przypisaną (§9.4) przed uzyskaniem jej wartości lub w przeciwnym razie wystąpi błąd czasu kompilacji.
- Wartość wyrażenia dostępu do właściwości jest uzyskiwana przez wywołanie metody uzyskiwania dostępu do właściwości. Jeśli właściwość nie ma akcesora get, wystąpi błąd czasu kompilacji. W przeciwnym razie wykonywane jest wywołanie elementu członkowskiego funkcji (§12.6.6), a wynik wywołania staje się wartością wyrażenia dostępu do właściwości.
- Wartość wyrażenia dostępu indeksatora jest uzyskiwana przez wywołanie akcesora pobierającego indeksatora. Jeśli indeksator nie ma akcesora get, wystąpi błąd czasu kompilacji. W przeciwnym razie wykonywane jest wywołanie elementu członkowskiego funkcji (§12.6.6) z listą argumentów skojarzoną z wyrażeniem dostępu do indeksatora, a wynik tego wywołania staje się wartością tego wyrażenia.
- Wartość wyrażenia krotkowego jest uzyskiwana przez zastosowanie niejawnej konwersji krotkowej (§10.2.13) do typu wyrażenia krotkowego. Błędem jest uzyskiwanie wartości wyrażenia krotki, które nie ma typu.
12.3 Powiązanie statyczne i dynamiczne
12.3.1 Ogólne
powiązanie to proces określania, do czego odnosi się operacja, na podstawie typu lub wartości wyrażeń (argumentów, operandów, odbiorników). Na przykład powiązanie wywołania metody jest określane na podstawie typu odbiornika i argumentów. Powiązanie operatora jest określane na podstawie typu operandów.
W języku C# powiązanie operacji jest zwykle określane w czasie kompilacji na podstawie typu zdefiniowanego w czasie kompilacji jego podwyrażeń. Podobnie, jeśli wyrażenie zawiera błąd, błąd zostanie wykryty i zgłoszony w czasie kompilacji. Takie podejście jest nazywane powiązaniem statycznym.
Jeśli jednak wyrażenie jest wyrażeniem dynamicznym (tj. ma typ dynamic
), oznacza to, że każde powiązanie, w których uczestniczy, powinno być oparte na typie czasu wykonywania, a nie typie, który ma w czasie kompilacji. Powiązanie takiej operacji jest zatem odroczone do momentu, w którym operacja ma zostać wykonana w trakcie działania programu. Jest to nazywane powiązaniem dynamicznym .
Gdy operacja jest dynamicznie powiązana, podczas kompilacji wykonywane jest niewielkie lub żadne sprawdzanie. Zamiast tego, jeśli powiązanie w czasie wykonywania zakończy się niepowodzeniem, błędy są zgłaszane jako wyjątki w czasie wykonywania.
Następujące operacje w języku C# podlegają powiązaniu:
- Dostęp do członków:
e.M
- Wywołanie metody:
e.M(e₁,...,eᵥ)
- Wywołanie delegata:
e(e₁,...,eᵥ)
- Dostęp do elementów:
e[e₁,...,eᵥ]
- Tworzenie obiektu: nowe
C(e₁,...,eᵥ)
- Przeciążone operatory jednoargumentowe:
+
,-
,!
(tylko negacja logiczna),~
,++
,--
,true
,false
- Przeciążone operatory binarne:
+
,-
,*
,/
,%
,&
,&&
,|
,||
,??
,^
,<<
,>>
,==
,!=
,>
,<
,>=
,<=
- Operatory przypisania:
=
,= ref
,+=
,-=
,*=
,/=
,%=
,&=
,|=
,^=
,<<=
,>>=
- Niejawne i jawne konwersje
Jeśli nie są wykorzystywane wyrażenia dynamiczne, język C# domyślnie stosuje powiązanie statyczne, co oznacza, że w procesie wyboru używane są typy podwyrażeń ustalane w czasie kompilacji. Jeśli jednak jedno z podwyrażeń w operacjach wymienionych powyżej jest wyrażeniem dynamicznym, operacja jest zamiast tego dynamicznie powiązana.
Jest to błąd czasu kompilacji, jeśli wywołanie metody jest dynamicznie powiązane, a dowolny z parametrów, w tym odbiornika, są parametrami wejściowymi.
12.3.2 Czas wiązania
Powiązanie statyczne odbywa się w czasie kompilacji, natomiast powiązanie dynamiczne odbywa się w czasie wykonywania. W poniższych podpunktach termin czas wiązania odnosi się do czasu kompilacji lub czasu wykonywania, w zależności od tego, kiedy ma miejsce wiązanie.
Przykład: Poniżej przedstawiono pojęcia statycznego i dynamicznego powiązania oraz czasu powiązania:
object o = 5; dynamic d = 5; Console.WriteLine(5); // static binding to Console.WriteLine(int) Console.WriteLine(o); // static binding to Console.WriteLine(object) Console.WriteLine(d); // dynamic binding to Console.WriteLine(int)
Dwa pierwsze wywołania są statycznie powiązane: przeciążenie
Console.WriteLine
jest wybierane na podstawie typu czasu kompilacji argumentu. W związku z tym czas powiązania jest czas kompilacji.Trzecie wywołanie jest dynamicznie powiązane: przeciążenie
Console.WriteLine
jest wybierane na podstawie typu czasu wykonywania argumentu. Dzieje się tak, ponieważ argument jest wyrażeniem dynamicznym — jego typ czasu kompilacji jest dynamiczny. W związku z tym czas powiązania trzeciego wywołania jest czasu wykonywania.koniec przykładu
12.3.3 Powiązanie dynamiczne
Ten podpunkt jest informacyjny.
Powiązanie dynamiczne umożliwia programom języka C# interakcję z obiektami dynamicznymi, tj. obiektami, które nie są zgodne z normalnymi regułami systemu typów języka C#. Obiekty dynamiczne mogą być obiektami z innych języków programowania z różnymi systemami typów lub mogą być obiektami, które są konfigurowane programowo w celu zaimplementowania własnych semantyki powiązań dla różnych operacji.
Mechanizm, za pomocą którego obiekt dynamiczny implementuje własną semantykę, jest określany przez implementację. Określony interfejs — ponownie zdefiniowany przez implementację — jest implementowany przez obiekty dynamiczne, aby sygnalizować środowisku uruchomieniowemu C#, że mają specjalne znaczenie. Kiedy tylko operacje na obiekcie dynamicznym są dynamicznie powiązane, własna semantyka powiązań, zamiast tej z języka C#, jak określono w tej specyfikacji, przejmuje kontrolę.
Chociaż celem powiązania dynamicznego jest umożliwienie współdziałania z obiektami dynamicznymi, język C# umożliwia powiązanie dynamiczne na wszystkich obiektach, niezależnie od tego, czy są dynamiczne, czy nie. Pozwala to na bezproblemową integrację obiektów dynamicznych, ponieważ wyniki operacji na nich mogą nie być obiektami dynamicznymi, ale nadal są typu nieznanego programisty w czasie kompilacji. Ponadto powiązanie dynamiczne może pomóc wyeliminować kod oparty na odbiciu, który jest podatny na błędy, nawet wtedy, gdy żadne obiekty nie są obiektami dynamicznymi.
12.3.4 Typy podwyrażeń
Gdy operacja jest statycznie powiązana, typ wyrażenia podrzędnego (np. odbiornik i argument, indeks lub operand) jest zawsze uważany za typ czasu kompilacji tego wyrażenia.
Gdy operacja jest dynamicznie powiązana, typ podexpressionu jest określany na różne sposoby w zależności od typu czasu kompilacji podexpressionu:
- Podwyrażenie dynamicznego typu w czasie kompilacji jest uważane za typ wartości, którą wyrażenie przyjmuje podczas wykonywania programu.
- Podwyrażenie, którego typ w czasie kompilacji jest parametrem typu, jest uważane za typ, z którym parametr jest powiązany podczas wykonywania.
- W przeciwnym razie podwyrażenie jest uznawane za typ czasu kompilacji.
12.4 Operatory
12.4.1 Ogólne
Wyrażenia są tworzone na podstawie operandów i operatorów. Operatory wyrażenia wskazują, które operacje mają być stosowane do operandów.
Przykład: przykłady operatorów obejmują
+
,-
,*
,/
inew
. Przykłady operandów obejmują literały, pola, zmienne lokalne i wyrażenia. koniec przykładu
Istnieją trzy rodzaje operatorów:
- Operatory jednoargumentowe. Operatory jednoargumentowe przyjmują jeden operand i używają notacji prefiksowej (na przykład
–x
) lub notacji postfiksowej (na przykładx++
). - Operatory binarne. Operatory binarne przyjmują dwa operandy i używają notacji infiksu (na przykład
x + y
). - Operator trójargumentowy. Istnieje tylko jeden operator trójargumentowy,
?:
; przyjmuje trzy operandy i używa notacji infiksowej (c ? x : y
).
Kolejność obliczania operatorów w wyrażeniu jest określana przez priorytet i asocjatywność operatorów (§12.4.2).
Operandy w wyrażeniu są obliczane od lewej do prawej.
Przykład: w
F(i) + G(i++) * H(i)
metodaF
jest wywoływana przy użyciu starej wartościi
, a następnie metodaG
jest wywoływana ze starą wartościąi
, a na koniec metodaH
jest wywoływana z nową wartością i. Jest to oddzielone od i niezwiązane z pierwszeństwem operatora. koniec przykładu
Niektóre operatory mogą być przeciążone. Przeciążenie operatora (§12.4.3) zezwala na określenie implementacji operatorów zdefiniowanych przez użytkownika dla operacji, w których jeden lub oba operandy są typu klasy lub struktury zdefiniowanej przez użytkownika.
12.4.2 Pierwszeństwo operatora i asocjatywność
Gdy wyrażenie zawiera wiele operatorów, pierwszeństwo operatorów kontroluje kolejność oceniania poszczególnych operatorów.
Uwaga: na przykład wyrażenie
x + y * z
jest oceniane jakox + (y * z)
, ponieważ operator*
ma wyższy priorytet niż operator+
binarny. uwaga końcowa
Pierwszeństwo operatora jest ustanawiane przez definicję jego skojarzonej produkcji gramatycznej.
Uwaga: na przykład additive_expression składa się z sekwencji multiplicative_expressionoddzielonych operatorami
+
lub-
, co daje operatorom+
i-
niższe pierwszeństwo niż operatory*
,/
i%
. uwaga końcowa
Uwaga: Poniższa tabela zawiera podsumowanie wszystkich operatorów według pierwszeństwa od najwyższego do najniższego:
podklasy Kategoria Operatory §12.8 Podstawowy x.y
x?.y
f(x)
a[x]
a?[x]
x++
x--
x!
new
typeof
default
checked
unchecked
delegate
stackalloc
§12.9 Jednoargumentowy +
-
!x
~
++x
--x
(T)x
await x
§12.10 Multiplikatywny *
/
%
§12.10 Dodatek +
-
§12.11 Zmiana <<
>>
§12.12 Testowanie relacyjne i typowe <
>
<=
>=
is
as
§12.12 Równość ==
!=
§12.13 Logiczne AND &
§12.13 Logiczny XOR ^
§12.13 Logiczne OR \|
§12.14 Warunkowe I &&
§12.14 Warunkowe LUB \|\|
§12.15 i §12.16 Łączenie wartości null i wyrażenie rzutowania ??
throw x
§12.18 Warunkowy ?:
§12.21 i §12.19 Przypisanie i wyrażenie lambda =
= ref
*=
/=
%=
+=
-=
<<=
>>=
&=
^=
\|=
=>
uwaga końcowa
Gdy operand występuje między dwoma operatorami o tym samym priorytecie, asocjatywność operatorów kontroluje kolejność wykonywania operacji:
- Z wyjątkiem operatorów przypisania i operatora łączenia wartości null wszystkie operatory binarne są lewej asocjacji, co oznacza, że operacje są wykonywane od lewej do prawej.
Przykład:
x + y + z
oceniany jest jako(x + y) + z
. koniec przykładu - Operatory przypisania, operator łączenia wartości null i operator warunkowy (
) są kojarzące, co oznacza, że operacje są wykonywane od prawej do lewej.Przykład:
x = y = z
oceniany jest jakox = (y = z)
. koniec przykładu
Pierwszeństwo i kojarzenie można kontrolować przy użyciu nawiasów.
Przykład:
x + y * z
najpierw mnożyy
przezz
, a następnie dodaje wynik dox
, ale(x + y) * z
najpierw dodajex
iy
, a następnie mnoży wynik przezz
. koniec przykładu
Przeciążenie operatora 12.4.3
Wszystkie operatory jednoargumentowe i binarne mają wstępnie zdefiniowane implementacje. Ponadto implementacje zdefiniowane przez użytkownika można wprowadzać za pomocą deklaracji operatorów (§15.10) w klasach i strukturach. Implementacje operatorów zdefiniowanych przez użytkownika zawsze mają pierwszeństwo przed wstępnie zdefiniowanymi implementacjami operatorów: tylko wtedy, gdy nie istnieją implementacje operatorów zdefiniowanych przez użytkownika, będą brane pod uwagę wstępnie zdefiniowane implementacje operatorów, zgodnie z opisem w §12.4.4 i §12.4.5.
operatory jednoargumentowe, które można przeciążać, to:
+ - !
(tylko negacja logiczna) ~ ++ -- true false
Uwaga: Chociaż
true
ifalse
nie są używane jawnie w wyrażeniach (i dlatego nie są uwzględniane w tabeli pierwszeństwa w §12.4.2), są traktowane jako operatory, ponieważ są wywoływane w kilku kontekstach wyrażeń: Wyrażenia logiczne (§12.24) i wyrażenia obejmujące warunkowe (§12.18) i warunkowe operatory logiczne (§12.14). uwaga końcowa
Uwaga: operator ignorujący wartość null (postfiks
!
, §12.8.9) nie jest operatorem przeciążalnym. uwaga końcowa
Przeciążalne operatory binarne są następujące:
+ - * / % & | ^ << >> == != > < <= >=
Tylko wymienione powyżej operatory mogą być przeciążone. W szczególności nie można przeciążyć dostępu do składowych, wywołania metody ani operatorów =
, &&
, ||
, ??
, ?:
, =>
, checked
, unchecked
, new
, typeof
, default
, as
i is
.
Gdy operator binarny jest przeciążony, odpowiedni operator przypisania złożonego, jeśli istnieje, jest również niejawnie przeciążony.
Przykład: przeciążenie operatora
*
jest również przeciążeniem operatora*=
. Opisano to dalej w §12.21. koniec przykładu
Nie można przeciążyć samego operatora przypisania (=)
. Przypisanie zawsze polega na prostym zapisaniu wartości do zmiennej (§12.21.2).
Operacje rzutu, takie jak (T)x
, są przeciążone przez zapewnienie konwersji zdefiniowanych przez użytkownika (§10.5).
Uwaga: konwersje zdefiniowane przez użytkownika nie mają wpływu na zachowanie operatorów
is
anias
. uwaga końcowa
Dostęp do elementów, takich jak a[x]
, nie jest uważany za przeciążalny operator. Zamiast tego indeksowanie zdefiniowane przez użytkownika jest obsługiwane za pośrednictwem indeksatorów (§15.9).
W wyrażeniach operatory są przywoływane przy użyciu notacji operatorów, a w deklaracjach operatory są przywoływane przy użyciu notacji funkcjonalnej. W poniższej tabeli przedstawiono zależność między operatorami a notacjami funkcyjnymi dla operatorów jednoargumentowych i binarnych. W pierwszym wpisie «op» oznacza dowolny przeciążalny operator prefiksowy unarny. W drugim wpisie "op" oznacza jednoargumentowe postfiksowe operatory ++
i --
. W trzecim wpisie "op" oznacza dowolnego przeciążonego operatora binarnego.
Uwaga: na przykład przeciążenia operatorów
++
i--
zobacz §15.10.2. uwaga końcowa
Notacja Operatora | notacja funkcjonalna |
---|---|
«op» x |
operator «op»(x) |
x «op» |
operator «op»(x) |
x «op» y |
operator «op»(x, y) |
Deklaracje operatorów zdefiniowane przez użytkownika zawsze wymagają co najmniej jednego z parametrów klasy lub typu struktury zawierającego deklarację operatora.
Uwaga: w związku z tym nie jest możliwe, aby operator zdefiniowany przez użytkownika miał taki sam podpis jak wstępnie zdefiniowany operator. uwaga końcowa
Deklaracje operatorów zdefiniowane przez użytkownika nie mogą modyfikować składni, pierwszeństwa ani kojarzenia operatora.
Przykład: operator
/
jest zawsze operatorem binarnym, zawsze ma poziom pierwszeństwa określony w §12.4.2i zawsze jest w lewo asocjacyjny. koniec przykładu
Uwaga: Chociaż operator zdefiniowany przez użytkownika może wykonać dowolne obliczenia, implementacje generujące wyniki inne niż oczekiwane intuicyjnie, są zdecydowanie odradzane. Na przykład implementacja operatora
==
powinna porównać dwa operandy pod kątem równości i zwrócić odpowiedni wynikbool
. uwaga końcowa
Opisy poszczególnych operatorów w §12.9 do §12.21 określają wstępnie zdefiniowane implementacje operatorów i wszelkie dodatkowe reguły, które mają zastosowanie do każdego operatora. Opisy korzystają z terminów rozwiązywania przeciążeń operatora jednoargumentowego, rozwiązywania przeciążeń operatorów binarnych, promocji numerycznejoraz definicji operatorów podniesionych, których definicje znajdują się w poniższych podklauzulach.
12.4.4 Rozpoznawanie przeciążenia operatora unarnego
Operacja postaci «op» x
lub x «op»
, gdzie «op» jest przeciążalnym operatorem jednoargumentowym, a x
jest wyrażeniem typu X
, jest przetwarzana w następujący sposób:
- Zestaw operatorów zdefiniowanych przez użytkownika udostępnianych przez
X
dla operacjioperator «op»(x)
jest określany przy użyciu reguł §12.4.6. - Jeśli zestaw kandydatów operatorów zdefiniowanych przez użytkownika nie jest pusty, staje się to zestawem operatorów kandydatów dla operacji. W przeciwnym razie wstępnie zdefiniowane implementacje binarne
operator «op»
, w tym ich podniesione formy, stają się zestawem operatorów kandydatowych dla operacji. Wstępnie zdefiniowane implementacje danego operatora są określone w opisie operatora. Wstępnie zdefiniowane operatory udostępniane przez typ wyliczenia lub delegata są uwzględniane tylko w tym zestawie, gdy typ czasu powiązania — lub typ bazowy, jeśli jest to typ dopuszczalny wartości null — jednego z operandów jest typem wyliczenia lub delegata. - Reguły rozpoznawania przeciążeń §12.6.4 są stosowane do zestawu operatorów kandydatów w celu wybrania najlepszego operatora w odniesieniu do listy argumentów
(x)
, a ten operator staje się wynikiem procesu rozpoznawania przeciążenia. Jeśli rozpoznawanie przeciążenia nie wybierze jednego najlepszego operatora, wystąpi błąd czasu powiązania.
12.4.5 Rozpoznawanie przeciążenia operatora binarnego
Operacja formularza x «op» y
, gdzie «op» jest przeciążalnym operatorem binarnym, x
jest wyrażeniem typu X
, a y
jest wyrażeniem typu Y
, jest przetwarzane w następujący sposób:
- Określa się zestaw kandydatów na operatorów zdefiniowanych przez użytkownika udostępnianych przez
X
iY
dla operacjioperator «op»(x, y)
. Zestaw składa się z unii operatorów kandydatów dostarczonych przezX
i operatorów kandydatów dostarczonych przezY
, z których każdy jest określany przy użyciu zasad §12.4.6. W przypadku połączonego zestawu kandydaci są scalani w następujący sposób:- Jeśli
X
iY
są wzajemnie konwertowalne jako tożsamości lub jeśliX
iY
pochodzą ze wspólnego typu podstawowego, to współdzielone operatory kandydatów występują tylko raz w zestawie połączonym. - Jeśli istnieje konwersja tożsamości między
X
aY
, operator«op»Y
dostarczony przezY
ma taki sam typ zwracany jak«op»X
dostarczony przezX
, a typy operandów«op»Y
mają konwersję tożsamości na odpowiadające typy operandów«op»X
wtedy tylko«op»X
występuje w zestawie.
- Jeśli
- Jeśli zestaw kandydatów operatorów zdefiniowanych przez użytkownika nie jest pusty, staje się to zestawem operatorów kandydatów dla operacji. W przeciwnym razie wstępnie zdefiniowane implementacje binarne
operator «op»
, w tym ich podniesione formy, stają się zestawem operatorów kandydatowych dla operacji. Wstępnie zdefiniowane implementacje danego operatora są określone w opisie operatora. Dla określonych z góry operatorów enum i delegatów uwzględnia się wyłącznie operatory dostarczane przez typ wyliczenia lub delegata, który jest typem powiązanym w czasie jednej z operandów. - Reguły rozpoznawania przeciążeń §12.6.4 są stosowane do zestawu operatorów kandydatów w celu wybrania najlepszego operatora w odniesieniu do listy argumentów
(x, y)
, a ten operator staje się wynikiem procesu rozpoznawania przeciążenia. Jeśli rozpoznawanie przeciążenia nie wybierze jednego najlepszego operatora, wystąpi błąd czasu powiązania.
12.4.6 Operatory zdefiniowane przez użytkownika
Biorąc pod uwagę typ T
i operację operator «op»(A)
, gdzie «op» jest operatorem przeciążalnym, a A
jest listą argumentów, zestaw operatorów zdefiniowanych przez użytkownika dostarczonych przez T
dla operatora «op»(A)
jest określany w następujący sposób:
- Określ typ
T₀
. JeśliT
jest typem wartości dopuszczanej do wartości null,T₀
jest jego typem bazowym; w przeciwnym razieT₀
jest równaT
. - Dla wszystkich deklaracji
operator «op»
wT₀
i wszystkich podniesionych form takich operatorów, jeśli co najmniej jeden operator jest możliwy do zastosowania (§12.6.4.2) w odniesieniu do listy argumentówA
, to zestaw kandydatów na operatorów składa się ze wszystkich takich stosownych operatorów wT₀
. - W przeciwnym razie, jeśli
T₀
jestobject
, zestaw operatorów do wyboru jest pusty. - W przeciwnym razie zestaw operatorów kandydatów udostępnianych przez
T₀
jest zestawem operatorów kandydatów dostarczonych przez bezpośrednią klasę bazowąT₀
lub efektywną klasę bazowąT₀
, jeśliT₀
jest parametrem typu.
12.4.7 Promocje liczbowe
12.4.7.1 Ogólne
Ten podpunkt jest informacyjny.
§12.4.7 i jego podpunkty są podsumowaniem połączonego efektu:
- reguły niejawnych konwersji liczbowych (§10.2.3);
- zasady lepszej konwersji (§12.6.4.7); i
- dostępne arytmetyczne (§12.10), relacyjne (§12.12) i logiczne całkowite operatory (§12.13.2).
Podwyższenie poziomu liczbowego polega na automatycznym wykonywaniu pewnych niejawnych konwersji operandów wstępnie zdefiniowanych operatorów jednoargumentowych i binarnych liczbowych. Promocja numeryczna nie jest odrębnym mechanizmem, ale raczej efektem zastosowania rozwiązywania przeciążenia do wstępnie zdefiniowanych operatorów. Promocja liczbowa nie wpływa bezpośrednio na ocenę operatorów zdefiniowanych przez użytkownika, chociaż operatory te można zaimplementować tak, aby wykazywały podobne efekty.
Jako przykład podwyższania poziomu liczbowego należy wziąć pod uwagę predefiniowane implementacje binarnego operatora *
:
int operator *(int x, int y);
uint operator *(uint x, uint y);
long operator *(long x, long y);
ulong operator *(ulong x, ulong y);
float operator *(float x, float y);
double operator *(double x, double y);
decimal operator *(decimal x, decimal y);
Gdy reguły rozpoznawania przeciążeń (§12.6.4) są stosowane do tego zestawu operatorów, efekt polega na wybraniu pierwszego z operatorów, dla których istnieją niejawne konwersje z typów operandów.
Przykład: w przypadku operacji
b * s
, gdzieb
jestbyte
, as
jestshort
, rozwiązywanie przeciążenia wybieraoperator *(int, int)
jako najlepszy operator. W związku z tym efekt polega na tym, żeb
is
są konwertowane naint
, a typ wyniku jestint
. Podobnie w przypadku operacjii * d
, gdziei
jestint
, ad
jestdouble
,overload
decyzja wybieraoperator *(double, double)
na najlepszego operatora. koniec przykładu
Koniec tekstu informacyjnego.
12.4.7.2 Jednoargumentowe promocje liczbowe
Ten podpunkt jest informacyjny.
Jednoargumentowa promocja liczbowa występuje dla operandów wstępnie zdefiniowanych operatorów +
, -
i ~
jednoargumentowych. Promocja jednoargumentowa polega po prostu na konwertowaniu operandów z typów sbyte
, byte
, short
, ushort
lub char
na typ int
. Ponadto dla operatora jednoargumentowego - jednoargumentowa promocja liczbowa przekształca operandy typu uint
na typ long
.
Koniec tekstu informacyjnego.
12.4.7.3 Promocje liczbowe binarne
Ten podpunkt jest informacyjny.
Podwyższanie liczby binarnej występuje dla operandów wstępnie zdefiniowanych +
, -
, *
, /
, %
, &
, |
, ^
, ==
, !=
, >
, <
, >=
i <=
operatorów binarnych. Promocja numeryczna binarna niejawnie konwertuje oba operandy na wspólny typ, który w przypadku operatorów nierelacyjnych staje się również typem wyniku operacji. Promocja liczbowa binarna składa się z zastosowania następujących reguł w kolejności, w której są one wyświetlane tutaj:
- Jeśli którykolwiek z operandów ma typ
decimal
, drugi operand jest konwertowany na typdecimal
, lub wystąpi błąd czasu powiązania, jeśli drugi operand ma typfloat
lubdouble
. - W przeciwnym razie jeśli którykolwiek operand ma typ
double
, drugi operand jest konwertowany na typdouble
. - W przeciwnym razie jeśli którykolwiek operand ma typ
float
, drugi operand jest konwertowany na typfloat
. - W przeciwnym razie, jeśli jeden z operandów ma typ
ulong
, drugi operand jest konwertowany na typulong
, lub następuje błąd czasu powiązania, jeśli inny operand jest typutype sbyte
,short
,int
lublong
. - W przeciwnym razie jeśli którykolwiek operand ma typ
long
, drugi operand jest konwertowany na typlong
. - W przeciwnym razie, jeśli operand ma typ
uint
, a drugi operand ma typsbyte
,short
lubint
, oba operandy są konwertowane na typlong
. - W przeciwnym razie jeśli którykolwiek operand ma typ
uint
, drugi operand jest konwertowany na typuint
. - W przeciwnym razie oba operandy są konwertowane na typ
int
.
Uwaga: pierwsza reguła nie zezwala na wszystkie operacje, które mieszają typ
decimal
z typamidouble
ifloat
. Reguła wynika z faktu, że nie ma niejawnych konwersji między typemdecimal
a typamidouble
ifloat
. uwaga końcowa
Uwaga: Należy również zauważyć, że operand nie może być typu
ulong
, gdy drugi operand jest oznaczonego typu całkowitego. Przyczyną jest to, że żaden typ całkowity nie istnieje, który może reprezentować pełny zakresulong
, a także podpisane typy całkowite. uwaga końcowa
W obu powyższych przypadkach wyrażenie rzutowania może służyć do jawnego przekonwertowania jednego operandu na typ zgodny z drugim operatorem.
Przykład: w poniższym kodzie
decimal AddPercent(decimal x, double percent) => x * (1.0 + percent / 100.0);
Występuje błąd czasu powiązania, ponieważ nie można pomnożyć
decimal
przezdouble
. Błąd został rozwiązany przez jawne przekonwertowanie drugiego operandu nadecimal
w następujący sposób:decimal AddPercent(decimal x, double percent) => x * (decimal)(1.0 + percent / 100.0);
koniec przykładu
Koniec tekstu informacyjnego.
12.4.8 Operatory zniesione
Operatory uniesione umożliwiają używanie wstępnie zdefiniowanych i zdefiniowanych przez użytkownika operatorów, które działają na typach wartości niebędących nullem, z formami, które dopuszczają null dla tych typów. Operatory podniesione są tworzone na podstawie wstępnie zdefiniowanych i zdefiniowanych przez użytkownika operatorów spełniających określone wymagania, jak opisano poniżej.
- W przypadku operatorów jednoargumentowych
+
,++
,-
,--
,!
(negacja logiczna) i~
, istnieje zniesiona forma operatora, jeśli operand i typy wyników są typami wartości niepustych. Podniesiona forma jest konstruowana przez dodanie pojedynczego modyfikatora?
do typów operandów i wyników. Operator zniesiony generuje wartośćnull
, jeśli operand ma wartośćnull
. W przeciwnym razie operator lifted rozpakowuje operand, stosuje operatora podstawowego i zawija wynik. - W przypadku operatorów binarnych
+
,-
,*
,/
,%
,&
,|
,^
,<<
i>>
, istnieje zniesiona forma operatora, jeśli argument operand i typy wyników są typami wartości niepustych. Podniesiona forma jest tworzona przez dodanie pojedynczego modyfikatora?
do każdego operandu i typu wyniku. Operator podniesiony generuje wartośćnull
, jeśli jeden lub oba operandy sąnull
(z wyjątkiem operatorów&
oraz|
typubool?
, jak opisano w §12.13.5). W przeciwnym razie operator podniesiony rozdziela operandy, stosuje operator bazowy i opakowuje wynik. - W przypadku operatorów równości
==
i!=
istnieje zniesiona forma operatora, jeśli typy operandów są typami wartości niepustych i jeśli typ wyniku jestbool
. Podniesiona forma jest konstruowana przez dodanie pojedynczego modyfikatora?
do każdego typu operandu. Podniesiony operator traktuje dwie wartościnull
jako równe, a wartośćnull
jako nierówną żadnej wartości innej niżnull
. Jeśli oba operandy nie sąnull
, podniesiony operator odpakowuje operandy i stosuje operator bazowy, aby wygenerować wynikbool
. - W przypadku operatorów relacyjnych
<
,>
,<=
i>=
istnieje zniesiona forma operatora, jeśli typy operandów są typami wartości niepustych, a jeśli typ wyniku jestbool
. Podniesiona forma jest konstruowana przez dodanie pojedynczego modyfikatora?
do każdego typu operandu. Operator podniesiony generuje wartośćfalse
, jeśli jeden lub oba operandy sąnull
. W przeciwnym razie operator podniesiony (lifted) rozpakowuje operandy i stosuje operator bazowy, aby wygenerować wynikbool
.
12.5 Wyszukiwanie członków
12.5.1 Ogólne
Wyszukiwanie członka to proces, w którym określa się znaczenie nazwy w kontekście typu. Wyszukiwanie członka może nastąpić podczas ewaluacji simple_name (§12.8.4) lub member_access (§12.8.7) w wyrażeniu. Jeśli simple_name lub member_access występuje jako primary_expressioninvocation_expression (§12.8.10.2), mówi się, że członek jest wywoływany.
Jeśli członek jest metodą lub zdarzeniem albo jest stałym, polem lub właściwością typu delegata (§20) lub typu dynamic
(§8.2.4), określa się go jako wywoływalny.
Wyszukiwanie elementu członkowskiego uwzględnia nie tylko nazwę elementu członkowskiego, ale także liczbę parametrów typu, które ma element członkowski i czy jest dostępny. Na potrzeby wyszukiwania elementów członkowskich, metody generyczne i zagnieżdżone typy generyczne mają liczbę parametrów typów wskazanych w swoich deklaracjach, podczas gdy wszystkie pozostałe elementy członkowskie mają zerową liczbę parametrów typów.
Wyszukiwanie elementu członkowskiego nazwy N
z argumentami typu K
w typie T
jest przetwarzane w następujący sposób:
- Najpierw określa się zbiór dostępnych członków nazwany
N
:- Jeśli
T
jest parametrem typu, zestaw jest związkiem zestawów dostępnych elementów członkowskich o nazwieN
w każdym z typów określonych jako ograniczenie podstawowe lub ograniczenie pomocnicze (§15.2.5) dlaT
, wraz z zestawem dostępnych elementów członkowskich o nazwieN
wobject
. - W przeciwnym razie zestaw składa się ze wszystkich dostępnych elementów (§7.5) nazwanych
N
wT
, w tym dziedziczonych elementów i dostępnych elementów o nazwieN
wobject
. JeśliT
jest typem skonstruowanym, zestaw elementów członkowskich jest uzyskiwany przez podstawianie argumentów typu zgodnie z opisem w §15.3.3. Członkowie zawierający modyfikatoroverride
zostają wykluczeni z zestawu.
- Jeśli
- Następnie, jeśli
K
ma wartość zero, wszystkie zagnieżdżone typy, których deklaracje zawierają parametry typu, zostaną usunięte. JeśliK
nie jest równa zero, wszyscy członkowie z inną liczbą parametrów typu zostaną usunięci. JeśliK
jest zero, metody o parametrach typu nie są usuwane, ponieważ proces wnioskowania typu (§12.6.3) może być w stanie wywnioskować argumenty typu. - Następnie, jeśli członek jest wywoływany, wszystkie niewywołalne członki zostaną usunięte z kolekcji.
- Następnie członkowie ukrywani przez innych członków są usuwani z zestawu. Dla każdego członka
S.M
w zestawie, gdzieS
jest typem, w którym zadeklarowano członkaM
, stosowane są następujące reguły:- Jeśli
M
jest stałym, polem, właściwością, zdarzeniem lub elementem członkowskim wyliczania, wszystkie elementy członkowskie zadeklarowane w podstawowym typieS
zostaną usunięte z zestawu. - Jeśli
M
jest deklaracją typu, wszystkie typy inne niż zadeklarowane w podstawowym typieS
zostaną usunięte z zestawu, a wszystkie deklaracje typów o tej samej liczbie parametrów typu coM
zadeklarowane w podstawowym typieS
zostaną usunięte z zestawu. - Jeśli
M
jest metodą, to wszystkie składniki, które nie są metodami i są zadeklarowane w typie bazowym związanym zS
, zostaną usunięte z zestawu.
- Jeśli
- Następnie składowe interfejsu ukryte przez składowe klasy są usuwane z zestawu. Ten krok ma wpływ tylko wtedy, gdy
T
jest parametrem typu, aT
ma zarówno efektywną klasę bazową inną niżobject
, jak i niepusty zestaw efektywnych interfejsów (§15.2.5). Dla każdego elementuS.M
w zbiorze, gdzieS
to typ, w którym elementM
jest zadeklarowany, stosuje się każdą regułę, jeśliS
stanowi deklarację klasy, inną niżobject
:- Jeśli
M
jest stałą, polem, właściwością, zdarzeniem, elementem członkowskim wyliczenia lub deklaracją typu, wszystkie elementy członkowskie zadeklarowane w deklaracji interfejsu zostaną usunięte z zestawu. - Jeśli
M
jest metodą, wszystkie elementy nienależące do metody zadeklarowane w deklaracji interfejsu zostaną usunięte z zestawu, a wszystkie metody z tym samym podpisem coM
zadeklarowane w deklaracji interfejsu zostaną usunięte z zestawu.
- Jeśli
- Na koniec, po usunięciu ukrytych członków, określany jest wynik wyszukiwania:
- Jeśli zestaw składa się z jednego elementu członkowskiego, który nie jest metodą, ten element członkowski jest wynikiem wyszukiwania.
- W przeciwnym razie, jeśli zestaw zawiera tylko metody, ta grupa metod jest wynikiem wyszukiwania.
- W przeciwnym razie wyszukiwanie jest niejednoznaczne i występuje błąd czasu powiązania.
W przypadku wyszukiwania członków w typach innych niż parametry typów i interfejsy oraz wyszukiwania członków w interfejsach, które są ściśle jednokrotnego dziedziczenia (każdy interfejs w łańcuchu dziedziczenia ma dokładnie zero lub jeden bezpośredni interfejs bazowy), efektem reguł wyszukiwania jest po prostu to, że członkowie pochodni ukrywają członków bazowych o tej samej nazwie lub podpisie. Takie wyszukiwania pojedynczego dziedziczenia nigdy nie są niejednoznaczne. Niejednoznaczności, które mogą wynikać z odwołań do członków w interfejsach wielokrotnego dziedziczenia, są opisane w §18.4.6.
Uwaga: ta faza stanowi tylko jeden rodzaj niejednoznaczności. Jeśli rezultatem wyszukiwania składowego jest grupa metod, dalsze użycia tej grupy metod mogą zakończyć się niepowodzeniem z powodu niejednoznaczności, na przykład opisanym w §12.6.4.1 i §12.6.6.2. uwaga końcowa
12.5.2 Typy podstawowe
Do celów wyszukiwania członków, typ T
jest uważany za mający następujące typy bazowe.
- Jeśli
T
jestobject
lubdynamic
,T
nie ma typu podstawowego. - Jeśli
T
jest enum_type, podstawowymi typamiT
są typy klasSystem.Enum
,System.ValueType
iobject
. - Jeśli
T
jest struct_type, to podstawowymi typamiT
są te typy klasSystem.ValueType
iobject
.Uwaga: nullable_value_type jest struct_type (§8.3.1). uwaga końcowa
- Jeśli
T
jest class_type, podstawowe typyT
są klasami podstawowymiT
, w tym typ klasyobject
. - Jeśli
T
jest interface_type, to podstawowe typyT
są podstawowymi interfejsamiT
oraz klasowym typemobject
. - Jeśli
T
jest typu tablicy , to podstawowe typyT
są typami klasSystem.Array
iobject
. - Jeśli
T
jest typem delegata , podstawowe typy dlaT
to typy klasSystem.Delegate
iobject
.
Elementy funkcji
12.6.1 Ogólne
Członkowie funkcji to elementy zawierające instrukcje wykonywalne. Składowe funkcji są zawsze członkami typów i nie mogą być członkami przestrzeni nazw. Język C# definiuje następujące kategorie elementów członkowskich funkcji:
- Metody
- Właściwości
- Zdarzenia
- Indeksujący
- Operatory zdefiniowane przez użytkownika
- Konstruktory wystąpień
- Konstruktory statyczne
- Finalizatory
Z wyjątkiem finalizatorów i konstruktorów statycznych (których nie można wywołać jawnie), instrukcje zawarte w elementach członkowskich funkcji są wykonywane za pośrednictwem wywołań składowych funkcji. Faktyczna składnia wywołania członka funkcji zależy od konkretnej kategorii członka funkcji.
Lista argumentów (§12.6.2) wywołania składowej funkcji zawiera rzeczywiste wartości lub odwołania do zmiennych parametrów elementu członkowskiego funkcji.
Wywołania metod ogólnych mogą używać wnioskowania typu w celu określenia zestawu argumentów typu, które mają zostać przekazane do metody. Ten proces jest opisany w §12.6.3.
Wywołania metod, indeksatorów, operatorów i konstruktorów instancji wykorzystują rozpoznawanie przeciążeń w celu określenia, który z członków funkcji z zestawu kandydatów należy wywołać. Ten proces jest opisany w §12.6.4.
Po zidentyfikowaniu określonego członka funkcji w trakcie wiązania, możliwe poprzez rozpoznanie przeciążenia, rzeczywisty proces wywoływania członka funkcji jest opisany w §12.6.6.
Uwaga: Poniższa tabela zawiera podsumowanie przetwarzania, które odbywa się w konstrukcjach obejmujących sześć kategorii składowych funkcji, które można jawnie wywołać. W tabeli
e
,x
,y
ivalue
wskazują wyrażenia sklasyfikowane jako zmienne lub wartości,T
wskazuje wyrażenie sklasyfikowane jako typ,F
jest prostą nazwą metody, aP
jest prostą nazwą właściwości.
Konstruować Przykład Opis Wywołanie metody F(x, y)
Rozpoznawanie przeciążenia jest stosowane, aby wybrać najlepszą metodę F
w zawierającej klasie lub strukturze. Metoda jest wywoływana z listą argumentów(x, y)
. Jeśli metoda nie jeststatic
, wyrażenie wystąpienia jestthis
.T.F(x, y)
Rozpoznawanie przeciążenia jest stosowane w celu wybrania najlepszej metody F
w klasie lub strukturyT
. Błąd czasu wiązania występuje, jeśli metoda nie jeststatic
. Metoda jest wywoływana z listą argumentów(x, y)
.e.F(x, y)
Rozpoznawanie przeciążenia jest stosowane w celu wybrania najlepszej metody F
w klasie, strukturze lub interfejsie określonym przez type
. Błąd związany z czasem wiązania występuje, jeśli metoda jeststatic
. Metoda jest wywoływana z użyciem wyrażenia instancjie
oraz listy argumentów(x, y)
.Dostęp do właściwości P
Wywołany jest akcesor pobierający właściwości P
w zawierającej klasie lub strukturze. Błąd czasu kompilacji występuje, jeśliP
jest tylko do zapisu. JeśliP
nie jeststatic
, wyrażenie wystąpienia jestthis
.P = value
Akcesor ustawiający właściwości P
w zawierającej klasie lub strukturze zostaje wywołany z listą argumentów(value)
. Błąd czasu kompilacji występuje, jeśliP
jest ustawione jako tylko do odczytu. JeśliP
nie jeststatic
, wyrażenie wystąpienia jestthis
.T.P
Wywoływany jest akcesor get właściwości P
w klasie lub strukturzeT
. Błąd występuje w czasie kompilacji, jeśliP
nie jeststatic
lub jeśliP
jest tylko do zapisu.T.P = value
Akcesor ustawiający właściwości P
w klasieT
lub strukturze jest wywoływany z listą argumentów(value)
. Błąd czasu kompilacji występuje, jeśliP
nie jeststatic
lub jeśliP
jest ustawione jako tylko do odczytu.e.P
Akcesor get właściwości P
w klasie, strukturze lub interfejsie danej przez typE
jest wywoływany przy użyciu wyrażenia instancyjnegoe
. Błąd czasu powiązania występuje, jeśliP
jeststatic
lub jeśliP
jest tylko do zapisu.e.P = value
Akcesor set właściwości P
w klasie, strukturze lub interfejsie określonym przez typE
zostaje wywołany z użyciem wyrażenia instancjie
i listy argumentów(value)
. Błąd czasu powiązania występuje, jeśliP
jeststatic
lub jeśliP
jest tylko do odczytu.Dostęp do zdarzeń E += value
Wywoływany jest akcesor dodawania zdarzenia E
w klasie lub strukturze zawierającej. JeśliE
nie jeststatic
, wyrażenie wystąpienia jestthis
.E -= value
Wywołano akcesor usuwania zdarzenia E
w klasie lub strukturze zawierającej. JeśliE
nie jeststatic
, wyrażenie wystąpienia jestthis
.T.E += value
Wywoływany jest akcesor dodający zdarzenia E
w klasieT
lub strukturze. Błąd czasu powiązania występuje, jeśliE
nie jeststatic
.T.E -= value
Wywołano mutator usuwający zdarzenia E
w klasieT
lub strukturze. Błąd czasu powiązania występuje, jeśliE
nie jeststatic
.e.E += value
Akcesor dodawania zdarzenia E
w klasie, strukturze lub interfejsie określonym przez typE
jest wywoływane z wyrażeniem wystąpieniae
. Błąd czasu wiązania występuje, jeśliE
jeststatic
.e.E -= value
Akcesor usuwania zdarzenia E
w klasie, strukturze lub interfejsie danego typuE
jest wywoływany z użyciem wyrażenia instancjie
. Błąd czasu wiązania występuje, jeśliE
jeststatic
.Dostęp indeksatora e[x, y]
Rozpoznawanie przeciążenia jest stosowane w celu wybrania najlepszego indeksatora w klasie, strukturze lub interfejsie określonym przez typ e
. Akcesor pobierający indeksatora jest wywoływany za pomocą wyrażenia wystąpieniae
i listy argumentów(x, y)
. Błąd czasu powiązania występuje, jeśli indeksator jest przeznaczony wyłącznie do zapisu.e[x, y] = value
Rozpoznawanie przeciążenia jest stosowane w celu wybrania najlepszego indeksatora w klasie, strukturze lub interfejsie określonym przez typ e
. Akcesor ustawiający indeksatora jest wywoływany z wyrażeniem instancjie
i listą argumentów(x, y, value)
. Błąd na etapie wiązania występuje, jeśli indeksator jest tylko do odczytu.Wywołanie operatora -x
Rozdzielczość przeciążenia jest stosowana w celu wybrania najlepszego operatora jednoargumentowego w klasie lub struktury podanej przez typ x
. Wybrany operator jest wywoływany z listą argumentów(x)
.x + y
Rozpoznawanie przeciążeń jest stosowane w celu wybrania najlepszego operatora binarnego w klasach lub strukturach podanych przez typy x
iy
. Wybrany operator jest wywoływany z listą argumentów(x, y)
.Wywołanie konstruktora wystąpienia new T(x, y)
Rozpoznawanie przeciążenia jest stosowane w celu wybrania najlepszego konstruktora instancji w klasie lub strukturze T
. Konstruktor instancji jest wywoływany z listą argumentów(x, y)
.uwaga końcowa
12.6.2 Listy argumentów
12.6.2.1 Ogólne
Każda składowa funkcji i wywołanie delegata zawierają listę argumentów, która dostarcza rzeczywiste wartości lub odwołania do zmiennych dla parametrów elementu funkcji. Składnia określająca listę argumentów wywołania elementu członkowskiego funkcji zależy od kategorii składowej funkcji:
- Na przykład w konstruktorach, metodach, indeksatorach i delegatach argumenty są określane jako argument_list, jak opisano poniżej. W przypadku indeksatorów, podczas wywoływania akcesora set, lista argumentów dodatkowo zawiera wyrażenie określone jako operand po prawej stronie operatora przypisania.
Uwaga: Ten dodatkowy argument nie jest używany do rozstrzygania przeciążenia, a jedynie podczas wywołania akcesora set. uwaga końcowa
- W przypadku właściwości lista argumentów jest pusta przy wywoływaniu akcesora get i składa się z wyrażenia określonego jako prawy operand operatora przypisania podczas wywoływania akcesora set.
- W przypadku zdarzeń lista argumentów składa się z wyrażenia określonego jako prawy operand operatora
+=
lub-=
. - W przypadku operatorów zdefiniowanych przez użytkownika lista argumentów składa się z pojedynczego operandu operatora jednoargumentowego lub dwóch operandów operatora binarnego.
Argumenty właściwości (§15.7) i zdarzenia (§15.8) są zawsze przekazywane jako parametry wartości (§15.6.2.2). Argumenty operatorów zdefiniowanych przez użytkownika (§15.10) są zawsze przekazywane jako parametry wartości (§15.6.2.2) lub parametry wejściowe (§9.2.8). Argumenty indeksatorów (§15.9) są zawsze przekazywane jako parametry wartości (§15.6.2.2), parametry wejściowe (§9.2.8) lub tablice parametrów (§15.6.2.4). Parametry wyjściowe i referencyjne nie są obsługiwane dla tych kategorii członków funkcji.
Argumenty konstruktora wystąpienia, metody, indeksatora lub wywołania delegata są określane w postaci argument_list:
argument_list
: argument (',' argument)*
;
argument
: argument_name? argument_value
;
argument_name
: identifier ':'
;
argument_value
: expression
| 'in' variable_reference
| 'ref' variable_reference
| 'out' variable_reference
;
argument_list składa się z co najmniej jednego argumentus oddzielonego przecinkami. Każdy argument składa się z opcjonalnego argument_name, po którym następuje argument_value. Argument z nazwą argument_name jest nazywany argumentem nazwanym , natomiast argument bez nazwy argument_name jest argumentem pozycyjnym .
argument_value może przyjąć jedną z następujących form:
- Wyrażenie , wskazujące, że argument jest przekazywany jako parametr wartości lub jest przekształcany w parametr wejściowy, a następnie przekazywany jako taki, co określone jest w (§12.6.4.2 ) i opisane w §12.6.2.3.
- Słowo kluczowe
in
, po którym następuje odwołanie_do_zmiennej (§9.5), wskazujące, że argument jest podawany jako parametr wejściowy (§15.6.2.3.2). Zmienna musi być zdecydowanie przypisana (§9.4), zanim będzie można ją przekazać jako parametr wejściowy. - Słowo kluczowe
ref
następnie variable_reference (§9.5), wskazując, że argument jest przekazywany jako parametr referencyjny (§15.6.2.3.3). Zmienna musi być zdecydowanie przypisana (§9.4), zanim będzie można ją przekazać jako parametr referencyjny. - Słowo kluczowe
out
wraz z odwołaniem do zmiennej (§9.5), wskazujące, że argument jest przekazywany jako parametr wyjściowy (§15.6.2.3.4). Zmienna jest uznawana za zdecydowanie przypisaną (§9.4) po wywołaniu elementu członkowskiego funkcji, w którym zmienna jest przekazywana jako parametr wyjściowy.
Forma określa tryb przekazywania parametrów argumentu: wartość, wejście, odwołanielub wyjście, odpowiednio. Jednak jak wspomniano powyżej, argument z trybem przekazywania wartości może zostać przekształcony w jeden z trybem przekazywania danych wejściowych.
Przekazywanie pola nietrwałego (§15.5.4) jako parametr wejściowy, wyjściowy lub referencyjny powoduje ostrzeżenie, ponieważ pole może nie być traktowane jako nietrwałe przez wywołaną metodę.
12.6.2.2 Odpowiednie parametry
Dla każdego argumentu na liście argumentów musi istnieć odpowiadający parametr w wywoływanym członku funkcji lub delegacie.
Lista parametrów używana poniżej jest określona następująco:
- W przypadku metod wirtualnych i indeksatorów zdefiniowanych w klasach lista parametrów jest wybierana z pierwszej deklaracji lub zastępowania elementu członkowskiego funkcji znalezionego podczas rozpoczynania od statycznego typu odbiornika i przeszukiwania jego klas bazowych.
- W przypadku metod częściowych używana jest lista parametrów definiującej deklarację metody częściowej.
- Dla wszystkich pozostałych elementów funkcyjnych oraz delegatów istnieje tylko jedna lista parametrów, która jest stosowana.
Pozycja argumentu lub parametru jest definiowana jako liczba argumentów lub parametrów poprzedzających go na liście argumentów lub na liście parametrów.
Odpowiednie parametry argumentów składowych funkcji są ustanawiane w następujący sposób:
- Argumenty w argument_list konstruktorów instancji, metod, indeksatorów i delegatów:
- Argument pozycyjny, w którym parametr występuje na tej samej pozycji na liście parametrów, odpowiada tego parametru, chyba że parametr jest tablicą parametrów, a element członkowski funkcji jest wywoływany w postaci rozwiniętej.
- Argument pozycyjny elementu członkowskiego funkcji z tablicą parametrów wywoływaną w rozszerzonej formie, która występuje w lub po pozycji tablicy parametrów na liście parametrów, odpowiada elementowi w tablicy parametrów.
- Nazwany argument odpowiada parametrowi o tej samej nazwie na liście parametrów.
- W przypadku indeksatorów, podczas wywoływania akcesora set, wyrażenie określone jako prawy operand operatora przypisania odpowiada niejawnemu parametrowi
value
deklaracji akcesora set.
- W przypadku właściwości podczas wywoływania akcesora 'get' nie ma żadnych argumentów. Podczas wywoływania akcesora ustawiającego wyrażenie określone jako prawy operand operatora przypisania odpowiada niejawnie przekazywanemu parametrowi wartości w deklaracji akcesora ustawiającego.
- W przypadku operatorów jednoargumentowych zdefiniowanych przez użytkownika (w tym konwersji) pojedynczy operand odpowiada pojedynczemu parametrowi deklaracji operatora.
- W przypadku operatorów binarnych zdefiniowanych przez użytkownika lewy operand odpowiada pierwszemu parametrowi, a prawy operand odpowiada drugiemu parametrowi deklaracji operatora.
- Argument nienazwany nie odpowiada żadnemu parametrowi, gdy pojawia się po nazwanym argumencie, który jest poza swoją pozycją, lub po nazwanym argumencie, który odpowiada tablicy parametrów.
Uwaga: uniemożliwia to wywoływanie
void M(bool a = true, bool b = true, bool c = true);
przezM(c: false, valueB);
. Pierwszy argument jest używany poza pozycją (argument jest używany w pierwszej pozycji, ale parametr o nazwiec
znajduje się na trzeciej pozycji), więc należy nazwać następujące argumenty. Innymi słowy, argumenty nazwane inne niż końcowe są dozwolone tylko wtedy, gdy nazwa i pozycja powodują znalezienie tego samego odpowiedniego parametru. uwaga końcowa
12.6.2.3 Ocena czasu wykonywania list argumentów
Podczas przetwarzania w czasie wykonywania wywołania elementu członkowskiego funkcji (§12.6.6), wyrażenia lub odwołania zmiennych listy argumentów są oceniane w kolejności od lewej do prawej w następujący sposób:
W przypadku argumentu typu wartość, jeśli tryb przekazywania parametru to wartość
wyrażenie argumentu jest obliczane i wykonywana jest niejawna konwersja (§10.2) do odpowiedniego typu parametru. Wynikowa wartość staje się początkową wartością parametru wartości w wywołaniu elementu członkowskiego funkcji.
w przeciwnym razie tryb przekazywania parametru to tryb wejściowy. Jeśli argument jest odwołaniem do zmiennej i istnieje konwersja tożsamości (§10.2.2) między typem argumentu a typem parametru, wynikowa lokalizacja magazynu staje się lokalizacją magazynu reprezentowaną przez parametr w wywołaniu elementu funkcji. W przeciwnym razie zostanie utworzona lokalizacja magazynu o tym samym typie co odpowiedni parametr. Wyrażenie argumentu jest obliczane i wykonywana jest niejawna konwersja (§10.2) do odpowiedniego typu parametru. Wynikowa wartość jest przechowywana w tej lokalizacji magazynu. Ta lokalizacja magazynu jest reprezentowana przez parametr wejściowy w wywołaniu członka funkcji.
Przykład: przy uwzględnieniu następujących deklaracji i wywołań metody:
static void M1(in int p1) { ... } int i = 10; M1(i); // i is passed as an input argument M1(i + 5); // transformed to a temporary input argument
W wywołaniu metody
M1(i)
sami
jest przekazywany jako argument wejściowy, ponieważ jest klasyfikowany jako zmienna i ma ten sam typint
co parametr wejściowy. W wywołaniu metodyM1(i + 5)
zostanie utworzona zmiennaint
bez nazwy, zainicjowana przy użyciu wartości argumentu, a następnie przekazana jako argument wejściowy. Zobacz §12.6.4.2 i §12.6.4.4.koniec przykładu
W przypadku argumentu wejściowego, wyjściowego lub odwołania, referencja zmiennej jest oceniana, a wynikowa lokalizacja pamięci staje się lokalizacją pamięci, która jest przypisana do parametru w wywołaniu członka funkcji. W przypadku argumentu wejściowego lub referencyjnego zmienna musi być zdecydowanie przypisana w momencie wywołania metody. Jeśli odwołanie do zmiennej jest podane jako argument wyjściowy lub jest elementem tablicy reference_type, wykonywane jest sprawdzanie czasu wykonywania, aby upewnić się, że typ elementu tablicy jest identyczny z typem parametru. Jeśli to sprawdzenie zakończy się niepowodzeniem, zostanie wygenerowany
System.ArrayTypeMismatchException
.
Uwaga: to sprawdzanie w czasie wykonywania jest wymagane z powodu kowariancji tablic (§17.6). uwaga końcowa
Przykład: w poniższym kodzie
class Test { static void F(ref object x) {...} static void Main() { object[] a = new object[10]; object[] b = new string[10]; F(ref a[0]); // Ok F(ref b[1]); // ArrayTypeMismatchException } }
drugie wywołanie
F
powoduje wyrzucenieSystem.ArrayTypeMismatchException
, ponieważ typ rzeczywisty elementub
tostring
, a nieobject
.koniec przykładu
Metody, indeksatory i konstruktory wystąpień mogą zadeklarować ich najbardziej odpowiedni parametr jako tablicę parametrów (§15.6.2.4). Tacy członkowie funkcji są wywoływani w postaci normalnej lub rozszerzonej, w zależności od zastosowania (§12.6.4.2):
- Gdy składowa funkcji z tablicą parametrów jest wywoływana w postaci normalnej, argument podany dla tablicy parametrów jest pojedynczym wyrażeniem, które jest niejawnie konwertowane (§10.2) do typu tablicy parametrów. W tym przypadku tablica parametrów działa dokładnie jak parametr wartości.
- Gdy element członkowski funkcji z tablicą parametrów jest wywoływany w rozszerzonej formie, wywołanie określa zero lub więcej argumentów pozycyjnych dla tablicy parametrów, gdzie każdy argument jest wyrażeniem niejawnie konwertowanym (§10.2) do typu elementu tablicy parametrów. W tym przypadku wywołanie tworzy wystąpienie typu tablicy parametrów o długości odpowiadającej liczbie argumentów, inicjuje elementy wystąpienia tablicy z podanymi wartościami argumentów i używa nowo utworzonego wystąpienia tablicy jako rzeczywistego argumentu.
Wyrażenia listy argumentów są zawsze oceniane w kolejności tekstowej.
przykładowo: Tak więc, przykład
class Test { static void F(int x, int y = -1, int z = -2) => Console.WriteLine($"x = {x}, y = {y}, z = {z}"); static void Main() { int i = 0; F(i++, i++, i++); F(z: i++, x: i++); } }
generuje dane wyjściowe
x = 0, y = 1, z = 2 x = 4, y = -1, z = 3
koniec przykładu
Gdy element członkowski funkcji z tablicą parametrów jest wywoływany w rozszerzonej formie z co najmniej jednym rozwiniętym argumentem, wywołanie jest przetwarzane tak, jakby wyrażenie tworzenia tablicy z inicjatorem tablicy (§12.8.17.5) zostało wstawione wokół rozszerzonych argumentów. Pusta tablica jest przekazywana, gdy nie ma argumentów dla tablicy parametrów; Nie określono, czy przekazane odwołanie jest do nowo przydzielonej, czy istniejącej pustej tablicy.
Przykład: Podana deklaracja
void F(int x, int y, params object[] args);
następujące wywołania rozszerzonej formy metody
F(10, 20, 30, 40); F(10, 20, 1, "hello", 3.0);
odpowiada dokładnie
F(10, 20, new object[] { 30, 40 }); F(10, 20, new object[] { 1, "hello", 3.0 });
koniec przykładu
Gdy argumenty są pomijane z elementu członkowskiego funkcji z odpowiednimi opcjonalnymi parametrami, domyślne argumenty deklaracji składowej funkcji są niejawnie przekazywane. (Może to obejmować utworzenie lokalizacji magazynu, zgodnie z powyższym opisem).
Uwaga: Ponieważ są one zawsze stałe, ich ocena nie będzie mieć wpływu na ocenę pozostałych argumentów. uwaga końcowa
12.6.3 Wnioskowanie typu
12.6.3.1 Ogólne
Gdy metoda ogólna jest wywoływana bez określania argumentów typu, proces wnioskowania typu próbuje określić argumenty typu dla wywołania. Obecność wnioskowania typu umożliwia użycie wygodniejszej składni do wywoływania metody ogólnej i umożliwia programistom unikanie określania nadmiarowych informacji o typie.
Przykład:
class Chooser { static Random rand = new Random(); public static T Choose<T>(T first, T second) => rand.Next(2) == 0 ? first : second; } class A { static void M() { int i = Chooser.Choose(5, 213); // Calls Choose<int> string s = Chooser.Choose("apple", "banana"); // Calls Choose<string> } }
Za pomocą wnioskowania typu argumenty typu
int
istring
są określane z argumentów do metody.koniec przykładu
Wnioskowanie typu odbywa się w ramach przetwarzania w czasie powiązania wywołania metody (§12.8.10.2) i odbywa się przed krokiem rozwiązywania przeciążenia wywołania. Gdy określona grupa metod jest określona w wywołaniu metody i nie określono żadnych argumentów typu w ramach wywołania metody, wnioskowanie typu jest stosowane do każdej metody ogólnej w grupie metod. Jeśli wnioskowanie typu powiedzie się, wnioskowane argumenty typu są używane do określenia typów argumentów podczas późniejszego rozpoznawania przeciążeń. Jeśli rozpoznawanie przeciążenia wybierze metodę ogólną jako metodę do wywołania, argumenty typu wnioskowanego są używane jako argumenty typu dla wywołania. Jeśli wnioskowanie typu dla określonej metody nie powiedzie się, ta metoda nie uczestniczy w rozwiązywaniu przeciążenia. Błąd wnioskowania typu samo w sobie nie powoduje błędu czasu wiązania. Jednak często prowadzi to do błędu czasu wiązania, gdy rozwiązanie przeciążenia nie znajduje żadnych odpowiednich metod.
Jeśli każdy podany argument nie odpowiada dokładnie jednemu parametrowi w metodzie (§12.6.2.2), lub nie ma opcjonalnego parametru bez odpowiedniego argumentu, wnioskowanie natychmiast kończy się niepowodzeniem. W przeciwnym razie załóżmy, że metoda ogólna ma następujący podpis:
Tₑ M<X₁...Xᵥ>(T₁ p₁ ... Tₓ pₓ)
Wywołanie metody o postaci M(E₁ ...Eₓ)
polega na zadaniu wnioskowania typu, które wymaga znalezienia unikatowych argumentów typu S₁...Sᵥ
dla każdego z parametrów typu X₁...Xᵥ
, tak aby wywołanie M<S₁...Sᵥ>(E₁...Eₓ)
było prawidłowe.
Proces wnioskowania typu jest opisany poniżej jako algorytm. Kompilator zgodny może zostać zaimplementowany przy użyciu alternatywnego podejścia, pod warunkiem, że osiągnie ten sam wynik we wszystkich przypadkach.
Podczas procesu wnioskowania każdy parametr typu Xᵢ
jest albo ustalony do określonego typu Sᵢ
, albo niewiązany, z przypisanym zestawem ograniczeń . Każde z ograniczeń to jakiś typ T
. Początkowo każda zmienna typu Xᵢ
jest niefiksowana z pustym zestawem granic.
Wnioskowanie typu odbywa się w fazach. Każda faza będzie próbowała wywnioskować argumenty typu dla większej liczby zmiennych typu na podstawie wyników poprzedniej fazy. Pierwsza faza powoduje początkowe wnioskowanie granic, podczas gdy druga faza ustala zmienne typu na określone typy i wnioskuje dalsze ograniczenia. Druga faza może być powtarzana kilka razy.
Uwaga: wnioskowanie typu jest również używane w innych kontekstach, w tym do konwersji grup metod (§12.6.3.14) i znalezienie najlepszego wspólnego typu zestawu wyrażeń (§12.6.3.15). uwaga końcowa
12.6.3.2 Pierwsza faza
Dla każdego argumentu metody Eᵢ
:
- Jeśli
Eᵢ
jest funkcją anonimową, jawne wnioskowanie typu parametru (§12.6.3.8) jest wykonywane zEᵢ
doTᵢ
- W przeciwnym razie, jeśli
ma typ , a odpowiadający mu parametr jest parametrem wartości ( §15.6.2.2 ),( §12.6.3.10 ) jestz do . - W przeciwnym razie, jeśli
Eᵢ
ma typU
, a odpowiedni parametr jest parametrem referencyjnym (§15.6.2.3.3) lub parametr wyjściowy (§15.6.2 3.4) następnie dokładne wnioskowanie (§12.6.3.9) jest wykonywane zU
doTᵢ
. - W przeciwnym razie, jeśli
Eᵢ
ma typU
, a odpowiadający mu parametr jest parametrem wejściowym (§15.6.2.3.2) iEᵢ
jest argumentem wejściowym, wówczas dokładnym wnioskowaniem (§12.6.3.9) jest zU
doTᵢ
. - W przeciwnym razie, jeśli
Eᵢ
ma typU
, a odpowiedni parametr jest parametrem wejściowym (§15.6.2.3.2), niższego wnioskowania granic (§12.6.3.10) jest zU
doTᵢ
. - W przeciwnym razie dla tego argumentu nie jest wykonywane żadne wnioskowanie.
12.6.3.3 Druga faza
Druga faza jest kontynuowana w następujący sposób:
- Wszystkie niefiksowane zmienne typu
Xᵢ
, które nie zależą od (§12.6.3.6) wszystkieXₑ
są stałe (§12.6.3.12). - Jeśli takie zmienne typu nie istnieją, wszystkie niefiksowane zmienne typu
Xᵢ
są stałe, dla których wszystkie następujące blokady:- Istnieje co najmniej jedna zmienna typu
Xₑ
, która zależy odXᵢ
-
Xᵢ
ma niepusty zbiór ograniczeń
- Istnieje co najmniej jedna zmienna typu
- Jeśli takie zmienne typu nie istnieją i nadal istnieją niefiksowane zmienne typu, wnioskowanie typu kończy się niepowodzeniem.
- W przeciwnym razie, jeśli nie istnieją żadne dodatkowe niewiązane zmienne typu, wnioskowanie typu powiedzie się.
- W przeciwnym razie dla wszystkich argumentów
Eᵢ
z odpowiednim typem parametruTᵢ
, w których typy danych wyjściowych (§12.6.3.5) zawierają niefiksowane zmienne typuXₑ
, ale typy wejściowe (§12.6.3.4) nie, typ danych wyjściowych (§12.6.3.7) jest zEᵢ
doTᵢ
. Następnie druga faza jest powtarzana.
Typy danych wejściowych 12.6.3.4
Jeśli E
jest grupą metod lub niejawnie typowaną funkcją anonimową, a T
jest typem delegata lub typem drzewa wyrażeń, to wszystkie typy parametrów T
są typami wejściowymi E
z typemT
.
Typy danych wyjściowych 12.6.3.5
Jeśli E
jest grupą metod lub funkcją anonimową, a T
jest typem delegata lub typem drzewa wyrażeń, zwracany typ T
jest typem danych wyjściowych typuE
z typemT
.
12.6.3.6 Zależność
niefiksowana zmienna typuXᵢ
zależy bezpośrednio odniefiksowanej zmiennej typuXₑ
, jeśli dla niektórych Eᵥ
argumentów z typem Tᵥ
Xₑ
występuje w typie wejściowymEᵥ
z typem Tᵥ
i Xᵢ
występuje w typie wyjściowymEᵥ
z typem Tᵥ
.
Xₑ
zależy odXᵢ
, czy Xₑ
zależy bezpośrednio odXᵢ
, czy Xᵢ
zależy bezpośrednio odXᵥ
i Xᵥ
zależy odXₑ
. W związku z tym "zależy od" jest domknięciem przechodnim, ale nie domknięciem refleksyjnym "zależy bezpośrednio od".
12.6.3.7 Wnioskowanie typu danych wyjściowych
Wnioskowanie typu wyjściowego jest wykonywane z wyrażenia E
do typu T w następujący sposób:
- Jeśli
E
jest funkcją anonimową z wywnioskowanym typem zwracanymU
(§12.6.3.13) iT
jest typem delegata lub typem drzewa wyrażeń z typem zwracanymTₓ
, następnie wnioskowania o niższej granicy (§12.6.3.10) jest zU
doTₓ
. - W przeciwnym razie jeśli
jest grupą metod, a jest typem typu delegata lub drzewa wyrażeń z typami parametrów i zwracanym typem , a rozpoznawanie przeciążenia z typami zwraca jedną metodę z typem zwracanym , jest wykonywane z do . - W przeciwnym razie, jeśli
E
jest wyrażeniem z typemU
, wnioskowania niższego jest wykonywane zU
doT
. - W przeciwnym razie nie są wykonywane żadne wnioskowania.
12.6.3.8 Jawne wnioskowania typu parametru
jawnego wnioskowania typu parametru jest wykonywane z wyrażenia E
w celu typu T
w następujący sposób:
- Jeśli
E
jest jawnie wpisaną funkcją anonimową z typami parametrówU₁...Uᵥ
, aT
jest typem delegowanym lub typem drzewa wyrażeń z typami parametrówV₁...Vᵥ
, a następnie dla każdegoUᵢ
dokładnego wnioskowania (§12.6.3.9) jest zUᵢ
odpowiedniegoVᵢ
.
12.6.3.9 Dokładne wnioskowania
Dokładne wnioskowanie z typu U
do typu V
jest wykonywane w następujący sposób:
- Jeśli
V
jest jednym z niefiksowanychXᵢ
, toU
zostanie dodany do zestawu dokładnych granic dlaXᵢ
. - W przeciwnym razie zestawy
V₁...Vₑ
iU₁...Uₑ
są określane przez sprawdzenie, czy ma zastosowanie dowolny z następujących przypadków:-
V
jest typem tablicyV₁[...]
, aU
jest typem tablicyU₁[...]
tej samej rangi -
V
jest typemV₁?
, aU
jest typemU₁
-
V
jest skonstruowanym typemC<V₁...Vₑ>
, aU
jest skonstruowanym typemC<U₁...Uₑ>
Jeśli którykolwiek z tych przypadków ma zastosowanie, dokładne wnioskowanie jest wykonywane z każdegoUᵢ
do odpowiedniegoVᵢ
.
-
- W przeciwnym razie nie są wykonywane żadne wnioskowania.
12.6.3.10 Wnioskowanie dotyczące dolnej granicy
Wnioskowanie od typu U
do typu V
jest wykonywane w następujący sposób:
- Jeśli
V
jest jednym z niefiksowanychXᵢ
,U
zostanie dodany do zestawu dolnych granic dlaXᵢ
. - W przeciwnym razie, jeśli
V
jest typemV₁?
, aU
jest typemU₁?
, wtedy następuje wnioskowanie dolnej granicy zU₁
doV₁
. - W przeciwnym razie zestawy
U₁...Uₑ
iV₁...Vₑ
są określane przez sprawdzenie, czy ma zastosowanie dowolny z następujących przypadków:-
V
jest typem tablicyV₁[...]
, aU
jest typem tablicyU₁[...]
tej samej rangi -
V
jest jednym zIEnumerable<V₁>
,ICollection<V₁>
,IReadOnlyList<V₁>>
,IReadOnlyCollection<V₁>
lubIList<V₁>
, aU
jest jednowymiarowym typem tablicyU₁[]
-
V
jest skonstruowanym typemclass
, takim jakstruct
,interface
,delegate
lubC<V₁...Vₑ>
, i istnieje unikalny typC<U₁...Uₑ>
, dla któregoU
(lub, jeśliU
jest typemparameter
, jego efektywną klasą bazową lub dowolnym członkiem skutecznego zestawu interfejsów) jest identyczny zinherits
(bezpośrednio lub pośrednio) albo implementuje (bezpośrednio lub pośrednio)C<U₁...Uₑ>
. - (Ograniczenie "unikatowości" oznacza, że w przypadku interfejsu
C<T>{} class U: C<X>, C<Y>{}
, podczas wnioskowania zU
doC<T>
nie jest wykonywane żadne wnioskowanie, ponieważU₁
może byćX
lubY
.)
Jeśli którykolwiek z tych przypadków ma zastosowanie, wnioskowanie jest wykonywane z każdegoUᵢ
do odpowiedniegoVᵢ
w następujący sposób: - Jeśli
Uᵢ
nie jest znany jako typ odwołania, zostanie wykonane dokładne wnioskowanie - W przeciwnym razie jeśli
U
jest typem tablicy, wykonywane jest wnioskowanie o granicy dolnej. - W przeciwnym razie, jeśli
V
jestC<V₁...Vₑ>
, to wnioskowanie zależy od parametru typui-th
wC
:- Jeśli jest kowariantny, wykonuje się wnioskowanie o niższą granicę.
- Jeśli jest kontrawariantny, wnioskowanie górnej granicy zostaje przeprowadzone.
- Jeśli jest niezmienny, wykonywane jest dokładne wnioskowanie .
-
- W przeciwnym razie nie są wykonywane żadne wnioskowania.
12.6.3.11 Wnioskowanie górnej granicy
Wnioskowanie górnej granicy od typu U
do typu V
jest wykonywane w następujący sposób:
- Jeśli
V
jest jednym z niefiksowanychXᵢ
,U
zostanie dodany do zestawu górnych granic dlaXᵢ
. - W przeciwnym razie zestawy
V₁...Vₑ
iU₁...Uₑ
są określane przez sprawdzenie, czy ma zastosowanie dowolny z następujących przypadków:-
U
jest typem tablicyU₁[...]
, aV
jest typem tablicyV₁[...]
tej samej rangi -
U
jest jednym zIEnumerable<Uₑ>
,ICollection<Uₑ>
,IReadOnlyList<Uₑ>
,IReadOnlyCollection<Uₑ>
lubIList<Uₑ>
, aV
jest jednowymiarowym typem tablicyVₑ[]
-
U
jest typemU1?
, aV
jest typemV1?
-
U
jest konstruowana klasa, struktura, interfejs lub typ delegataC<U₁...Uₑ>
, aV
jest typemclass, struct, interface
lubdelegate
, który jestidentical
,inherits
z (bezpośrednio lub pośrednio) lub implementuje (bezpośrednio lub pośrednio) unikatowy typC<V₁...Vₑ>
- (Ograniczenie "unikatowości" oznacza, że biorąc pod uwagę interfejs
C<T>{} class V<Z>: C<X<Z>>, C<Y<Z>>{}
, podczas wnioskowania zC<U₁>
doV<Q>
nie jest wykonywane żadne wnioskowanie. Wnioskowanie nie jest wykonywane zU₁
doX<Q>
lubY<Q>
).
Jeśli którykolwiek z tych przypadków ma zastosowanie, wnioskowanie jest wykonywane z każdegoUᵢ
do odpowiedniegoVᵢ
w następujący sposób: - Jeśli
Uᵢ
nie jest znany jako typ odwołania, zostanie wykonane dokładne wnioskowanie - W przeciwnym razie, jeśli
V
jest typem tablicy, zostanie wykonane wnioskowanie górnej granicy - W przeciwnym razie, jeśli
U
jestC<U₁...Uₑ>
, to wnioskowanie zależy od parametru typui-th
wC
:- Jeśli jest kowariantny, wykonywane jest wnioskowanie górnej granicy .
- Jeśli jest kontrawariantny, wykonywane jest wnioskowanie o niższej granicy .
- Jeśli jest niezmienny, wykonywane jest dokładne wnioskowanie .
-
- W przeciwnym razie nie są wykonywane żadne wnioskowania.
12.6.3.12 Naprawianie
Niefiksowana zmienna typu
- Zestaw typów kandydatów
Uₑ
zaczyna się jako zestaw wszystkich typów w zestawie granic dlaXᵢ
. - Każda granica dla
Xᵢ
jest badana po kolei: Dla każdej dokładnej granicy U dlaXᵢ
wszystkie typyUₑ
, które nie są identyczne zU
, są usuwane z zestawu kandydatów. Dla każdej niższej granicyU
zXᵢ
, wszystkie typyUₑ
, które nie mają niejawnej konwersji zU
, są usuwane z zestawu kandydatów. Dla każdej górnej granicy U zestawuXᵢ
, typyUₑ
, dla których nie istnieje niejawna konwersja naU
, są usuwane z zestawu kandydatów. - Jeśli wśród pozostałych typów kandydatów
Uₑ
istnieje unikatowy typV
, do którego istnieje niejawna konwersja ze wszystkich pozostałych typów kandydatów, toXᵢ
jest ustalony jakoV
. - W przeciwnym razie wnioskowanie typu kończy się niepowodzeniem.
12.6.3.13 Wnioskowany typ zwracany
Wnioskowany zwracany typ funkcji anonimowej F
jest używany podczas wnioskowania typu i rozpoznawania przeciążenia. Wywnioskowany typ zwracany można określić tylko dla funkcji anonimowej, w której znane są wszystkie typy parametrów, ponieważ są one jawnie określone, dostępne poprzez konwersję funkcji anonimowej lub wywnioskowane podczas inferencji typu na otaczającym wywołaniu metody generycznej.
wywnioskowany typ rzeczywistego zwracania jest określany w następujący sposób:
- Jeśli treść
F
jest wyrażeniem , które ma typ, to efektywny wnioskowany typ zwrotnyF
jest typem tego wyrażenia. - Jeśli treść
F
jest blokiem , a zestaw wyrażeń w instrukcjachreturn
bloku ma najlepszy wspólny typT
(§12.6.3.15), to wnioskowany efektywny typ zwracanyF
jestT
. - W przeciwnym razie nie można wywnioskować efektywnego typu zwracanego dla
F
.
wnioskowany typ zwracany jest określany w następujący sposób:
- Jeśli
F
jest asynchroniczny, a treśćF
jest wyrażeniem sklasyfikowanym jako „nic” (§12.2) lub blokiem, w którym żadna z instrukcjireturn
nie ma wyrażeń, wnioskowany typ zwracany to«TaskType»
(§15.15.1). - Jeśli
F
jest asynchroniczny i ma wnioskowany typ zwrotuT
, wnioskowany typ zwrotu jest«TaskType»<T>»
(§15.15.1). - Jeśli
F
nie jest asynchroniczny i ma wywnioskowany efektywny typ zwracanyT
, wnioskowany typ zwracany jestT
. - W przeciwnym razie nie można wywnioskować typu zwracanego dla
F
.
Przykład: jako przykład wnioskowania typu obejmującego funkcje anonimowe należy wziąć pod uwagę metodę rozszerzenia
Select
zadeklarowaną w klasieSystem.Linq.Enumerable
:namespace System.Linq { public static class Enumerable { public static IEnumerable<TResult> Select<TSource,TResult>( this IEnumerable<TSource> source, Func<TSource,TResult> selector) { foreach (TSource element in source) { yield return selector(element); } } } }
Przy założeniu, że przestrzeń nazw
System.Linq
została zaimportowana przy użyciu dyrektywyusing namespace
i biorąc pod uwagę klasęCustomer
z właściwościąName
typustring
, można użyć metodySelect
, aby wybrać nazwy klientów:List<Customer> customers = GetCustomerList(); IEnumerable<string> names = customers.Select(c => c.Name);
Wywołanie metody rozszerzenia (§12.8.10.3)
Select
jest przetwarzane przez ponowne zapisywanie wywołania metody statycznej:IEnumerable<string> names = Enumerable.Select(customers, c => c.Name);
Ponieważ argumenty typu nie zostały jawnie określone, wnioskowanie typu służy do wnioskowania argumentów typu. Najpierw argument klienta jest powiązany z parametrem źródłowym, co sugeruje, że
TSource
powinno byćCustomer
. Następnie, korzystając z opisanego powyżej procesu wnioskowania typu funkcji anonimowej,c
otrzymuje typCustomer
, a wyrażeniec.Name
odnosi się do typu zwrotnego parametru selektora, wnioskując, żeTResult
jeststring
. W związku z tym wywołanie jest równoważneSequence.Select<Customer,string>(customers, (Customer c) => c.Name)
a wynik jest typu
IEnumerable<string>
.W poniższym przykładzie pokazano, w jaki sposób wnioskowanie typu dla funkcji anonimowych pozwala informacjom o typie przepływać między argumentami w wywołaniu metody ogólnej. Biorąc pod uwagę następującą metodę i wywołanie:
class A { static Z F<X,Y,Z>(X value, Func<X,Y> f1, Func<Y,Z> f2) { return f2(f1(value)); } static void M() { double hours = F("1:15:30", s => TimeSpan.Parse(s), t => t.TotalHours); } }
wnioskowanie typu dla wywołania następuje w następujący sposób: Najpierw argument "1:15:30" jest powiązany z parametrem wartości, wnioskowując, że
X
jest ciągiem. Następnie parametr pierwszej funkcji anonimowej,s
, otrzymuje wywnioskowany typstring
, a wyrażenieTimeSpan.Parse(s)
jest powiązane z typem zwrotnymf1
, wnioskując, żeY
jestSystem.TimeSpan
. Na koniec, parametr drugiej funkcji anonimowej,t
, ma przypisany wywnioskowany typSystem.TimeSpan
, a wyrażeniet.TotalHours
jest powiązane z typem zwrotnymf2
, co pozwala wnioskować, żeZ
jestdouble
. W związku z tym wynik wywołania jest typudouble
.koniec przykładu
12.6.3.14 Wnioskowanie typów dla konwersji grup metod
Podobnie jak wywołania metod ogólnych, wnioskowanie typu stosuje się również, gdy grupa metod M
zawierająca metodę ogólną jest konwertowana na dany typ delegata D
(§10.8). Biorąc pod uwagę metodę
Tₑ M<X₁...Xᵥ>(T₁ x₁ ... Tₑ xₑ)
a grupa metod M
przypisana do typu delegata D
zadaniem wnioskowania typu jest znalezienie argumentów typu S₁...Sᵥ
tak, aby wyrażenie:
M<S₁...Sᵥ>
staje się zgodny (§20.2) z D
.
W przeciwieństwie do algorytmu wnioskowania typu dla wywołań metod ogólnych, w tym przypadku istnieją tylko typy argumentów , bez wyrażeń argumentów. W szczególności nie ma funkcji anonimowych i dlatego nie ma potrzeby wielu faz wnioskowania.
Zamiast tego wszystkie Xᵢ
są traktowane jako niefiksowane, a wnioskowania niższego jest wykonywane z każdego typu argumentu Uₑ
D
odpowiadający typ parametru Tₑ
M
. Jeśli dla któregokolwiek z Xᵢ
nie znaleziono żadnych granic, wnioskowanie typu nie powiedzie się. W przeciwnym razie wszystkie Xᵢ
są stałe odpowiadające Sᵢ
, które są wynikiem wnioskowania typu.
12.6.3.15 Znajdowanie najlepszego typowego typu zestawu wyrażeń
W niektórych przypadkach typ wspólny musi zostać wywnioskowany dla zestawu wyrażeń. W szczególności typy elementów niejawnie typizowanych tablic oraz zwracane typy funkcji anonimowych z ciałami bloków są znajdowane w ten sposób.
Najlepszy typ dla zbioru wyrażeń E₁...Eᵥ
jest określany w następujący sposób:
- Wprowadzono nową niefiksowaną zmienną typu
X
. - Dla każdego wyrażenia
Ei
przeprowadzane jest wnioskowanie typu danych wyjściowych (§12.6.3.7) od niego doX
. -
X
jest stały (§12.6.3.12), jeśli to możliwe, a wynikowy typ jest najlepszym typem typowym. - W przeciwnym razie wnioskowanie kończy się niepowodzeniem.
Uwaga: Intuicyjnie to wnioskowanie jest równoważne wywołaniu metody
void M<X>(X x₁ ... X xᵥ)
zEᵢ
jako argumentami i wnioskowaniemX
. uwaga końcowa
12.6.4 Rozpoznawanie przeciążenia
12.6.4.1 Ogólne
Rozwiązywanie przeciążeń to mechanizm czasu wiązania do wyboru najlepszego członka funkcji do wywołania, biorąc pod uwagę listę argumentów i zestaw kandydatów członków funkcji. Rozwiązanie przeciążenia wybiera człon funkcji do wywołania w następujących odrębnych kontekstach w języku C#:
- Wywołanie metody o nazwie określonej w invocation_expression (§12.8.10).
- Wywołanie konstruktora wystąpienia nazwanego w object_creation_expression (§12.8.17.2).
- Wywołanie akcesora indeksatora poprzez element_access (§12.8.12).
- Wywołanie wstępnie zdefiniowanego lub zdefiniowanego przez użytkownika operatora przywoływanego w wyrażeniu (§12.4.4 i §12.4.5).
Każdy z tych kontekstów definiuje zestaw elementów członkowskich funkcji kandydata i listę argumentów we własny unikatowy sposób. Na przykład zestaw kandydatów do wywołania metody nie obejmuje metod oznaczonych jako 'override' (§12.5), a metody w klasie bazowej nie są kandydatami, jeśli jakakolwiek metoda w klasie pochodnej może być zastosowana (§12.8.10.2).
Po zidentyfikowaniu członków funkcji kandydującej i listy argumentów wybór najlepszego członka funkcji jest taki sam we wszystkich przypadkach:
- Po pierwsze, zestaw członków funkcji kandydujących jest zawężony do tych, które są odpowiednie dla danej listy argumentów (§12.6.4.2). Jeśli ten ograniczony zestaw jest pusty, wystąpi błąd czasu kompilacji.
- Następnie znajduje się najlepszy element członkowski funkcji z zestawu odpowiednich członków funkcji kandydata. Jeśli zestaw zawiera tylko jeden członek funkcji, wówczas jest on najlepszym członkiem funkcji. W przeciwnym razie najlepszym elementem członkowskim funkcji jest jeden element członkowski funkcji, który jest lepszy niż wszystkie inne elementy członkowskie funkcji w odniesieniu do danej listy argumentów, pod warunkiem, że każdy element członkowski funkcji jest porównywany ze wszystkimi innymi elementami członkowskimi funkcji przy użyciu reguł w §12.6.4.3. Jeśli nie ma dokładnie jednego członka funkcji, który jest lepszy niż wszyscy inni członkowie funkcji, to wywołanie członka funkcji jest niejednoznaczne i występuje błąd w czasie wiązania.
Następujące podpunkty definiują dokładne znaczenie terminów odpowiednich składowych funkcji i lepszej składowej funkcji.
12.6.4.2 Stosowna funkcja członkowska
Mówi się, że członek funkcji jest odpowiednim członkiem funkcji w odniesieniu do listy argumentów A
, gdy spełnione są wszystkie następujące warunki:
- Każdy argument w
A
odpowiada parametrowi w deklaracji składowej funkcji zgodnie z opisem w §12.6.2.2, co najwyżej jeden argument odpowiada każdemu parametrowi, a każdy parametr, do którego żaden argument nie odpowiada, jest opcjonalnym parametrem. - Dla każdego argumentu w
A
tryb przekazywania parametrów argumentu jest identyczny z trybem przekazywania parametrów odpowiedniego parametru i- dla parametru wartości lub tablicy parametrów niejawna konwersja (§10.2) istnieje z wyrażenia argumentu do typu odpowiedniego parametru lub
- dla parametru referencyjnego lub wyjściowego istnieje konwersja tożsamości między typem wyrażenia argumentu (jeśli istnieje) a typem odpowiedniego parametru lub
- parametr wejściowy, kiedy odpowiedni argument ma modyfikator
in
, istnieje identyczna konwersja między typem wyrażenia argumentu (jeśli takie istnieje) a typem odpowiedniego parametru lub - Dla parametru wejściowego, gdy odpowiadający mu argument pomija modyfikator
in
, istnieje niejawna konwersja (§10.2) z wyrażenia argumentu na typ tego parametru.
W przypadku członka funkcji, który zawiera tablicę parametrów, jeśli członek funkcji jest stosowalny zgodnie z powyższymi regułami, mówi się, że jest stosowalny w swojej normalnej postaci. Jeśli członek funkcji zawierający tablicę parametrów nie ma zastosowania w postaci normalnej, ten członek funkcji może zamiast tego mieć zastosowanie w rozszerzonej postaci:
- Formularz rozwinięty jest konstruowany przez zastąpienie tablicy parametrów w deklaracji członka funkcji zerowym lub większą liczbą parametrów wartościowych o typie elementu tablicy parametrów w taki sposób, aby liczba argumentów na liście argumentów
A
odpowiadała łącznej liczbie parametrów. JeśliA
ma mniej argumentów niż liczba stałych parametrów w deklaracji składowej funkcji, rozszerzona forma składowej funkcji nie może być skonstruowana i w związku z tym nie ma zastosowania. - W przeciwnym razie rozszerzony formularz ma zastosowanie, jeśli dla każdego argumentu w
A
jest spełniony jeden z następujących warunków:- tryb przekazywania parametrów argumentu jest identyczny z trybem przekazywania parametrów odpowiedniego parametru i
- dla parametru stałej wartości lub parametru wartości utworzonego przez rozszerzenie, niejawna konwersja (§10.2) istnieje z wyrażenia argumentu do typu odpowiedniego parametru lub
- dla parametru by-reference typ wyrażenia argumentu jest identyczny z typem odpowiedniego parametru.
- tryb przekazywania parametrów argumentu jest wartością, a tryb przekazywania parametrów odpowiedniego parametru jest wejściowy, a niejawna konwersja (§10.2) istnieje z wyrażenia argumentu na typ odpowiedniego parametru
- tryb przekazywania parametrów argumentu jest identyczny z trybem przekazywania parametrów odpowiedniego parametru i
Gdy niejawna konwersja typu argumentu na typ parametru wejściowego jest dynamiczną niejawną konwersją (§10.2.10), wyniki są niezdefiniowane.
Przykład: przy uwzględnieniu następujących deklaracji i wywołań metody:
public static void M1(int p1) { ... } public static void M1(in int p1) { ... } public static void M2(in int p1) { ... } public static void Test() { int i = 10; uint ui = 34U; M1(in i); // M1(in int) is applicable M1(in ui); // no exact type match, so M1(in int) is not applicable M1(i); // M1(int) and M1(in int) are applicable M1(i + 5); // M1(int) and M1(in int) are applicable M1(100u); // no implicit conversion exists, so M1(int) is not applicable M2(in i); // M2(in int) is applicable M2(i); // M2(in int) is applicable M2(i + 5); // M2(in int) is applicable }
koniec przykładu
- Metoda statyczna ma zastosowanie tylko wtedy, gdy grupa metod wynika z simple_name lub member_access za pośrednictwem typu.
- Metoda instancyjna ma zastosowanie tylko wtedy, gdy grupa metod wynika z simple_name, member_access za pośrednictwem zmiennej lub wartości, lub base_access.
- Jeśli grupa metod wynika z simple_name, metoda instancji ma zastosowanie tylko wtedy, o ile
this
dostęp jest dozwolony §12.8.14.
- Jeśli grupa metod wynika z simple_name, metoda instancji ma zastosowanie tylko wtedy, o ile
- Gdy grupa metod wynika z member_access, która może być dostępna zarówno za pośrednictwem wystąpienia, jak i typu, zgodnie z opisem w §12.8.7.2, mają zastosowanie zarówno metody instancyjne, jak i metody statyczne.
- Metoda ogólna, której argumenty typu (jawnie określone lub wnioskowane) nie spełniają ich ograniczeń, nie ma zastosowania.
- W kontekście konwersji grupy metod musi istnieć konwersja identyczności (§10.2.2) lub niejawna konwersja referencyjna (§10.2.8) z typu zwracanego metody do typu zwracanego delegata. W przeciwnym razie metoda kandydacka nie ma zastosowania.
12.6.4.3 Lepszy element funkcji
Na potrzeby określenia lepszego członka funkcji, konstruowana jest uproszczona lista argumentów A
, zawierająca tylko wyrażenia argumentowe w kolejności, w jakiej pojawiają się na oryginalnej liście argumentów, z pominięciem wszystkich argumentów out
lub ref
.
Listy parametrów dla każdego z członków funkcji kandydatów są tworzone w następujący sposób:
- Forma rozszerzona jest używana, jeśli członek funkcji był stosowany tylko w formie rozszerzonej.
- Parametry opcjonalne bez odpowiednich argumentów są usuwane z listy parametrów
- Parametry referencyjne i wyjściowe są usuwane z listy parametrów
- Parametry są zmieniane tak, aby występowały w tej samej pozycji co odpowiedni argument na liście argumentów.
Biorąc pod uwagę listę argumentów
- dla każdego argumentu niejawna konwersja z
Eᵥ
naQᵥ
nie jest lepsza niż niejawna konwersja zEᵥ
naPᵥ
i - dla co najmniej jednego argumentu konwersja z
Eᵥ
naPᵥ
jest lepsza niż konwersja zEᵥ
naQᵥ
.
W przypadku, gdy sekwencje typów parametrów {P₁, P₂, ..., Pᵥ}
i {Q₁, Q₂, ..., Qᵥ}
są równoważne (tj. każda Pᵢ
ma konwersję bezpośrednią do odpowiadającego Qᵢ
), stosuje się następujące reguły rozstrzygania remisów, jedna po drugiej, aby wybrać lepszego członka funkcji.
- Jeśli
Mᵢ
jest metodą niegeneryjną, aMₑ
jest metodą ogólną,Mᵢ
jest lepsza niżMₑ
. - W przeciwnym razie, jeśli
Mᵢ
ma zastosowanie w postaci normalnej iMₑ
ma tablicę params i ma zastosowanie tylko w rozszerzonej formie,Mᵢ
jest lepszy niżMₑ
. - W przeciwnym razie, jeśli obie metody mają tablice params i mają zastosowanie tylko w ich rozszerzonych formularzach, a jeśli tablica parametrów
Mᵢ
ma mniej elementów niż tablica paramsMₑ
,Mᵢ
jest lepsza niżMₑ
. - W przeciwnym razie, jeśli
Mᵥ
ma bardziej szczegółowe typy parametrów niżMₓ
,Mᵥ
jest lepszy niżMₓ
. Niech{R1, R2, ..., Rn}
i{S1, S2, ..., Sn}
reprezentują niezainstantowane i nierozwinięte typy parametrówMᵥ
iMₓ
.Mᵥ
typy parametrów są bardziej szczegółowe niżMₓ
, jeśli dla każdego parametruRx
nie jest mniej szczegółowy niżSx
, a dla co najmniej jednego parametruRx
jest bardziej szczegółowy niżSx
:- Parametr typu jest mniej specyficzny niż parametr inny niż typ.
- Rekursywnie skonstruowany typ jest bardziej szczegółowy niż inny typ skonstruowany (z tą samą liczbą argumentów typu), jeśli co najmniej jeden argument typu jest bardziej szczegółowy i żaden argument typu nie jest mniej specyficzny niż odpowiadający mu argument typu w drugiej.
- Typ tablicy jest bardziej szczegółowy niż inny typ tablicy (o tej samej liczbie wymiarów), jeśli typ elementu pierwszego jest bardziej szczegółowy niż typ elementu drugiego.
- W przeciwnym razie, jeśli jeden element jest operatorem niepodniesionym, a drugi operatorem podniesionym, to lepszy jest operator niepodniesiony.
- Jeśli nie znaleziono ani jednego członka funkcji, który byłby lepszy, a wszystkie parametry
Mᵥ
mają odpowiedni argument, podczas gdy dla co najmniej jednego opcjonalnego parametru wMₓ
muszą być użyte argumenty domyślne, wtedyMᵥ
jest lepszy niżMₓ
. - Jeśli w przypadku co najmniej jednego parametru
Mᵥ
używa lepszego wyboru przekazywania parametrów (§12.6.4.4) niż odpowiedni parametr wMₓ
i żaden z parametrów wMₓ
używa lepszego wyboru przekazywania parametrów niżMᵥ
,Mᵥ
jest lepszy niżMₓ
. - W przeciwnym razie, żaden element funkcji nie jest lepszy.
12.6.4.4 Lepszy tryb przekazywania parametrów
Dozwolone jest posiadanie odpowiednich parametrów w dwóch przeciążonych metodach, które różnią się tylko trybem przekazywania parametrów, pod warunkiem że jeden z dwóch parametrów przekazuje wartość.
public static void M1(int p1) { ... }
public static void M1(in int p1) { ... }
Biorąc pod uwagę int i = 10;
, zgodnie z §12.6.4.2, wywołania M1(i)
i M1(i + 5)
powodują zastosowanie obu przeciążeń. W takich przypadkach metoda z trybem przekazywania parametrów jest lepszym wyborem trybu przekazywania parametrów.
Uwaga: nie ma takiego wyboru dla argumentów trybów przekazywania danych wejściowych, wyjściowych lub odwołań, ponieważ te argumenty są zgodne tylko z dokładnie tymi samymi trybami przekazywania parametrów. uwaga końcowa
12.6.4.5 Lepsza konwersja z wyrażenia
Biorąc pod uwagę niejawną konwersję C₁
, która konwertuje z wyrażenia E
na typ T₁
, oraz niejawną konwersję C₂
, która konwertuje z wyrażenia E
na typ T₂
, C₁
jest lepszą konwersją niż C₂
, jeśli spełniony jest jeden z następujących warunków:
-
E
dokładnie pasuje doT₁
iE
nie pasuje dokładnie doT₂
(§12.6.4.6) -
E
dokładnie pasuje albo do obu, albo do żadnego zT₁
iT₂
, aT₁
jest lepszym celem konwersji niżT₂
(§12.6.4.7) -
E
jest grupą metod (§12.2),T₁
jest zgodna (§20.4) z pojedynczą najlepszą metodą z grupy metod konwersjiC₁
, aT₂
nie jest zgodna z jedną najlepszą metodą z grupy metod konwersjiC₂
12.6.4.6 Dokładnie pasujące wyrażenie
Biorąc pod uwagę wyrażenie E
i typ T
, E
dokładnie pasuje doT
, jeśli zachodzi jeden z następujących warunków:
-
E
ma typS
, a konwersja identyczności istnieje zS
doT
-
E
jest funkcją anonimową,T
jest typem delegataD
lub typem drzewa wyrażeńExpression<D>
i spełnia jeden z następujących warunków:- Wywnioskowany typ zwracany
X
istnieje dlaE
w kontekście listy parametrówD
(§12.6.3.12), a konwersja identyczności istnieje zX
do zwracanego typuD
-
E
jestasync
lambda bez wartości zwracanej, aD
ma typ zwracany, który jest niegeneryczny«TaskType»
- Albo
E
nie jest asynchroniczny, aD
ma typ zwracanyY
, lubE
jest asynchroniczny, aD
ma typ zwracany«TaskType»<Y>
(§15.15.1), i spełniony jest jeden z następujących warunków:- Treść
E
jest wyrażeniem, które pasuje dokładnie doY
- Treść
E
to blok, w którym każda instrukcja return zwraca wyrażenie, które dokładnie odpowiadaY
- Treść
- Wywnioskowany typ zwracany
12.6.4.7 Lepszy cel konwersji
Biorąc pod uwagę dwa typy T₁
i T₂
, T₁
jest lepszym celem konwersji niż T₂
, jeśli zachodzi jeden z następujących warunków:
- Istnieje niejawna konwersja z
T₁
naT₂
i nie istnieje niejawna konwersja zT₂
naT₁
-
T₁
jest«TaskType»<S₁>
(§15.15.1),T₂
jest«TaskType»<S₂>
, aS₁
jest lepszym celem konwersji niżS₂
-
T₁
jest«TaskType»<S₁>
(§15.15.1),T₂
jest«TaskType»<S₂>
, aT₁
jest bardziej wyspecjalizowany niżT₂
-
T₁
jestS₁
lubS₁?
, gdzieS₁
jest typem całkowitoliczbowym ze znakiem, aT₂
jestS₂
lubS₂?
, gdzieS₂
jest bezznakowym typem całkowitoliczbowym. Specyficznie:-
S₁
jestsbyte
, aS₂
jestbyte
,ushort
,uint
lubulong
-
S₁
jestshort
iS₂
jestushort
,uint
lubulong
-
S₁
jestint
, aS₂
jestuint
lubulong
-
S₁
jestlong
, aS₂
jestulong
-
12.6.4.8 Przeciążanie w klasach ogólnych
Uwaga: Podczas gdy podpisy zadeklarowane są unikatowe (§8.6), istnieje możliwość, że podstawienie argumentów typu powoduje identyczne podpisy. W takiej sytuacji rozwiązanie przeciążenia wybierze najbardziej specyficzne (§12.6.4.3) oryginalnych podpisów (przed zastąpieniem argumentów typu), jeśli istnieje, i w przeciwnym razie zgłosi błąd. uwaga końcowa
Przykład: W poniższych przykładach przedstawiono prawidłowe i nieprawidłowe przeciążenia zgodnie z tą regułą:
public interface I1<T> { ... } public interface I2<T> { ... } public abstract class G1<U> { public abstract int F1(U u); // Overload resolution for G<int>.F1 public abstract int F1(int i); // will pick non-generic public abstract void F2(I1<U> a); // Valid overload public abstract void F2(I2<U> a); } abstract class G2<U,V> { public abstract void F3(U u, V v); // Valid, but overload resolution for public abstract void F3(V v, U u); // G2<int,int>.F3 will fail public abstract void F4(U u, I1<V> v); // Valid, but overload resolution for public abstract void F4(I1<V> v, U u); // G2<I1<int>,int>.F4 will fail public abstract void F5(U u1, I1<V> v2); // Valid overload public abstract void F5(V v1, U u2); public abstract void F6(ref U u); // Valid overload public abstract void F6(out V v); }
koniec przykładu
12.6.5 Sprawdzanie wywołania dynamicznego członka w czasie kompilacji
Mimo że rozpoznawanie przeciążenia operacji dynamicznie powiązanej odbywa się w czasie wykonywania, czasami w czasie kompilacji jest możliwe poznanie listy elementów członkowskich funkcji, z których zostanie wybrane przeciążenie:
- W przypadku wywołania delegacji (§12.8.10.4) lista jest pojedynczym członkiem funkcji z takim samym wykazem parametrów co delegate_type wywołania.
- W przypadku wywołania metody (§12.8.10.2) dla typu lub wartości, której typ statyczny nie jest dynamiczny, zestaw metod dostępnych w grupie metod jest znany w czasie kompilacji.
- W przypadku wyrażenia tworzenia obiektu (§12.8.17.2) zestaw dostępnych konstruktorów w typie jest znany w czasie kompilacji.
- W przypadku dostępu indeksatora (§12.8.12.3) zestaw dostępnych indeksatorów w odbiorniku jest znany w czasie kompilacji.
W takich przypadkach na każdej funkcji w znanym zestawie funkcji przeprowadzane jest ograniczone sprawdzenie w czasie kompilacji, aby sprawdzić, czy można na pewno stwierdzić, że nigdy nie będzie wywoływana w czasie wykonywania. Dla każdego elementu członkowskiego funkcji F
jest konstruowana zmodyfikowana lista parametrów i argumentów:
- Po pierwsze, jeśli
F
jest metodą ogólną i podano argumenty typu, są one zastępowane parametrami typu na liście parametrów. Jeśli jednak nie podano argumentów typu, takie podstawienie nie następuje. - Następnie każdy parametr, którego typ jest otwarty (tj. zawiera parametr typu; zobacz §8.4.3), jest pominięty, wraz z odpowiednimi parametrami.
Aby F
przeszedł kontrolę, wszystkie następujące warunki muszą być spełnione:
- Zmodyfikowana lista parametrów dla
F
ma zastosowanie do zmodyfikowanej listy argumentów w zakresie §12.6.4.2. - Wszystkie skonstruowane typy na liście zmodyfikowanych parametrów spełniają swoje ograniczenia (§8.4.5).
- Jeśli parametry typu
F
zostały zastąpione w powyższym kroku, ich ograniczenia są spełnione. - Jeśli
F
jest metodą statyczną, grupa metod nie powinna wynikać z member_access, którego odbiornik jest znany w czasie kompilacji jako zmienna lub wartość. - Jeśli
F
jest metodą wystąpienia, grupa metod nie wynika z member_access, którego odbiornik jest znany w czasie kompilacji jako typ.
Jeśli żaden kandydat nie przejdzie tego testu, wystąpi błąd czasu kompilacji.
Wywołanie składowej funkcji 12.6.6
12.6.6.1 Ogólne
W tym podpunkcie opisano proces, który odbywa się w czasie wykonywania w celu wywołania określonego członka funkcji. Zakłada się, że proces czasu powiązania określił już konkretny element członkowski do wywołania, być może stosując rozpoznawanie przeciążenia do zestawu elementów członkowskich funkcji kandydata.
Dla opisu procesu wywoływania, członkowie funkcji są podzieleni na dwie kategorie:
- Członkowie funkcji statycznych. Są to metody statyczne, metody dostępu do właściwości statycznych i operatory zdefiniowane przez użytkownika. Statyczne elementy członkowskie funkcji są zawsze niewirtualne.
- Członkowie funkcji instancji Są to metody wystąpień, konstruktory wystąpień, metody dostępu do właściwości wystąpienia i metody dostępu indeksatora. Członkowie funkcji instancji są niewirtualni lub wirtualni i są zawsze wywoływani na danej instancji. Instancja jest obliczana za pomocą wyrażenia instancji i staje się dostępna w elemencie członkowskim funkcji jako
this
(§12.8.14). W przypadku konstruktora wystąpienia wyrażenie wystąpienia jest traktowane jako nowo przydzielony obiekt.
Przetwarzanie w czasie wykonywania wywołania składnika funkcji składa się z następujących kroków, w których M
jest składnikiem funkcji, a jeśli M
jest członkiem instancji, E
jest wyrażeniem instancji.
Jeśli
M
jest elementem członkowskim funkcji statycznej:- Lista argumentów jest oceniana zgodnie z opisem w §12.6.2.
-
M
jest wywoływany.
W przeciwnym razie, jeśli typ
E
jest typem wartościV
, aM
jest zadeklarowany lub zastąpiony wV
:-
E
jest oceniana. Jeśli ta ocena spowoduje wyjątek, nie zostaną wykonane żadne dalsze kroki. W przypadku konstruktora wystąpienia ten proces składa się z przydzielania pamięci (zazwyczaj ze stosu wykonywania) dla nowego obiektu. W tym przypadkuE
jest klasyfikowana jako zmienna. - Jeśli
E
nie jest klasyfikowana jako zmienna lub jeśliV
nie jest typem struktury readonly (§16.2.2), aE
jest jednym z:- parametr wejściowy (§15.6.2.3.2) lub
- pole
readonly
(§15.5.3) lub - zmienna referencyjna
readonly
lub wartość zwracana (§9.7),
następnie jest tworzona tymczasowa zmienna lokalna typu
E
, a wartośćE
jest przypisywana do tej zmiennej.E
jest następnie ponownie sklasyfikowany jako odwołanie do tej tymczasowej zmiennej lokalnej. Zmienna tymczasowa jest dostępna jakothis
wM
, ale nie w żaden inny sposób. W związku z tym tylko wtedy, gdy można napisaćE
, obiekt wywołujący może obserwować zmiany, któreM
wprowadza dothis
.- Lista argumentów jest oceniana zgodnie z opisem w §12.6.2.
-
M
jest wywoływany. Zmienna, do której odwołuje sięE
, staje się zmienną przywoływaną przezthis
.
-
Inaczej:
-
E
jest oceniana. Jeśli ta ocena spowoduje wyjątek, nie zostaną wykonane żadne dalsze kroki. - Lista argumentów jest oceniana zgodnie z opisem w §12.6.2.
- Jeśli typ
E
jest value_type, konwersja boksu (§10.2.9) jest wykonywana w celu przekonwertowaniaE
na class_type, aE
jest uważana za class_type w poniższych krokach. Jeśli value_type jest enum_type, to class_type jestSystem.Enum;
, w przeciwnym razie jestSystem.ValueType
. - Wartość
E
jest sprawdzana jako prawidłowa. Jeśli wartośćE
ma wartość null, zostanie zgłoszonySystem.NullReferenceException
i nie zostaną wykonane żadne dalsze kroki. - Określa się implementację członka funkcji do wywołania.
- Jeśli typ określony w czasie powiązania
E
jest interfejsem, element funkcji, który należy wywołać, to implementacjaM
zapewniona przez typ czasu wykonywania instancji, do której odwołuje sięE
. Ten członek funkcji jest ustalany poprzez zastosowanie reguł mapowania interfejsu (§18.6.5) w celu określenia implementacjiM
dostarczanego przez typ czasu wykonywania instancji, do której odwołuje sięE
. - W przeciwnym razie, jeśli
M
jest elementem członkowskim funkcji wirtualnej, elementem członkowskim funkcji do wywołania jest implementacjaM
zapewniana przez typ czasu wykonywania wystąpienia, do którego odwołuje sięE
. Ten członek funkcji jest określany poprzez zastosowanie reguł określania najbardziej pochodnej implementacji (§15.6.4)M
w odniesieniu do typu czasu wykonywania instancji, na którą wskazujeE
. - W przeciwnym razie
M
jest elementem członkowskim funkcji niewirtualnej, a wywoływaną funkcją jest samM
.
- Jeśli typ określony w czasie powiązania
- Wywoływana jest członkowska implementacja funkcji określona w powyższym kroku. Obiekt, do którego odwołuje się
E
, staje się obiektem odwołującym się do tego.
-
Wynikiem wywołania konstruktora wystąpienia (§12.8.17.2) jest tworzona wartość. Wynikiem wywołania dowolnego innego składnika funkcji jest wartość, o ile istnieje, zwrócona (§13.10.5) z jej ciała.
12.6.6.2 Wywołania na opakowanych wystąpieniach
Składowa funkcji zaimplementowana w value_type może być wywołana przez opakowane wystąpienie tej value_type w następujących sytuacjach:
- Gdy element członkowski funkcji jest przesłonięciem metody dziedziczonej z typu class_type i jest wywoływany za pomocą wyrażenia wystąpienia tego class_type.
Uwaga: class_type zawsze będzie jednym z
System.Object
,System.ValueType
lubSystem.Enum
. uwaga końcowa - Gdy element członkowski funkcji jest implementacją elementu członkowskiego funkcji interfejsu i jest wywoływany przez wyrażenie instancji interface_type.
- Gdy element członkowski funkcji jest wywoływany za pośrednictwem delegata.
W takich sytuacjach opakowana instancja jest uznawana za zawierającą zmienną value_type, a ta zmienna staje się zmienną, do której odnosi się to przy wywołaniu członka funkcji.
Uwaga: w szczególności oznacza to, że gdy element funkcji jest wywoływany na opakowanym wystąpieniu, możliwe jest zmodyfikowanie wartości zawartej w opakowanym wystąpieniu. uwaga końcowa
12.7 Dekonstrukcja
Dekonstrukcja to proces polegający na tym, że wyrażenie przekształca się w krotkę poszczególnych wyrażeń. Dekonstrukcja jest używana, gdy element docelowy prostego przypisania jest wyrażeniem krotki, aby uzyskać wartości do przypisania do każdego z elementów tej krotki.
Wyrażenie E
jest rozłożone do wyrażenia krotkowego z n
elementami w następujący sposób:
- Jeśli
E
jest wyrażeniem krotki zn
elementami, wynikiem dekonstrukcji jest samo wyrażenieE
. - W przeciwnym razie, jeśli
E
ma typ krotki(T1, ..., Tn)
z elementamin
,E
jest obliczana w zmiennej tymczasowej__v
, a wynikiem dekonstrukcji jest wyrażenie(__v.Item1, ..., __v.Itemn)
. - W przeciwnym razie, jeśli wyrażenie
E.Deconstruct(out var __v1, ..., out var __vn)
zostanie w czasie kompilacji rozpoznane jako unikatowa instancja lub metoda rozszerzenia, wyrażenie to zostaje ocenione, a wynikiem dekonstrukcji jest wyrażenie(__v1, ..., __vn)
. Taka metoda jest nazywana dekonstruktorem. - W przeciwnym razie nie można zdekonstruować
E
.
W tym miejscu __v
i __v1, ..., __vn
odnoszą się do innych niewidocznych i niedostępnych zmiennych tymczasowych.
Uwaga: nie można zdekonstruować wyrażenia typu
dynamic
. uwaga końcowa
12.8 Wyrażenia podstawowe
12.8.1 Ogólne
Wyrażenia podstawowe obejmują najprostsze formy wyrażeń.
primary_expression
: primary_no_array_creation_expression
| array_creation_expression
;
primary_no_array_creation_expression
: literal
| interpolated_string_expression
| simple_name
| parenthesized_expression
| tuple_expression
| member_access
| null_conditional_member_access
| invocation_expression
| element_access
| null_conditional_element_access
| this_access
| base_access
| post_increment_expression
| post_decrement_expression
| null_forgiving_expression
| object_creation_expression
| delegate_creation_expression
| anonymous_object_creation_expression
| typeof_expression
| sizeof_expression
| checked_expression
| unchecked_expression
| default_value_expression
| nameof_expression
| anonymous_method_expression
| pointer_member_access // unsafe code support
| pointer_element_access // unsafe code support
| stackalloc_expression
;
Uwaga: te reguły gramatyczne nie są przystosowane do ANTLR, ponieważ są częścią zestawu wzajemnie leworęcznych reguł cyklicznych (
primary_expression
,primary_no_array_creation_expression
,member_access
,invocation_expression
,element_access
,post_increment_expression
,post_decrement_expression
,null_forgiving_expression
,pointer_member_access
ipointer_element_access
), które ANTLR nie obsługuje. Standardowe techniki mogą służyć do przekształcania gramatyki w celu usunięcia wzajemnej rekursji po lewej stronie. Nie zostało to zrobione, ponieważ nie wszystkie strategie analizy wymagają tego (np. analizator LALR nie) i w ten sposób zaciemnia strukturę i opis. uwaga końcowa
pointer_member_access (§23.6.3) i pointer_element_access (§23.6.4) są dostępne tylko w niebezpiecznym kodzie (§23).
Wyrażenia podstawowe są dzielone między array_creation_expressions i primary_no_array_creation_expressions. Traktowanie array_creation_expression w ten sposób, a nie wymienianie go wraz z innymi prostymi formami wyrażeń, pozwala gramatyce zapobiegać potencjalnie mylącemu kodowi, takim jak
object o = new int[3][1];
które w przeciwnym razie byłyby interpretowane jako
object o = (new int[3])[1];
12.8.2 Literały
wyrażenie podstawowe, które składa się z literału (§6.4.5), jest klasyfikowane jako wartość.
12.8.3 Wyrażenia ciągów interpolowanych
interpolated_string_expression składa się z $
, $@
lub @$
, po których następuje tekst oznaczony przez "
znaków. W cytowanym tekście jest zero lub więcej interpolacji oddzielonych znakami {
i }
, z których każda zawiera wyrażenie i opcjonalne parametry formatowania.
Wyrażenia ciągów interpolowanych mają dwie formy: regularną (interpolated_regular_string_expression) i dosłowną (interpolated_verbatim_string_expression), które są leksykalicznie podobne do, ale różnią się semantycznie od dwóch form literałów ciągów (§6.4.5.6).
interpolated_string_expression
: interpolated_regular_string_expression
| interpolated_verbatim_string_expression
;
// interpolated regular string expressions
interpolated_regular_string_expression
: Interpolated_Regular_String_Start Interpolated_Regular_String_Mid?
('{' regular_interpolation '}' Interpolated_Regular_String_Mid?)*
Interpolated_Regular_String_End
;
regular_interpolation
: expression (',' interpolation_minimum_width)?
Regular_Interpolation_Format?
;
interpolation_minimum_width
: constant_expression
;
Interpolated_Regular_String_Start
: '$"'
;
// the following three lexical rules are context sensitive, see details below
Interpolated_Regular_String_Mid
: Interpolated_Regular_String_Element+
;
Regular_Interpolation_Format
: ':' Interpolated_Regular_String_Element+
;
Interpolated_Regular_String_End
: '"'
;
fragment Interpolated_Regular_String_Element
: Interpolated_Regular_String_Character
| Simple_Escape_Sequence
| Hexadecimal_Escape_Sequence
| Unicode_Escape_Sequence
| Open_Brace_Escape_Sequence
| Close_Brace_Escape_Sequence
;
fragment Interpolated_Regular_String_Character
// Any character except " (U+0022), \\ (U+005C),
// { (U+007B), } (U+007D), and New_Line_Character.
: ~["\\{}\u000D\u000A\u0085\u2028\u2029]
;
// interpolated verbatim string expressions
interpolated_verbatim_string_expression
: Interpolated_Verbatim_String_Start Interpolated_Verbatim_String_Mid?
('{' verbatim_interpolation '}' Interpolated_Verbatim_String_Mid?)*
Interpolated_Verbatim_String_End
;
verbatim_interpolation
: expression (',' interpolation_minimum_width)?
Verbatim_Interpolation_Format?
;
Interpolated_Verbatim_String_Start
: '$@"'
| '@$"'
;
// the following three lexical rules are context sensitive, see details below
Interpolated_Verbatim_String_Mid
: Interpolated_Verbatim_String_Element+
;
Verbatim_Interpolation_Format
: ':' Interpolated_Verbatim_String_Element+
;
Interpolated_Verbatim_String_End
: '"'
;
fragment Interpolated_Verbatim_String_Element
: Interpolated_Verbatim_String_Character
| Quote_Escape_Sequence
| Open_Brace_Escape_Sequence
| Close_Brace_Escape_Sequence
;
fragment Interpolated_Verbatim_String_Character
: ~["{}] // Any character except " (U+0022), { (U+007B) and } (U+007D)
;
// lexical fragments used by both regular and verbatim interpolated strings
fragment Open_Brace_Escape_Sequence
: '{{'
;
fragment Close_Brace_Escape_Sequence
: '}}'
;
Sześć reguł leksykalnych zdefiniowanych powyżej jest kontekstowych w następujący sposób:
Reguła | wymagania kontekstowe |
---|---|
Interpolated_Regular_String_Mid | Rozpoznawane tylko po Interpolated_Regular_String_Start, pomiędzy kolejnymi interpolacjami, a przed odpowiednim Interpolated_Regular_String_End. |
Regular_Interpolation_Format | Uznawane tylko w regular_interpolation i gdy dwukropek na początku (:) nie jest zagnieżdżony w żadnym rodzaju nawiasu (okrągłych/klamrowych/kwadratowych). |
Interpolated_Regular_String_End | Rozpoznawane tylko po Interpolated_Regular_String_Start i tylko wtedy, gdy wszystkie interweniujące tokeny albo są tokenami Interpolated_Regular_String_Mid, albo mogą być częścią regular_interpolation, w tym tokenów dla dowolnych interpolated_regular_string_expressionzawartych w takich interpolacjach. |
Interpolated_Verbatim_String_MidVerbatim_Interpolation_FormatInterpolated_Verbatim_String_End | Uznanie tych trzech reguł jest zgodne z odpowiednimi regułami powyżej, z których każda wymieniona regularna reguła gramatyki zastąpiona odpowiednią dosłowną. |
Uwaga: powyższe reguły są kontekstowe, ponieważ ich definicje nakładają się na inne tokeny w języku. uwaga końcowa
Uwaga: Powyższa gramatyka nie jest gotowa do użycia z ANTLR ze względu na kontekstowe reguły leksykalne. Podobnie jak w przypadku innych generatorów leksykalnych, ANTLR obsługuje reguły leksykalne wrażliwe na kontekst, na przykład przy użyciu trybów leksykalnych , ale jest to szczegół implementacji, i dlatego nie jest częścią tej specyfikacji. uwaga końcowa
interpolated_string_expression jest klasyfikowana jako wartość. Jeśli jest on natychmiast konwertowany na System.IFormattable
lub System.FormattableString
z niejawną konwersją ciągów interpolowanych (§10.2.5), wyrażenie ciągu interpolowanego ma ten typ. W przeciwnym razie ma typ string
.
Uwaga: Różnice między możliwymi typami wyrażenia interpolated_string_expression można określić na podstawie dokumentacji
System.String
(§C.2) iSystem.FormattableString
(§C.3). uwaga końcowa
Znaczenie interpolacji, zarówno regular_interpolation, jak i verbatim_interpolation, ma formatować wartość wyrażenia jako string
zgodnie z formatem określonym przez Regular_Interpolation_Format lub Verbatim_Interpolation_Formatlub zgodnie z domyślnym formatem dla typu wyrażenia . Sformatowany ciąg jest następnie, jeśli istnieje, modyfikowany przez interpolation_minimum_width, aby utworzyć ostateczną string
do interpolowania w interpolated_string_expression.
pl-PL: Uwaga: sposób określania domyślnego formatu typu jest szczegółowo opisany w dokumentacji dla
System.String
(§C.2) iSystem.FormattableString
(§C.3). Opisy formatów standardowych, które są identyczne dla Regular_Interpolation_Format i Verbatim_Interpolation_Format, można znaleźć w dokumentacjiSystem.IFormattable
(§C.4) i w innych typach w bibliotece standardowej (§C). uwaga końcowa
W interpolation_minimum_widthconstant_expression powinna mieć niejawną konwersję do int
. Niech szerokość pola będzie wartością bezwzględną tego wyrażenia stałej, a wyrównanie niech będzie znakiem (dodatnim lub ujemnym) wartości tego wyrażenia stałej:
- Jeśli wartość szerokości pola jest mniejsza lub równa długości sformatowanego ciągu, sformatowany ciąg nie jest modyfikowany.
- W przeciwnym razie sformatowany ciąg jest uzupełniony spacjami, tak aby jego długość była równa szerokości pola.
- Jeśli wyrównanie jest dodatnie, sformatowany ciąg jest wyrównany do prawej przez dodanie wypełnienia na początku.
- W przeciwnym razie jest wyrównany do lewej poprzez dodanie wypełnienia.
Ogólne znaczenie interpolated_string_expression, w tym powyższe formatowanie i wypełnienie interpolacji, jest definiowane przez konwersję wyrażenia na wywołanie metody: jeśli typ wyrażenia to System.IFormattable
lub System.FormattableString
, metodą jest System.Runtime.CompilerServices.FormattableStringFactory.Create
(§C.3), która zwraca wartość typu System.FormattableString
; w przeciwnym razie typem jest string
, a metodą jest string.Format
(§C.2), która zwraca wartość typu string
.
W obu przypadkach lista argumentów wywołania składa się z literału ciągu formatu ze specyfikacjami formatu dla każdej interpolacji oraz z argumentów dla każdego wyrażenia odpowiadającego specyfikacjom formatu.
Literał ciągu formatu jest konstruowany w następujący sposób, gdzie N
jest liczbą interpolacji w wyrażeniu ciągu interpolowanego . Literał ciągu formatu składa się z następującej kolejności:
- Znaki Interpolated_Regular_String_Start lub Interpolated_Verbatim_String_Start
- Znaki Interpolated_Regular_String_Mid lub Interpolated_Verbatim_String_Mid, o ile istnieją
- Następnie, jeśli
N ≥ 1
dla każdej liczbyI
z0
doN-1
:- Specyfikacja zastępnika:
- Lewy nawias klamrowy (
{
) znak - Reprezentacja dziesiętna liczby
I
- Następnie, jeśli odpowiadająca regular_interpolation lub verbatim_interpolation ma interpolation_minimum_width, pojawia się przecinek (
,
), po którym następuje reprezentacja dziesiętna wartości constant_expression - Znaki Regular_Interpolation_Format lub Verbatim_Interpolation_Format, jeśli istnieją, odpowiednich regular_interpolation lub verbatim_interpolation
- Znak prawego nawiasu klamrowego (
}
)
- Lewy nawias klamrowy (
- Znaki Interpolated_Regular_String_Mid lub Interpolated_Verbatim_String_Mid bezpośrednio po odpowiedniej interpolacji, jeśli istnieją
- Specyfikacja zastępnika:
- Na koniec znaki Interpolated_Regular_String_End lub Interpolated_Verbatim_String_End.
Kolejne argumenty to wyrażenia z interpolacji, w kolejności, jeśli istnieją.
Gdy interpolated_string_expression zawiera wiele interpolacji, wyrażenia w tych interpolacjach są oceniane w kolejności tekstowej od lewej do prawej.
Przykład:
W tym przykładzie użyto następujących funkcji specyfikacji formatu:
- specyfikacja formatu
X
, która formatuje liczby całkowite jako wielkie cyfry szesnastkowe, - domyślnym formatem wartości
string
jest sama wartość, - wartości wyrównania dodatniego, które wyrównują do prawej w ramach określonej minimalnej szerokości pola
- ujemne wartości wyrównania, które wyrównują do lewej w określonej minimalnej szerokości pola,
- zdefiniowane stałe dla interpolation_minimum_widthi
-
{{
i}}
są formatowane odpowiednio jako{
i}
.
Dane:
string text = "red";
int number = 14;
const int width = -4;
Wtedy:
wyrażenie ciągu interpolowanego |
równoważne znaczenie jako string |
wartości |
---|---|---|
$"{text}" |
string.Format("{0}", text) |
"red" |
$"{{text}}" |
string.Format("{{text}}) |
"{text}" |
$"{ text , 4 }" |
string.Format("{0,4}", text) |
" red" |
$"{ text , width }" |
string.Format("{0,-4}", text) |
"red " |
$"{number:X}" |
string.Format("{0:X}", number) |
"E" |
$"{text + '?'} {number % 3}" |
string.Format("{0} {1}", text + '?', number % 3) |
"red? 2" |
$"{text + $"[{number}]"}" |
string.Format("{0}", text + string.Format("[{0}]", number)) |
"red[14]" |
$"{(number==0?"Zero":"Non-zero")}" |
string.Format("{0}", (number==0?"Zero":"Non-zero")) |
"Non-zero" |
koniec przykładu
12.8.4 Proste nazwy
simple_name składa się z identyfikatora, po którym opcjonalnie następuje lista argumentów typu:
simple_name
: identifier type_argument_list?
;
simple_name jest I
formularza lub I<A₁, ..., Aₑ>
formularza, gdzie I
jest pojedynczym identyfikatorem, a I<A₁, ..., Aₑ>
jest opcjonalnym type_argument_list. Jeśli nie określono type_argument_list, należy przyjąć, że e
ma wartość zero.
simple_name jest oceniany i klasyfikowany w następujący sposób:
- Jeśli
e
ma wartość zero, a simple_name pojawia się w przestrzeni deklaracji zmiennej lokalnej (§7.3), która bezpośrednio zawiera zmienną lokalną, parametr lub stałą o nazwieI
, simple_name odnosi się do tej zmiennej lokalnej, parametru lub stałej i jest klasyfikowana jako zmienna lub wartość. - Jeśli
e
ma wartość zero, a simple_name pojawia się w deklaracji metody ogólnej, ale poza atrybutami method_declaration, a jeśli ta deklaracja zawiera parametr typu o nazwieI
, simple_name odnosi się do tego parametru typu. - W przeciwnym razie dla każdego typu wystąpienia
T
(§15.3.2), począwszy od typu wystąpienia deklaracji bezpośrednio otaczającego typu i kontynuując z typem wystąpienia każdej otaczającej klasy lub deklaracji struktury (jeśli taka istnieje):- Jeśli
e
ma wartość zero, a deklaracjaT
zawiera parametr typu o nazwieI
, simple_name odnosi się do tego parametru typu. - W przeciwnym razie, jeśli wyszukiwanie członka (§12.5)
I
wT
z argumentami typue
generuje dopasowanie:- Jeśli
T
jest typem wystąpienia natychmiast otaczającej klasy lub typu struktury, a wyszukiwanie identyfikuje jedną lub więcej metod, wynikiem jest grupa metod ze skojarzonym wyrażeniem wystąpieniathis
. Jeśli określono listę argumentów typu, jest ona używana w wywoływaniu metody ogólnej (§12.8.10.2). - W przeciwnym razie, jeśli
T
jest typem wystąpienia bezpośrednio otaczającej klasy lub typu struktury, gdy odnośnik identyfikuje element członkowski wystąpienia i odwołanie występuje w bloku konstruktora wystąpienia, metody wystąpienia lub metody dostępu wystąpienia (§12.2.1), wynik jest taki sam jak dostęp do elementu członkowskiego (§12.8.7) w formularzuthis.I
. Może się to zdarzyć tylko wtedy, gdye
wynosi zero. - W przeciwnym razie wynik jest taki sam jak dostęp do elementu członkowskiego (§12.8.7) w formie
T.I
lubT.I<A₁, ..., Aₑ>
.
- Jeśli
- Jeśli
- W przeciwnym razie dla każdej przestrzeni nazw
N
, począwszy od przestrzeni nazw, w której występuje simple_name, kontynuując każdą otaczającą przestrzeń nazw (jeśli istnieje), a kończąc na globalnej przestrzeni nazw, następujące kroki są oceniane do momentu zlokalizowania jednostki:- Jeśli
e
ma wartość zero, aI
jest nazwą przestrzeni nazw wN
, wówczas:- Jeśli lokalizacja, w której występuje simple_name, jest ujęta przez deklarację przestrzeni nazw dla
N
, a deklaracja przestrzeni nazw zawiera extern_alias_directive lub using_alias_directive, która kojarzy nazwęI
z przestrzenią nazw lub typem, simple_name jest niejednoznaczny i występuje błąd czasu kompilacji. - W przeciwnym razie simple_name odnosi się do przestrzeni nazw o nazwie
I
wN
.
- Jeśli lokalizacja, w której występuje simple_name, jest ujęta przez deklarację przestrzeni nazw dla
- W przeciwnym razie jeśli
N
zawiera dostępny typ o nazwieI
i parametrach typue
, wówczas:- Jeśli
e
ma wartość zero, a lokalizacja, w której występuje simple_name, jest zawarta w deklaracji przestrzeni nazw dlaN
, a deklaracja przestrzeni nazw zawiera extern_alias_directive lub using_alias_directive, które kojarzą nazwęI
z przestrzenią nazw lub typem, wtedy simple_name jest niejednoznaczna i powoduje błąd czasu kompilacji. - W przeciwnym razie namespace_or_type_name odnosi się do typu skonstruowanego przy użyciu podanych argumentów typu.
- Jeśli
- W przeciwnym razie, jeśli lokalizacja, w której występuje simple_name, jest ujęta przez deklarację przestrzeni nazw dla
N
:- Jeśli
e
ma wartość zero, a deklaracja przestrzeni nazw zawiera extern_alias_directive lub using_alias_directive, które kojarzą nazwęI
z zaimportowaną przestrzenią nazw lub typem, to simple_name odnosi się do tej przestrzeni nazw lub typu. - W przeciwnym razie, jeśli przestrzenie nazw zaimportowane poprzez using_namespace_directives w deklaracji przestrzeni nazw zawierają dokładnie jeden typ o nazwie
I
i ze
parametrami typu, to simple_name odnosi się do tego typu, skonstruowanego przy użyciu podanych argumentów dotyczących typów. - W przeciwnym razie, jeśli przestrzenie nazw zaimportowane przez using_namespace_directivedeklaracji przestrzeni nazw zawierają więcej niż jeden typ o nazwie
I
i parametry typue
, simple_name jest niejednoznaczny i wystąpi błąd czasu kompilacji.
- Jeśli
Uwaga: cały krok jest dokładnie równoległy do odpowiedniego kroku przetwarzania namespace_or_type_name (§7.8). uwaga końcowa
- Jeśli
- W przeciwnym razie, jeśli
ma wartość zero, a jest identyfikatorem , simple_name jest prostym odrzucania, który jest formą wyrażenia deklaracji (§12.17 ). - W przeciwnym razie simple_name jest niezdefiniowany i występuje błąd czasu kompilacji.
12.8.5 Wyrażenia w nawiasach
parenthesized_expression składa się z wyrażenia ujętego w nawiasy.
parenthesized_expression
: '(' expression ')'
;
parenthesized_expression jest obliczana przez ocenę wyrażenia w nawiasach. Jeśli wyrażenie w nawiasach oznacza przestrzeń nazw lub typ, wystąpi błąd czasu kompilacji. W przeciwnym razie wynikiem parenthesized_expression jest ocena zawartego wyrażenia .
12.8.6 Wyrażenia krotki
tuple_expression reprezentuje krotkę i składa się z co najmniej dwóch rozdzielonych przecinkami i opcjonalnie nazwanych wyrażeń ujętych w nawiasy. deconstruction_expression to skrócona składnia krotki zawierającej niejawnie wpisane wyrażenia deklaracji.
tuple_expression
: '(' tuple_element (',' tuple_element)+ ')'
| deconstruction_expression
;
tuple_element
: (identifier ':')? expression
;
deconstruction_expression
: 'var' deconstruction_tuple
;
deconstruction_tuple
: '(' deconstruction_element (',' deconstruction_element)+ ')'
;
deconstruction_element
: deconstruction_tuple
| identifier
;
tuple_expression jest klasyfikowana jako krotka.
deconstruction_expressionvar (e1, ..., en)
jest skrótem od tuple_expression(var e1, ..., var en)
i działa na tej samej zasadzie. Ma to zastosowanie rekursywnie do wszystkich zagnieżdżonych deconstruction_tuples w deconstruction_expression. Każdy identyfikator zagnieżdżony w deconstruction_expression w ten sposób wprowadza wyrażenie deklaracji (§12.17). W wyniku tego deconstruction_expression może wystąpić tylko po lewej stronie prostego przypisania.
Wyrażenia krotki mają typ wtedy i tylko wtedy, gdy każde z jego wyrażenie elementu Ei
ma typ Ti
. Typ musi być typem krotki o tej samej liczbie argumentów co wyrażenie krotki, gdzie każdy element jest podawany następująco:
- Jeśli element krotki w odpowiedniej pozycji ma nazwę
Ni
, to element typu tej krotki powinien byćTi Ni
. - W przeciwnym razie, jeśli
Ei
ma formęNi
lubE.Ni
lubE?.Ni
, to element typu krotki będzieTi Ni
, , chyba że dotyczy któregokolwiek z następujących:- Inny element wyrażenia krotki ma nazwę
Ni
, lub - Inny element krotki bez nazwy ma wyrażenie elementu krotki w formie
Ni
,E.Ni
lubE?.Ni
. -
Ni
ma postaćItemX
, gdzieX
jest sekwencją cyfr dziesiętnych, które nie są inicjowane przez0
i które mogą reprezentować pozycję elementu krotki, aX
nie reprezentuje pozycji elementu.
- Inny element wyrażenia krotki ma nazwę
- W przeciwnym razie element typu krotki to będzie
Ti
.
Wyrażenie krotki jest oceniane poprzez obliczenie każdego z wyrażeń elementu w kolejności od lewej do prawej.
Wartość krotki można uzyskać z wyrażenia krotki, konwertując ją na typ krotki (§10.2.13), przeklasyfikowując ją na wartość (§12.2.2) lub używając jej jako celu przypisania dekonstrukcji (§12.21.2).
Przykład:
(int i, string) t1 = (i: 1, "One"); (long l, string) t2 = (l: 2, null); var t3 = (i: 3, "Three"); // (int i, string) var t4 = (i: 4, null); // Error: no type
W tym przykładzie wszystkie cztery wyrażenia krotek są prawidłowe. Pierwsze dwa,
t1
it2
, nie używają typu wyrażenia krotki, ale zamiast tego stosują niejawną konwersję krotki. W przypadkut2
niejawna konwersja krotki opiera się na niejawnych konwersjach z2
nalong
i znull
dostring
. Trzecie wyrażenie krotki ma typ(int i, string)
, dlatego można je ponownie sklasyfikować jako wartość tego typu. Z drugiej strony deklaracjat4
jest błędem: Wyrażenie krotki nie ma typu, ponieważ jego drugi element nie ma typu.if ((x, y).Equals((1, 2))) { ... };
W tym przykładzie pokazano, że krotki mogą czasami prowadzić do wielu warstw nawiasów, zwłaszcza gdy wyrażenie krotki jest jedynym argumentem wywołania metody.
koniec przykładu
12.8.7 Dostęp do składowych
12.8.7.1 Ogólne
member_access składa się z primary_expression, predefined_typelub qualified_alias_member, po których następuje token ".
", a dalej identyfikator , opcjonalnie zakończony przez type_argument_list.
member_access
: primary_expression '.' identifier type_argument_list?
| predefined_type '.' identifier type_argument_list?
| qualified_alias_member '.' identifier type_argument_list?
;
predefined_type
: 'bool' | 'byte' | 'char' | 'decimal' | 'double' | 'float' | 'int'
| 'long' | 'object' | 'sbyte' | 'short' | 'string' | 'uint' | 'ulong'
| 'ushort'
;
Produkcja qualified_alias_member jest zdefiniowana w §14.8.
member_access ma postać E.I
lub E.I<A₁, ..., Aₑ>
, w przypadku gdy E
jest wyrażeniem podstawowym, typem predefiniowanym lub zakwalifikowanym członkiem aliasu,I
jest pojedynczym identyfikatorem, a <A₁, ..., Aₑ>
jest opcjonalną listą argumentów typu. Jeśli nie określono type_argument_list, należy przyjąć, że e
ma wartość zero.
member_access z primary_expression typu dynamic
jest dynamicznie powiązana (§12.3.3). W tym przypadku kompilator klasyfikuje dostęp do elementu jako dostęp do właściwości typu dynamic
. Poniższe reguły do określenia znaczenia member_access są stosowane w czasie wykonywania, używając typu wykorzystywanego w czasie wykonania zamiast typu wykorzystywanego w czasie kompilacji primary_expression. Jeśli ta klasyfikacja w czasie wykonywania prowadzi do grupy metod, to dostęp do składowych powinien być primary_expressioninvocation_expression.
member_access ocenia się i klasyfikuje w następujący sposób:
- Jeśli
e
ma wartość zero, aE
jest przestrzenią nazw, aE
zawiera zagnieżdżoną przestrzeń nazw o nazwieI
, rezultatem jest ta przestrzeń nazw. - W przeciwnym razie, jeśli
E
jest przestrzenią nazw, aE
zawiera dostępny typ o nazwieI
zK
parametrami typu, wówczas wynikowym typem jest ten skonstruowany z podanymi argumentami typu. - Jeśli
E
jest klasyfikowany jako typ, jeśliE
nie jest parametrem typu, a jeśli przeszukiwanie składnika (§12.5)I
wE
z parametrami typuK
daje wynik,E.I
jest oceniane i klasyfikowane w następujący sposób:Uwaga: gdy wynik takiego wyszukiwania składowego jest grupą metod i
K
wynosi zero, grupa metod może zawierać metody o parametrach typu. Dzięki temu takie metody mogą być brane pod uwagę podczas wnioskowania argumentów typu. uwaga końcowa- Jeśli
I
zidentyfikuje typ, wynikiem jest typ skonstruowany z dowolnymi argumentami typu. - Jeśli
I
zidentyfikuje jedną lub więcej metod, wynik jest grupą metod bez skojarzonego wyrażenia wystąpienia. - Jeśli
I
zidentyfikuje właściwość statyczną, wynik jest dostępem do właściwości bez skojarzonego wyrażenia wystąpienia. - Jeśli
I
zidentyfikuje pole statyczne:- Jeśli pole jest tylko do odczytu, a odwołanie występuje poza konstruktorem statycznym klasy lub struktury, w której to pole jest zadeklarowane, wtedy wynik jest wartością, a mianowicie wartością statycznego pola
I
wE
. - W przeciwnym razie wynikiem jest zmienna, czyli pole statyczne
I
wE
.
- Jeśli pole jest tylko do odczytu, a odwołanie występuje poza konstruktorem statycznym klasy lub struktury, w której to pole jest zadeklarowane, wtedy wynik jest wartością, a mianowicie wartością statycznego pola
- Jeśli
I
zidentyfikuje zdarzenie statyczne:- Jeśli odwołanie występuje w klasie lub strukturze, w której zadeklarowano zdarzenie, a zdarzenie zostało zadeklarowane bez event_accessor_declarations (§15.8.1),
E.I
jest traktowane dokładnie tak, jakbyI
było polem statycznym. - W przeciwnym razie wynik to dostęp do zdarzenia bez skojarzonego wyrażenia instancji.
- Jeśli odwołanie występuje w klasie lub strukturze, w której zadeklarowano zdarzenie, a zdarzenie zostało zadeklarowane bez event_accessor_declarations (§15.8.1),
- Jeśli
I
identyfikuje stałą, wynik jest wartością, a mianowicie wartością tej stałej. - Jeśli
I
zidentyfikuje element członkowski wyliczenia, wynik jest wartością, a mianowicie wartością tego elementu członkowskiego wyliczenia. - W przeciwnym razie
E.I
jest nieprawidłowym odwołaniem do składowych i występuje błąd czasu kompilacji.
- Jeśli
- Jeśli
E
jest dostępem do właściwości, dostępem indeksatora, zmienną lub wartością, którego typ toT
, a wyszukiwanie składowej (§12.5)I
wT
z argumentami typuK
generuje dopasowanie, a następnieE.I
jest oceniany i klasyfikowany w następujący sposób:- Po pierwsze, jeśli
E
jest dostępem do właściwości lub indeksatora, zostanie uzyskana wartość właściwości lub indeksatora (§12.2.2) i E zostanie ponownie sklasyfikowana jako wartość. - Jeśli
I
zidentyfikuje co najmniej jedną metodę, wynikiem jest grupa metod ze skojarzonym wyrażeniem wystąpieniaE
. - Jeśli
I
identyfikuje właściwość instancji, to wynikiem jest dostęp do tej właściwości z przypisanym wyrażeniem instancjiE
i przypisanym typem, który jest typem tej właściwości. JeśliT
jest typem klasy, skojarzony typ jest wybierany z pierwszej deklaracji lub zastępowania właściwości znalezionej podczas rozpoczynania odT
i przeszukiwania jej klas bazowych. - Jeśli
T
jest class_type iI
identyfikuje pole wystąpienia tego class_type:- Jeśli wartość
E
jestnull
, wtedy zostanie rzuconySystem.NullReferenceException
. - W przeciwnym razie, jeśli pole jest tylko do odczytu i odwołanie występuje poza konstruktorem wystąpienia klasy, w której pole jest zadeklarowane, to wynikiem jest wartość tego pola
I
w obiekcie, do którego odnosi sięE
. - W przeciwnym razie wynikiem jest zmienna, czyli pole
I
w obiekcie, do których odwołuje sięE
.
- Jeśli wartość
- Jeśli
T
jest struct_type iI
identyfikuje pole wystąpienia tego struct_type:- Jeśli
E
jest wartością lub jeśli pole jest tylko do odczytu, a odwołanie do pola występuje poza konstruktorem instancji struktury, w której pole jest zadeklarowane, to wynik jest wartością, a mianowicie wartością polaI
w instancji struktury podanej przezE
. - W przeciwnym razie wynik jest zmienną, czyli pole
I
w wystąpieniu struktury podanym przezE
.
- Jeśli
- Jeśli
I
zidentyfikuje zdarzenie wystąpienia:- Jeśli odwołanie występuje w klasie lub struktur, w której zadeklarowano zdarzenie, a zdarzenie zostało zadeklarowane bez event_accessor_declarations (§15.8.1), a odwołanie nie występuje jako lewa strona operatora
a +=
lub-=
,E.I
jest przetwarzane dokładnie tak, jakbyI
było polem wystąpienia. - W przeciwnym razie wynikiem jest dostęp do zdarzenia z przypisanym wyrażeniem instancji
E
.
- Jeśli odwołanie występuje w klasie lub struktur, w której zadeklarowano zdarzenie, a zdarzenie zostało zadeklarowane bez event_accessor_declarations (§15.8.1), a odwołanie nie występuje jako lewa strona operatora
- Po pierwsze, jeśli
- W przeciwnym razie podejmowana jest próba przetworzenia
E.I
jako wywołania metody rozszerzenia (§12.8.10.3). Jeśli to się nie powiedzie,E.I
jest nieprawidłowym odwołaniem do elementu członkowskiego, co powoduje błąd powiązania w czasie.
12.8.7.2 Identyczne proste nazwy i nazwy typów
W przypadku dostępu do elementu członkowskiego formularza E.I
, jeśli E
jest pojedynczym identyfikatorem, a znaczenie E
jako simple_name (§12.8.4) jest stałym, polem, właściwością, zmienną lokalną lub parametrem o tym samym typie co znaczenie E
co type_name (§7.8.1), następnie dozwolone są oba możliwe znaczenia E
. Wyszukiwanie członka E.I
nigdy nie jest niejednoznaczne, ponieważ I
musi być członkiem typu E
w obu przypadkach. Innymi słowy, reguła po prostu zezwala na dostęp do statycznych elementów członkowskich i zagnieżdżonych typów E
, w sytuacji gdy w przeciwnym razie wystąpiłby błąd czasu kompilacji.
Przykład:
struct Color { public static readonly Color White = new Color(...); public static readonly Color Black = new Color(...); public Color Complement() => new Color(...); } class A { public «Color» Color; // Field Color of type Color void F() { Color = «Color».Black; // Refers to Color.Black static member Color = Color.Complement(); // Invokes Complement() on Color field } static void G() { «Color» c = «Color».White; // Refers to Color.White static member } }
Tylko w celach wyjaśniających, w klasie
A
te wystąpienia identyfikatoraColor
, które odwołują się do typuColor
, są oddzielane za pomocą«...»
, a te, które odwołują się do polaColor
, nie są.koniec przykładu
12.8.8 Dostęp warunkowy o wartości null
null_conditional_member_access jest wersją warunkową member_access (§12.8.7) i jest to błąd czasu powiązania, jeśli typ wyniku jest void
. W przypadku wyrażenia warunkowego o wartości null, w którym typ wyniku może być void
zobacz (§12.8.11).
null_conditional_member_access składa się z primary_expression, po którym następują dwa tokeny „?
” i „.
”, a następnie identyfikator z opcjonalnym type_argument_list, po którym następuje zero lub więcej dependent_access, z których dowolny może być poprzedzony operatorem null_forgiving_operator.
null_conditional_member_access
: primary_expression '?' '.' identifier type_argument_list?
(null_forgiving_operator? dependent_access)*
;
dependent_access
: '.' identifier type_argument_list? // member access
| '[' argument_list ']' // element access
| '(' argument_list? ')' // invocation
;
null_conditional_projection_initializer
: primary_expression '?' '.' identifier type_argument_list?
;
Wyrażenie null_conditional_member_accessE
ma formę P?.A
. Znaczenie E
jest określane w następujący sposób:
Jeśli typ
P
jest typem wartości dopuszczającym wartość null:Niech
T
będzie typemP.Value.A
.Jeśli
T
jest parametrem typu, który nie jest znany jako typ referencyjny lub typ wartości niemogący przyjąć wartości null, wystąpi błąd czasu kompilacji.Jeśli
T
jest typem wartości, który nie dopuszcza wartości null, to typE
jestT?
, i znaczenieE
jest takie samo jak znaczenie:((object)P == null) ? (T?)null : P.Value.A
Z tą różnicą, że
P
jest obliczana tylko raz.W przeciwnym razie typ
E
jestT
, a znaczenieE
jest takie samo jak znaczenie:((object)P == null) ? (T)null : P.Value.A
Z tą różnicą, że
P
jest obliczana tylko raz.
Inaczej:
Niech
T
być typem wyrażeniaP.A
.Jeśli
T
jest parametrem typu, który nie jest znany jako typ referencyjny lub typ wartości niemogący przyjąć wartości null, wystąpi błąd czasu kompilacji.Jeśli
T
jest typem wartości, który nie dopuszcza wartości null, to typE
jestT?
, i znaczenieE
jest takie samo jak znaczenie:((object)P == null) ? (T?)null : P.A
Z tą różnicą, że
P
jest obliczana tylko raz.W przeciwnym razie typ
E
jestT
, a znaczenieE
jest takie samo jak znaczenie:((object)P == null) ? (T)null : P.A
Z tą różnicą, że
P
jest obliczana tylko raz.
Uwaga: w wyrażeniu formularza:
P?.A₀?.A₁
następnie, jeżeli
P
jest obliczany nanull
, aniA₀
, aniA₁
nie są obliczane. Dotyczy to także sytuacji, gdy wyrażenie stanowi sekwencję null_conditional_member_access lub null_conditional_element_access§12.8.13 operacji.uwaga końcowa
null_conditional_projection_initializer stanowi ograniczenie null_conditional_member_access i ma tę samą semantykę. Występuje on tylko jako inicjator projekcji w wyrażeniu tworzenia obiektu anonimowego (§12.8.17.7).
12.8.9 Wyrażenia typu null forgiving
12.8.9.1 Ogólne
Wartość, typ, klasyfikacja wyrażenia o wartości null (§12.2) i bezpieczny kontekst (§16.4.12) to wartość, typ, klasyfikacja i bezpieczny kontekst jego primary_expression.
null_forgiving_expression
: primary_expression null_forgiving_operator
;
null_forgiving_operator
: '!'
;
Uwaga: Postfiksowy operator ignorowania wartości null i prefiksowy operator negacji logicznej (§12.9.4), mimo że są reprezentowane przez ten sam token leksykalny (!
), są odrębne. Tylko ten ostatni może być zastępowany (§15.10), definicja operatora akceptującego null jest stała.
uwaga końcowa
Jest to błąd czasu kompilacji stosowanie operatora odrzucania null więcej niż raz do tego samego wyrażenia, niezależnie od nawiasów interweniujących.
Przykładowy: wszystkie następujące elementy są nieprawidłowe:
var p = q!!; // error: applying null_forgiving_operator more than once var s = ( ( m(t) ! ) )! // error: null_forgiving_operator applied twice to m(t)
koniec przykładu
Pozostała część tego podpunktu i następujące podpunkty równorzędne są warunkowo normatywne.
Kompilator wykonujący statyczną analizę stanu null (§8.9.5) musi być zgodny z następującą specyfikacją.
Operator niwelujący null to pseudooperacja wykonywana podczas kompilacji, która służy do informowania kompilatora o analizie statycznego stanu null. Ma dwa zastosowania: przysłonić określenie kompilatora, że wyrażenie może mieć wartość null, oraz przysłonić ostrzeżenie kompilatora dotyczące nullowalności.
Zastosowanie operatora forgiving o wartości null do wyrażenia, dla którego statyczna analiza stanu null kompilatora nie generuje żadnych ostrzeżeń, nie jest błędem.
12.8.9.2 Zastąpienie określenia "może być null"
W niektórych okolicznościach statyczna analiza stanu null kompilatora może określić, że wyrażenie ma stan null może mieć wartość null i wydać ostrzeżenie diagnostyczne, gdy inne informacje wskazują, że wyrażenie nie może mieć wartości null. Zastosowanie operatora wyciszającego null do takiego wyrażenia informuje statyczną analizę stanu null kompilatora, że stan null jest w , a nie w stanie null; co zapobiega ostrzeżeniu diagnostycznemu i może wpływać na bieżącą analizę.
Przykład: rozważ następujące kwestie:
#nullable enable public static void M() { Person? p = Find("John"); // returns Person? if (IsValid(p)) { Console.WriteLine($"Found {p!.Name}"); // p can't be null } } public static bool IsValid(Person? person) => person != null && person.Name != null;
Jeśli
IsValid
zwracatrue
, można bezpiecznie przeprowadzić dereferencjęp
, aby uzyskać dostęp do jego właściwościName
, a ostrzeżenie "dereferencja możliwej wartości null" można pominąć za pomocą!
.koniec przykładu
Przykład: Operator odpuszczający null powinien być używany z ostrożnością, należy rozważyć:
#nullable enable int B(int? x) { int y = (int)x!; // quash warning, throw at runtime if x is null return y; }
W tym miejscu operator wybaczający wartość null jest stosowany do typu wartości i pomija wszelkie ostrzeżenia w
x
. Jeśli jednakx
jestnull
w czasie wykonywania, zostanie zgłoszony wyjątek, ponieważnull
nie można rzutować naint
.koniec przykładu
12.8.9.3 Przesłanianie innych ostrzeżeń analizy null
Oprócz zastępowania może być nullem ustaleń, jak było to wspomniane wcześniej, mogą istnieć inne okoliczności, w których konieczne jest zastąpienie wyników statycznej analizy przez kompilator, które wskazują, że wyrażenie wymaga co najmniej jednego ostrzeżenia. Zastosowanie operatora pozwalającego na wartości null do takiego wyrażenia żąda, aby kompilator nie wystawiał żadnych ostrzeżeń dla tego wyrażenia. W odpowiedzi kompilator może nie wydawać ostrzeżeń, a także zmodyfikować jego dalszą analizę.
Przykład: rozważ następujące kwestie:
#nullable enable public static void Assign(out string? lv, string? rv) { lv = rv; } public string M(string? t) { string s; Assign(out s!, t ?? "«argument was null»"); return s; }
Typy parametrów metody
Assign
,lv
&rv
, sąstring?
, alv
jako parametr wyjściowy, który wykonuje proste przypisanie.Metoda
M
przekazuje zmiennąs
, typustring
, jako parametr wyjściowyAssign
. Kompilator wydaje ostrzeżenie, ponieważs
nie jest zmienną, która może przyjmować wartość null. Ponieważ drugi argumentAssign
nie może mieć wartości null, używany jest operator pomijający null, aby usunąć ostrzeżenie.koniec przykładu
Koniec warunkowo normatywnego tekstu.
12.8.10 Wyrażenia wywołania
12.8.10.1 Ogólne
Wyrażenie invocation_expression jest używane do wywoływania metody.
invocation_expression
: primary_expression '(' argument_list? ')'
;
primary_expression może być null_forgiving_expression tylko wtedy, gdy ma delegate_type.
invocation_expression jest dynamicznie powiązany (§12.3.3), jeśli spełniony jest co najmniej jeden z następujących warunków:
-
primary_expression ma typ czasu kompilacji
dynamic
. - Co najmniej jeden argument opcjonalnego argument_list ma typ czasu kompilacji
dynamic
.
W tym przypadku kompilator klasyfikuje invocation_expression jako wartość typu dynamic
. Poniższe reguły pozwalające określić znaczenie invocation_expression są następnie stosowane w czasie wykonywania, wykorzystuje się typ czasu wykonywania zamiast typu czasu kompilacji primary_expression i argumentów, które mają typ czasu kompilacji dynamic
. Jeśli primary_expression nie ma typu czasu kompilacji dynamic
, wywołanie metody przechodzi ograniczoną kontrolę czasu kompilacji zgodnie z opisem w §12.6.5.
primary_expression lub wartość invocation_expression musi być grupą metod lub wartością typu delegate_type. Jeśli primary_expression jest grupą metod, invocation_expression jest wywołaniem metody (§12.8.10.2). Jeśli primary_expression jest wartością typu delegate_type, invocation_expression jest wywołaniem delegata (§12.8.10.4). Jeśli primary_expression nie jest ani grupą metod, ani wartością typu delegate_type, wystąpi błąd w czasie wiązania.
Opcjonalny argument_list (§12.6.2) zawiera wartości lub odwołania do zmiennych dla parametrów metody.
Wynik oceny wyrażenia wywołania jest klasyfikowany w następujący sposób:
- Jeśli invocation_expression wywołuje metodę, która nie zwraca wartości (§15.6.1) lub delegata, który nie zwraca wartości, wynik jest pusty. Wyrażenie sklasyfikowane jako nic nie jest dozwolone tylko w kontekście statement_expression (§13.7) lub jako treść lambda_expression (§12.19). W przeciwnym razie występuje błąd czasu wiązania.
- W przeciwnym razie, jeśli wyrażenie invocation_expression wywołuje metodę zwracającą przez ref (§15.6.1) lub delegata zwracającego przez ref, wynikiem jest zmienna o typie związanym z typem zwracanym metody lub delegata. Jeśli wywołanie dotyczy metody instancji, a odbiornik jest typu klasy
T
, skojarzony typ jest wybierany z pierwszej deklaracji lub nadpisania metody znalezionej, zaczynając od klasyT
i przeszukując jej klasy bazowe. - W przeciwnym razie wyrażenie wywołujące wywołuje metodę zwracającą przez wartość (§15.6.1) lub delegata zwracającego wartość, a wynik jest wartością o typie skojarzonym z typem zwracanym przez metodę lub delegata. Jeśli wywołanie dotyczy metody instancji, a odbiornik jest typu klasy
T
, skojarzony typ jest wybierany z pierwszej deklaracji lub nadpisania metody znalezionej, zaczynając od klasyT
i przeszukując jej klasy bazowe.
Wywołania metod 12.8.10.2
W przypadku wywołania metody primary_expression wyrażenie invocation_expression powinno być określane jako grupa metod. Grupa metod identyfikuje jedną metodę do wywołania lub zestaw przeciążonych metod, z których należy wybrać określoną metodę do wywołania. W tym drugim przypadku określenie określonej metody wywoływania jest oparte na kontekście dostarczonym przez typy argumentów w argument_list.
Przetwarzanie czasu wiązania wywołania metody w formie M(A)
, gdzie M
jest zestawem metod (prawdopodobnie obejmującym type_argument_list), a A
jest opcjonalnym argument_list, składa się z następujących kroków:
- Konstruowany jest zestaw metod kandydatów dla wywołania metody. Dla każdej metody
F
skojarzonej z grupą metodM
:- Jeśli
F
nie jest ogólny,F
jest kandydatem, gdy:-
M
nie ma listy argumentów typu i -
F
ma zastosowanie w odniesieniu doA
(§12.6.4.2).
-
- Jeśli
F
jest ogólny iM
nie ma listy argumentów typu,F
jest kandydatem, gdy:- Wnioskowanie typu (§12.6.3) kończy się powodzeniem, w wyniku czego wywnioskowano listę argumentów typu dla wywołania i
- Po zastąpieniu argumentów typu wnioskowanego dla odpowiednich parametrów typu metody wszystkie typy skonstruowane na liście parametrów
F
spełniają swoje ograniczenia (§8.4.5), a lista parametrówF
ma zastosowanie w odniesieniu doA
(§12.6.4.2)
- Jeśli
F
jest ogólny iM
zawiera listę argumentów typu,F
jest kandydatem, gdy:-
F
ma taką samą liczbę parametrów typu metody, jak podano na liście argumentów typu i - Po zastąpieniu argumentów typu odpowiednimi parametrami typu metody wszystkie skonstruowane typy na liście parametrów
F
spełniają swoje ograniczenia (§8.4.5), a lista parametrówF
ma zastosowanie w odniesieniu doA
(§12.6.4.2).
-
- Jeśli
- Zestaw metod kandydatów jest ograniczony do zawierania tylko metod z najbardziej pochodnych typów: dla każdej metody
C.F
w zestawie, gdzieC
jest typem, w którym metodaF
jest zadeklarowana, wszystkie metody zadeklarowane w podstawowym typieC
są usuwane z zestawu. Ponadto, jeśliC
jest typem klasy innym niżobject
, wszystkie metody zadeklarowane w typie interfejsu są usuwane z zestawu.Uwaga: ta ostatnia reguła ma wpływ tylko wtedy, gdy grupa metodowa była wynikiem wyszukiwania składowego dla parametru typu o efektywnej klasie bazowej innej niż
object
i niepustego zestawu efektywnych interfejsów. uwaga końcowa - Jeśli wynikowy zestaw metod kandydatów jest pusty, dalsze przetwarzanie w kolejnych krokach jest porzucane, a zamiast tego podejmowana jest próba przetworzenia wywołania jako wywołania metody rozszerzenia (§12.8.10.3). Jeśli to się nie powiedzie, nie istnieją odpowiednie metody i wystąpi błąd czasu powiązania.
- Najlepsza metoda z zestawu metod kandydatów jest identyfikowana za pomocą reguł rozwiązywania przeciążenia §12.6.4. Jeśli nie można zidentyfikować jednej najlepszej metody, wywołanie metody jest niejednoznaczne i występuje błąd czasu powiązania. Podczas rozwiązywania przeciążeń parametry metody ogólnej są uwzględniane po podstawianiu argumentów typu (podanych lub wywnioskowanych) dla odpowiednich parametrów typu metody.
Po wybraniu i zweryfikowaniu metody na etapie wiązania przez powyższe kroki, rzeczywiste wywołanie w czasie wykonania jest przetwarzane zgodnie z regułami wywołania elementu członkowskiego funkcji opisanymi w §12.6.6.
Uwaga: Intuicyjny efekt reguł rozpoznawania opisanych powyżej jest następujący: Aby zlokalizować określoną metodę wywoływaną przez wywołanie metody, zacznij od typu określonego przez wywołanie metody i kontynuuj cały łańcuch dziedziczenia aż do momentu, gdy zostanie znaleziona co najmniej jedna odpowiednia, dostępna, niezastępująca deklaracja metody. Następnie wykonaj wnioskowanie typu i rozwiązywanie przeciążenia dla zestawu odpowiednich, dostępnych, nieprzesłanianych metod zadeklarowanych w tym typie i wywołaj w ten sposób wybraną metodę. Jeśli nie znaleziono metody, spróbuj przetworzyć wywołanie jako wywołanie metody rozszerzenia. uwaga końcowa
Wywołania metody rozszerzenia 12.8.10.3
W wywołaniu metody (§12.6.6.2) jednej z formularzy
«expr» . «identifier» ( )
«expr» . «identifier» ( «args» )
«expr» . «identifier» < «typeargs» > ( )
«expr» . «identifier» < «typeargs» > ( «args» )
Jeśli normalne przetwarzanie wywołania nie znajdzie żadnych odpowiednich metod, zostanie podjęta próba zinterpretowania konstrukcji jako wywołania metody rozszerzenia. Jeśli «expr» lub dowolny z «args» ma typ kompilacji dynamic
, metody rozszerzenia nie będą stosowane.
Celem jest znalezienie najlepszego type_nameC
, aby można było wykonać odpowiednie wywołanie metody statycznej:
C . «identifier» ( «expr» )
C . «identifier» ( «expr» , «args» )
C . «identifier» < «typeargs» > ( «expr» )
C . «identifier» < «typeargs» > ( «expr» , «args» )
Metoda rozszerzenia Cᵢ.Mₑ
jest kwalifikuje się, jeśli:
-
Cᵢ
to niegeneryczna, nienagnieżdżona klasa - Nazwa
Mₑ
to identyfikator -
Mₑ
jest dostępny i ma zastosowanie w przypadku zastosowania do argumentów jako metody statycznej, jak pokazano powyżej - Niejawna tożsamość, odwołanie lub konwersja boksu istnieje z do typu pierwszego parametru
Mₑ
.
Wyszukiwanie C
przebiega w następujący sposób:
- Począwszy od najbliższej otaczającej deklaracji przestrzeni nazw, kontynuując przez każdą otaczającą deklarację przestrzeni nazw, a kończąc na jednostce kompilacji, podejmowane są kolejne próby znalezienia potencjalnego zestawu metod rozszerzeń.
- Jeśli dana przestrzeń nazw lub jednostka kompilacji zawiera bezpośrednio deklaracje typów nieogólnych
Cᵢ
z uprawnionymi metodami rozszerzeniaMₑ
, to zestaw tych metod rozszerzenia jest zestawem kandydatów. - Jeśli przestrzenie nazw importowane przy użyciu dyrektyw przestrzeni nazw w danej przestrzeni nazw lub jednostce kompilacji bezpośrednio zawierają deklaracje typów innych niż ogólne
Cᵢ
z uprawnionymi metodami rozszerzeniaMₑ
, zestaw tych metod rozszerzenia jest zestawem kandydatów.
- Jeśli dana przestrzeń nazw lub jednostka kompilacji zawiera bezpośrednio deklaracje typów nieogólnych
- Jeśli żaden zestaw kandydatów nie zostanie znaleziony w żadnej otaczającej deklaracji przestrzeni nazw lub jednostce kompilacji, wystąpi błąd czasu kompilacji.
- W przeciwnym razie rozwiązanie przeciążenia jest stosowane do zestawu kandydatów zgodnie z opisem w §12.6.4. Jeśli nie zostanie znaleziona żadna najlepsza metoda, wystąpi błąd czasu kompilacji.
-
C
jest typem, w którym najlepsza metoda jest zadeklarowana jako metoda rozszerzenia.
Kiedy C
jest używany jako obiekt docelowy, wywołanie metody jest traktowane jako wywołanie metody statycznej (§12.6.6).
Uwaga: w przeciwieństwie do wywołania metody wystąpienia nie jest zgłaszany wyjątek, gdy wyrażenie oblicza odwołanie o wartości null. Zamiast tego ta wartość
null
jest przekazywana do metody rozszerzenia, tak jak w przypadku zwykłego wywołania metody statycznej. Implementacja metody rozszerzenia decyduje o tym, jak reagować na takie wywołanie. uwaga końcowa
Powyższe reguły oznaczają, że metody instancji mają pierwszeństwo przed metodami rozszerzeń, że metody rozszerzeń dostępne w deklaracjach przestrzeni nazw wewnętrznych mają pierwszeństwo przed metodami rozszerzeń dostępnymi w deklaracjach przestrzeni nazw zewnętrznych, a metody rozszerzeń zadeklarowane bezpośrednio w przestrzeni nazw mają pierwszeństwo przed metodami rozszerzeń importowanymi do tej samej przestrzeni nazw przy użyciu dyrektywy przestrzeni nazw.
Przykład:
public static class E { public static void F(this object obj, int i) { } public static void F(this object obj, string s) { } } class A { } class B { public void F(int i) { } } class C { public void F(object obj) { } } class X { static void Test(A a, B b, C c) { a.F(1); // E.F(object, int) a.F("hello"); // E.F(object, string) b.F(1); // B.F(int) b.F("hello"); // E.F(object, string) c.F(1); // C.F(object) c.F("hello"); // C.F(object) } }
W tym przykładzie metoda
B
ma pierwszeństwo przed pierwszą metodą rozszerzenia, aC
metoda ma pierwszeństwo przed obiem metodami rozszerzenia.public static class C { public static void F(this int i) => Console.WriteLine($"C.F({i})"); public static void G(this int i) => Console.WriteLine($"C.G({i})"); public static void H(this int i) => Console.WriteLine($"C.H({i})"); } namespace N1 { public static class D { public static void F(this int i) => Console.WriteLine($"D.F({i})"); public static void G(this int i) => Console.WriteLine($"D.G({i})"); } } namespace N2 { using N1; public static class E { public static void F(this int i) => Console.WriteLine($"E.F({i})"); } class Test { static void Main(string[] args) { 1.F(); 2.G(); 3.H(); } } }
Dane wyjściowe tego przykładu to:
E.F(1) D.G(2) C.H(3)
D.G
ma pierwszeństwo przedC.G
, aE.F
ma pierwszeństwo przed zarównoD.F
, jak iC.F
.koniec przykładu
12.8.10.4 Wywołania delegata
W przypadku wywołania delegata primary_expressioninvocation_expression powinno być wartością delegate_type. Ponadto biorąc pod uwagę, że delegate_type być członkiem funkcji z tą samą listą parametrów co delegate_type, delegate_type mają zastosowanie (§12.6.4.2) w odniesieniu do argument_listinvocation_expression.
Przetwarzanie w czasie wykonywania wywołania delegata dla formularza D(A)
, gdzie D
jest wyrażeniem pierwotnym (primary_expression) typu delegata (delegate_type), a A
jest opcjonalną listą argumentów (argument_list), obejmuje następujące kroki:
-
D
jest oceniana. Jeśli ta ocena powoduje wyjątek, nie są wykonywane żadne dalsze kroki. - Zostanie obliczona lista argumentów
A
. Jeśli ta ocena powoduje wyjątek, nie są wykonywane żadne dalsze kroki. - Wartość
D
jest sprawdzana jako prawidłowa. Jeśli wartośćD
jest równanull
, zostanie wyrzuconySystem.NullReferenceException
i żadne dalsze kroki nie zostaną wykonane. - W przeciwnym razie
D
jest odwołaniem do wystąpienia delegata. Wywołania elementów członkowskich funkcji (§12.6.6) są wykonywane na każdym z wywoływanych obiektów na liście wywołań elementów delegata. W przypadku wywoływalnych jednostek składających się z wystąpienia i metody wystąpienia, instancja używana do wywołania to ta zawarta w jednostce wywołującej.
Aby uzyskać szczegółowe informacje o wielu listach wywołań bez parametrów, zobacz §20.6.
12.8.11 Wyrażenie warunkowe wywołania przy null
null_conditional_invocation_expression jest składniowo albo wyrażeniem null_conditional_member_access (§12.8.8), lub wyrażeniem null_conditional_element_access (§12.8.13), gdzie końcowy dependent_access jest wyrażeniem wywoływania (§12.8.10).
null_conditional_invocation_expression występuje w kontekście statement_expression (§13.7), anonymous_function_body (§12.19.1) lub method_body (§15.6.1).
W przeciwieństwie do składni równoważnej null_conditional_member_access lub null_conditional_element_access, null_conditional_invocation_expression może być klasyfikowana jako nic.
null_conditional_invocation_expression
: null_conditional_member_access null_forgiving_operator? '(' argument_list? ')'
| null_conditional_element_access null_forgiving_operator? '(' argument_list? ')'
;
Opcjonalny null_forgiving_operator można uwzględnić tylko wtedy, gdy null_conditional_member_access lub null_conditional_element_access ma delegate_type.
Wyrażenie null_conditional_invocation_expressionE
ma postać P?A
; gdzie A
jest resztą syntaktycznie równoważnej null_conditional_member_access lub null_conditional_element_access, A
w związku z tym rozpocznie się od .
lub [
. Niech PA
oznacza połączenie P
i A
.
Gdy E
występuje jako wyrażenie , znaczenie E
jest takie samo jak znaczenie wyrażenia .
if ((object)P != null) PA
z tą różnicą, że P
jest obliczana tylko raz.
Gdy E
występuje jako anonymous_function_body lub method_body znaczenie E
zależy od jego klasyfikacji:
Jeśli
E
jest klasyfikowana jako nic, jego znaczenie jest takie samo jak znaczenie bloku :{ if ((object)P != null) PA; }
z tą różnicą, że
P
jest obliczana tylko raz.W przeciwnym razie znaczenie
E
jest takie samo jak znaczenie bloku :{ return E; }
i z kolei znaczenie bloku zależy od tego, czy
E
jest syntaktycznie równoważne z null_conditional_member_access (§12.8.8) albo z null_conditional_element_access (§12.8.13).
12.8.12 Dostęp do elementów
12.8.12.1 Ogólne
element_access składa się z primary_no_array_creation_expression, po którym następuje token "[
", argument_list, a na końcu token "]
".
argument_list składa się z co najmniej jednego argumentus oddzielonego przecinkami.
element_access
: primary_no_array_creation_expression '[' argument_list ']'
;
Argument argument_list w element_access nie może zawierać argumentów out
ani ref
.
element_access jest dynamicznie związany (§12.3.3), jeśli co najmniej jeden z następujących warunków:
- Wyrażenie primary_no_array_creation_expression ma typ ustalany podczas kompilacji
dynamic
. - Co najmniej jedno wyrażenie argument_list ma typ czasu kompilacji
dynamic
, a primary_no_array_creation_expression nie ma typu tablicy.
W tym przypadku kompilator klasyfikuje element_access jako wartość typu dynamic
. Poniższe reguły określające znaczenie element_access są następnie stosowane w czasie wykonywania przy użyciu typu czasu wykonywania zamiast typu czasu kompilacji tych z primary_no_array_creation_expression i argument_list wyrażeń, które mają typ czasu kompilacji dynamic
. Jeśli primary_no_array_creation_expression nie ma typu czasu kompilacji dynamic
, dostęp do elementu podlega ograniczonemu sprawdzeniu w czasie kompilacji zgodnie z opisem w §12.6.5.
Jeśli primary_no_array_creation_expressionelement_access jest wartością array_type, element_access jest dostępem do tablicy (§12.8.12.2). W przeciwnym razie primary_no_array_creation_expression powinien być zmienną lub wartością klasy, struktury bądź typu interfejsu, który posiada co najmniej jednego członka indeksatora; w takim przypadku element_access jest dostępem indeksatora (§12.8.12.3).
12.8.12.2 Dostęp do tablicy
W przypadku dostępu do tablicy primary_no_array_creation_expression w element_access powinno być wartością array_type. Ponadto argument_list w dostępie do tablicy nie może zawierać nazwanych argumentów. Liczba wyrażeń w argument_list jest taka sama jak ranga array_type, a każde wyrażenie jest typu int
, uint
, long
lub ulong,
lub niejawnie konwertowane na co najmniej jeden z tych typów.
Wynikiem oceny dostępu do tablicy jest zmienna typu elementu tablicy, a mianowicie element tablicy wybrany przez wartości wyrażeń w argument_list.
Przetwarzanie w czasie wykonywania dostępu do elementu tablicy w formacie P[A]
, gdzie P
jest wyrażeniem primary_no_array_creation_expression typu array_type i A
jest listą argument_list, składa się z następujących kroków:
-
P
jest oceniana. Jeśli ta ocena powoduje wyjątek, nie są wykonywane żadne dalsze kroki. - Wyrażenia indeksu argument_list są oceniane w kolejności od lewej do prawej. Po przeprowadzeniu oceny każdego wyrażenia indeksu wykonywana jest niejawna konwersja (§10.2) do jednego z następujących typów:
int
,uint
,long
,ulong
. Pierwszy typ na tej liście, dla którego istnieje niejawna konwersja, jest wybierany. Jeśli na przykład wyrażenie indeksu jest typushort
jest wykonywana niejawna konwersja naint
, ponieważ możliwe są niejawne konwersje zshort
naint
i zshort
dolong
. Jeśli ocena wyrażenia indeksu lub kolejnej niejawnej konwersji powoduje wyjątek, żadne dalsze wyrażenia indeksu nie są oceniane i nie są wykonywane żadne dalsze kroki. - Wartość
P
jest sprawdzana jako prawidłowa. Jeśli wartośćP
jest równanull
, zostanie wyrzuconySystem.NullReferenceException
i żadne dalsze kroki nie zostaną wykonane. - Wartość każdego wyrażenia w argument_list jest sprawdzana względem rzeczywistych granic każdego wymiaru wystąpienia tablicy, do których odwołuje się
P
. Jeśli co najmniej jedna wartość jest poza zakresem, zostanie rzuconySystem.IndexOutOfRangeException
i dalsze kroki nie będą wykonane. - Lokalizacja elementu tablicy podana przez wyrażenia indeksu jest obliczana, a ta lokalizacja staje się wynikiem dostępu do tablicy.
12.8.12.3 Dostęp do indeksatora
W przypadku dostępu indeksatora, "primary_no_array_creation_expression" z "element_access" musi być zmienną lub wartością klasy, struktury lub typu interfejsu, a ten typ musi implementować co najmniej jeden indeksator, który jest odpowiedni w stosunku do "argument_list" z "element_access".
Przetwarzanie czasu powiązania dostępu indeksatora do formularza P[A]
, gdzie P
jest primary_no_array_creation_expression klasy, struktury lub typu interfejsu T
, a A
jest argument_list, składa się z następujących kroków:
- Tworzony jest zestaw indeksatorów udostępnianych przez
T
. Zestaw składa się ze wszystkich indeksatorów zadeklarowanych wT
lub podstawowego typuT
, które nie są zastępowane deklaracjami i są dostępne w bieżącym kontekście (§7.5). - Zestaw jest zredukowany do tych indeksatorów, które mają zastosowanie i nie są ukryte przez innych indeksatorów. Następujące reguły są stosowane do każdego indeksatora
S.I
w zestawie, gdzieS
jest typem, w którym indeksatorI
jest zadeklarowany:- Jeśli
I
nie ma zastosowania w odniesieniu doA
(§12.6.4.2),I
zostanie usunięta z zestawu. - Jeśli
I
ma zastosowanie w odniesieniu doA
(§12.6.4.2), wszystkie indeksatory zadeklarowane w podstawowym typieS
zostaną usunięte z zestawu. - Jeśli
I
ma zastosowanie w odniesieniu doA
(§12.6.4.2) iS
jest typem klasy innym niżobject
, wszystkie indeksatory zadeklarowane w interfejsie są usuwane z zestawu.
- Jeśli
- Jeśli wynikowy zestaw indeksatorów kandydatów jest pusty, nie ma odpowiednich indeksatorów i wystąpi błąd czasu wiązania.
- Najlepszy indeksator zestawu indeksatorów kandydatów jest identyfikowany przy użyciu reguł rozpoznawania przeciążenia §12.6.4. Jeśli nie można zidentyfikować jednego najlepszego indeksatora, dostęp indeksatora jest niejednoznaczny i wystąpi błąd w czasie wiązania.
- Wyrażenia indeksu argument_list są oceniane w kolejności od lewej do prawej. Wynikiem przetwarzania dostępu indeksatora jest wyrażenie sklasyfikowane jako dostęp indeksatora. Wyrażenie dostępu indeksatora odwołuje się do indeksatora określonego w powyższym kroku i ma skojarzone wyrażenie instancji
P
i skojarzoną listę argumentówA
oraz skojarzony typ, który jest typem indeksatora. JeśliT
jest typem klasy, skojarzony typ jest wybierany z pierwszej deklaracji lub zastępowania indeksatora znalezionego podczas rozpoczynania odT
i przeszukiwania jego klas bazowych.
W zależności od kontekstu, w którym jest używany, dostęp do indeksatora powoduje wywołanie pobierającego akcesora lub ustawiającego akcesora indeksatora. Jeśli dostęp indeksatora jest celem przypisania, akcesor set jest wywoływany w celu przypisania nowej wartości (§12.21.2). We wszystkich innych przypadkach wywoływany jest akcesor, aby uzyskać bieżącą wartość (§12.2.2).
12.8.13 Dostęp warunkowy o wartości null
null_conditional_element_access składa się z primary_no_array_creation_expression, po którym następują dwa tokeny "?
" i "[
", następnie argument_list, potem token "]
", po którym występuje zero lub więcej dependent_access, z których każdy może być poprzedzony operatorem null_forgiving_operator.
null_conditional_element_access
: primary_no_array_creation_expression '?' '[' argument_list ']'
(null_forgiving_operator? dependent_access)*
;
null_conditional_element_access jest wersją warunkową element_access (§12.8.12) i jest to błąd czasu powiązania, jeśli typ wyniku jest void
. W przypadku wyrażenia warunkowego o wartości null, w którym typ wyniku może być void
zobacz (§12.8.11).
Wyrażenie null_conditional_element_accessE
ma postać P?[A]B
; gdzie B
są dependent_accesses, jeśli istnieją. Znaczenie E
jest określane w następujący sposób:
Jeśli typ
P
jest typem wartości dopuszczającym wartość null:Niech
T
być typem wyrażeniaP.Value[A]B
.Jeśli
T
jest parametrem typu, który nie jest znany jako typ referencyjny lub typ wartości niemogący przyjąć wartości null, wystąpi błąd czasu kompilacji.Jeśli
T
jest typem wartości, który nie dopuszcza wartości null, to typE
jestT?
, i znaczenieE
jest takie samo jak znaczenie:((object)P == null) ? (T?)null : P.Value[A]B
Z tą różnicą, że
P
jest obliczana tylko raz.W przeciwnym razie typ
E
jestT
, a znaczenieE
jest takie samo jak znaczenie:((object)P == null) ? null : P.Value[A]B
Z tą różnicą, że
P
jest obliczana tylko raz.
Inaczej:
Niech
T
być typem wyrażeniaP[A]B
.Jeśli
T
jest parametrem typu, który nie jest znany jako typ referencyjny lub typ wartości niemogący przyjąć wartości null, wystąpi błąd czasu kompilacji.Jeśli
T
jest typem wartości, który nie dopuszcza wartości null, to typE
jestT?
, i znaczenieE
jest takie samo jak znaczenie:((object)P == null) ? (T?)null : P[A]B
Z tą różnicą, że
P
jest obliczana tylko raz.W przeciwnym razie typ
E
jestT
, a znaczenieE
jest takie samo jak znaczenie:((object)P == null) ? null : P[A]B
Z tą różnicą, że
P
jest obliczana tylko raz.
Uwaga: w wyrażeniu formularza:
P?[A₀]?[A₁]
jeśli
P
ocenianull
nie są oceniane aniA₀
, aniA₁
. To samo jest prawdą, gdy wyrażenie jest sekwencją pod rząd operacji null_conditional_element_access lub null_conditional_member_access§12.8.8.uwaga końcowa
12.8.14 Ten dostęp
this_access składa się ze słowa kluczowego this
.
this_access
: 'this'
;
this_access jest dozwolona tylko w bloku konstruktora wystąpienia, metody wystąpienia, metody dostępu wystąpienia (§12.2.1) lub finalizatora. Ma jedno z następujących znaczenia:
- Gdy
this
jest używane w wyrażeniu_pierwotnym w konstruktorze instancji klasy, jest klasyfikowane jako wartość. Typ wartości jest typem wystąpienia (§15.3.2) klasy, w której występuje użycie, a wartość jest odwołaniem do tworzonego obiektu. - Gdy
this
jest używana w wyrażeniu podstawowym w metodzie instancji lub akcesorze instancji klasy, jest klasyfikowana jako wartość. Typ wartości jest typem wystąpienia (§15.3.2) klasy, w której następuje użycie, a wartość jest odwołaniem do obiektu, dla którego wywoływano metodę lub akcesor. - Gdy
this
jest używana w primary_expression w konstruktorze wystąpienia struktury, jest klasyfikowana jako zmienna. Typ zmiennej to typ wystąpienia (§15.3.2) struktury, w której występuje użycie, a zmienna reprezentuje konstruowaną strukturę.- Jeśli deklaracja konstruktora nie ma inicjatora konstruktora, zmienna
this
zachowuje się dokładnie tak samo jak parametr wyjściowy typu struktury. W szczególności oznacza to, że zmienna musi być jednoznacznie przypisana w każdej ścieżce wykonania konstruktora instancji. - W przeciwnym razie zmienna
this
zachowuje się dokładnie tak samo jak parametrref
typu struktury. W szczególności oznacza to, że zmienna jest uznawana za początkowo przypisaną.
- Jeśli deklaracja konstruktora nie ma inicjatora konstruktora, zmienna
- Gdy
this
jest używana w wyrażeniu podstawowym w metodzie instancji lub akcesorze instancji struktury, jest klasyfikowana jako zmienna. Typ zmiennej to typ wystąpienia (§15.3.2) struktury, w której występuje użycie.- Jeśli metoda lub metoda dostępu nie jest iteratorem (§15.14) lub funkcji asynchronicznej (§15.15), zmienna
this
reprezentuje strukturę, dla której wywoływano metodę lub metodę dostępu.- Jeśli struktura jest
readonly struct
, zmiennathis
zachowuje się dokładnie tak samo jak parametr wejściowy typu struktury - W przeciwnym razie zmienna
this
zachowuje się dokładnie tak samo jak parametrref
typu struktury
- Jeśli struktura jest
- Jeśli metoda lub metoda dostępu jest iteratorem lub funkcją asynchronikową, zmienna
this
reprezentuje kopii struktury, dla której wywołano metodę lub metodę dostępu, i zachowuje się dokładnie tak samo jak wartość parametr typu struktury.
- Jeśli metoda lub metoda dostępu nie jest iteratorem (§15.14) lub funkcji asynchronicznej (§15.15), zmienna
Użycie this
w primary_expression w kontekście innym niż wymienione powyżej jest błędem czasu kompilacji. W szczególności nie można odwoływać się do this
w metodzie statycznej, statycznym akcesorze właściwości lub w variable_initializer deklaracji pola.
12.8.15 Dostęp podstawowy
base_access składa się z bazy słów kluczowych, a następnie tokenu ".
" oraz identyfikatora i opcjonalnego type_argument_list lub argument_list ujętego w nawiasy kwadratowe:
base_access
: 'base' '.' identifier type_argument_list?
| 'base' '[' argument_list ']'
;
base_access służy do dostępu do składowych klas bazowych, które są ukryte przez podobnie nazwane składowe w bieżącej klasie lub strukturze.
base_access jest dozwolona tylko w treści konstruktora instancji, metody instancji, akcesora instancji (§12.2.1) lub finalizatora. Gdy base.I
występuje w klasie lub strukturze, będę oznaczać element członkowski klasy bazowej tej klasy lub struktury. Podobnie, gdy base[E]
występuje w klasie, odpowiedni indeksator musi istnieć w klasie bazowej.
W czasie wiązania wyrażenia base_access w formie base.I
i base[E]
są oceniane dokładnie tak, jak gdyby były zapisane ((B)this).I
i ((B)this)[E]
, gdzie B
jest klasą bazową klasy lub struktury, w której konstrukcja występuje. W związku z tym base.I
i base[E]
odpowiadają this.I
i this[E]
, z wyjątkiem tego, że this
jest postrzegane jako wystąpienie klasy bazowej.
Gdy base_access odwołuje się do wirtualnego członka funkcji (metody, właściwości lub indeksatora), ustalenie członka funkcji do wywołania w czasie wykonywania (§12.6.6) ulega zmianie. Element członkowski funkcji wywoływany jest określany przez znalezienie najbardziej pochodnej implementacji (§15.6.4) elementu członkowskiego funkcji w odniesieniu do B
(zamiast w odniesieniu do typu czasu wykonywania this
, jak zwykle w dostępie niepodstawowym). W związku z tym w ramach przesłonięcia elementu członkowskiego funkcji wirtualnej można użyć base_access do wywołania dziedziczonej implementacji elementu członkowskiego funkcji. Jeśli element członkowski funkcji, do którego odwołuje się base_access, jest abstrakcyjny, pojawi się błąd czasu wiązania.
Uwaga: w przeciwieństwie do
this
base
nie jest wyrażeniem samym w sobie. Jest to słowo kluczowe używane tylko w kontekście base_access lub constructor_initializer (§15.11.2). uwaga końcowa
12.8.16 Operatory przyrostku i dekrementacji
post_increment_expression
: primary_expression '++'
;
post_decrement_expression
: primary_expression '--'
;
Operand operacji przyrostka lub dekrementacji jest wyrażeniem sklasyfikowanym jako zmienna, dostęp do właściwości lub dostęp indeksatora. Wynikiem działania jest wartość tego samego typu co operandu.
Jeśli primary_expression ma typ czasu kompilacji dynamic
operator jest dynamicznie powiązany (§12.3.3), post_increment_expression lub post_decrement_expression ma typ czasu kompilacji dynamic
, a następujące reguły są stosowane w czasie wykonywania przy użyciu typu czasu wykonywania primary_expression.
Jeśli operand operacji postfiksowej inkrementacji lub dekrementacji jest właściwością lub indeksatorem, właściwość lub indeksator musi mieć zarówno metodę get, jak i metodę set. Jeśli tak nie jest, wystąpi błąd powiązania czasowego.
Rozpoznawanie przeciążenia operatora jednoargumentowego (§12.4.4) jest stosowane w celu wybrania określonej implementacji operatora. Istnieją wstępnie zdefiniowane operatory ++
i --
dla następujących typów: sbyte
, byte
, short
, ushort
, int
, uint
, long
, ulong
, char
, float
, double
, decimal
i dowolnego typu wyliczeniowego. Wstępnie zdefiniowane operatory ++
zwracają wartość wygenerowaną przez dodanie 1
do operandu, a wstępnie zdefiniowane operatory --
zwracają wartość wygenerowaną przez odjęcie 1
z operandu. Jeśli wynik tego dodawania lub odejmowania jest poza zakresem typu rezultatu, a typ wyniku jest typem całkowitym lub typem wyliczenia, w kontekście sprawdzanym, zgłaszany jest System.OverflowException
.
Istnieje niejawna konwersja z typu zwrotnego wybranego operatora jednoargumentowego na typ primary_expression; w przeciwnym razie wystąpi błąd podczas kompilacji.
Przetwarzanie podczas wykonywania operacji przyrostu lub dekrementu postfiksowego formy x++
lub x--
składa się z następujących kroków:
- Jeśli
x
jest klasyfikowana jako zmienna:-
x
jest obliczana w celu utworzenia zmiennej. - Wartość
x
jest zapisana. - Zapisana wartość
x
jest konwertowana na typ operandu wybranego operatora, a operator jest wywoływany z tą wartością jako argumentem. - Wartość zwracana przez operatora jest konwertowana na typ
x
i przechowywana w lokalizacji podanej przez wcześniejszą ocenęx
. - Zapisana wartość
x
staje się wynikiem operacji.
-
- Jeśli
x
jest klasyfikowana jako właściwość lub dostęp indeksatora:- Wyrażenie wystąpienia (jeśli
x
nie jeststatic
) oraz lista argumentów (jeślix
jest dostępem indeksatora) skojarzona zx
są ewaluowane, a ich wyniki są następnie używane w kolejnych wywołaniach metod get i set. - Metoda pobierania
x
jest wywoływana, a zwrócona wartość jest zapisywana. - Zapisana wartość
x
jest konwertowana na typ operandu wybranego operatora, a operator jest wywoływany z tą wartością jako argumentem. - Wartość zwracana przez operatora jest konwertowana na typ
x
, a akcesor ustawiającyx
jest wywoływany z tą wartością przekazaną jako argument. - Zapisana wartość
x
staje się wynikiem operacji.
- Wyrażenie wystąpienia (jeśli
Operatory ++
i --
obsługują również notację prefiksów (§12.9.6). Wynikiem x++
lub x--
jest wartość x
przed operacji, natomiast wynikiem ++x
lub --x
jest wartość x
po operacji. W obu przypadkach sama x
ma tę samą wartość po operacji.
Implementację operatora ++
lub operatora --
można wywołać, używając notacji postfiksowej lub prefiksowej. Nie można mieć oddzielnych implementacji operatorów dla dwóch notacji.
12.8.17 Nowy operator
12.8.17.1 Ogólne
Operator new
służy do tworzenia nowych wystąpień typów.
Istnieją trzy formy nowych wyrażeń:
- Wyrażenia tworzenia obiektów i anonimowe wyrażenia tworzenia obiektów są używane do tworzenia nowych wystąpień typów klas i typów wartości.
- Wyrażenia tworzenia tablic są używane do tworzenia nowych wystąpień typów tablic.
- Wyrażenia tworzenia delegata są używane do uzyskania wystąpień typu delegata.
Operator new
oznacza utworzenie wystąpienia typu, ale niekoniecznie oznacza alokację pamięci. W szczególności wystąpienia typów wartości nie wymagają dodatkowej pamięci poza zmiennymi, w których się znajdują, i nie występują alokacje, gdy new
jest używana do tworzenia wystąpień typów wartości.
Uwaga: wyrażenia tworzenia delegata nie zawsze prowadzą do utworzenia nowych instancji. Gdy wyrażenie jest przetwarzane w taki sam sposób jak konwersja grupy metod (§10.8) lub konwersja funkcji anonimowej (§10.7) może to spowodować ponowne użycie istniejącego wystąpienia delegata. uwaga końcowa
12.8.17.2 Wyrażenia tworzenia obiektów
wyrażenie tworzenia obiektu służy do tworzenia nowej instancji typu klasy lub typu wartości.
object_creation_expression
: 'new' type '(' argument_list? ')' object_or_collection_initializer?
| 'new' type object_or_collection_initializer
;
object_or_collection_initializer
: object_initializer
| collection_initializer
;
Wyrażenie tworzenia obiektu typu musi być typu klasa, wartośćlub parametr typu. Typ nie może być tuple_type ani abstrakcyjnym ani statycznym class_type.
Opcjonalne argument_list (§12.6.2) jest dozwolone tylko wtedy, gdy typ jest class_type lub struct_type.
Wyrażenie tworzenia obiektu może pominąć listę argumentów konstruktora i otaczające nawiasy, pod warunkiem, że zawiera inicjator obiektu lub inicjator kolekcji. Pominięcie listy argumentów konstruktora i ujęcie nawiasów jest równoważne określeniu pustej listy argumentów.
Przetwarzanie wyrażenia tworzenia obiektu, które zawiera inicjator obiektu lub inicjator kolekcji składa się z pierwszego przetwarzania konstruktora wystąpienia, a następnie przetwarzania inicjalizacji elementu członkowskiego lub elementu określonego przez inicjator obiektu (§12.8.17.3) lub inicjator kolekcji (§12.8.17.4).
Jeśli którykolwiek z argumentów w opcjonalnym argument_list ma typ kompilacji dynamic
, wówczas object_creation_expression jest dynamicznie powiązany (§12.3.3) i stosuje się następujące reguły w czasie wykonywania, używając typu wykonywania tych argumentów argument_list, które mają typ kompilacji dynamic
. Jednak tworzenie obiektu jest poddawane ograniczonej kontroli czasu kompilacji zgodnie z opisem w §12.6.5.
Przetwarzanie w czasie powiązania object_creation_expression w formie nowego T(A)
, gdzie T
jest typem klasylub typem wartości, a A
jest opcjonalną listą argumentów, składa się z szeregu kroków:
- Jeśli
T
jest value_type iA
nie jest obecny:-
object_creation_expression jest wywołaniem konstruktora domyślnego. Wynikiem object_creation_expression jest wartość typu
T
, innymi słowy, wartość domyślna dlaT
zdefiniowana w §8.3.3.
-
object_creation_expression jest wywołaniem konstruktora domyślnego. Wynikiem object_creation_expression jest wartość typu
- W przeciwnym razie, jeśli
T
jest type_parameter iA
nie jest obecny:- Jeśli nie określono żadnego ograniczenia typu wartości lub ograniczenia konstruktora (§15.2.5) dla
T
występuje błąd czasu powiązania. - Wynikiem object_creation_expression jest wartość typu czasu wykonywania, z którą został powiązany parametr typu, a mianowicie wynik wywołania domyślnego konstruktora tego typu. Typ czasu wykonywania może być typem odwołania lub typem wartości.
- Jeśli nie określono żadnego ograniczenia typu wartości lub ograniczenia konstruktora (§15.2.5) dla
- W przeciwnym razie, jeśli
T
jest class_type lub struct_type:- Jeśli
T
jest abstrakcyjnym lub statycznym class_type, wystąpi błąd kompilacji. - Konstruktor wystąpienia do wywołania jest określany przy użyciu reguł rozpoznawania przeciążenia §12.6.4. Zbiór kandydackich konstruktorów instancji składa się ze wszystkich dostępnych konstruktorów instancji zadeklarowanych w
T
, które są stosowalne względem A (§12.6.4.2). Jeśli zestaw konstruktorów instancji kandydatów jest pusty lub nie można zidentyfikować jednego najlepszego konstruktora instancji, wystąpi błąd czasu wiązania. - Wynikiem object_creation_expression jest wartość typu
T
, czyli wartość wygenerowana przez wywołanie konstruktora wystąpienia określonego w powyższym kroku. - W przeciwnym razie object_creation_expression jest nieprawidłowy i występuje błąd w czasie wiązania.
- Jeśli
Nawet jeśli object_creation_expression jest dynamicznie powiązana, typ czasu kompilacji jest nadal T
.
Przetwarzanie w czasie wykonywania object_creation_expression formularza nowego T(A)
, gdzie T
jest class_type lub struct_type i A
jest opcjonalnym argument_list, składa się z następujących kroków:
- Jeśli
T
jest typ_klasy:- Przydzielane jest nowe wystąpienie klasy
T
. Jeśli nie ma wystarczającej ilości dostępnej pamięci do przydzielenia nowego wystąpienia, zostanie zgłoszonySystem.OutOfMemoryException
i żadne dalsze kroki nie zostaną wykonane. - Wszystkie pola nowego wystąpienia są inicjowane do ich wartości domyślnych (§9.3).
- Konstruktor instancji jest wywoływany zgodnie z regułami wywoływania członków funkcji (§12.6.6). Odwołanie do nowo przydzielonego wystąpienia jest automatycznie przekazywane do konstruktora wystąpienia i można uzyskać do niego dostęp z tego konstruktora jako this.
- Przydzielane jest nowe wystąpienie klasy
- Jeśli
T
jest strukturą typu:- Wystąpienie typu
T
jest tworzone przez przydzielanie tymczasowej zmiennej lokalnej. Ponieważ konstruktor instancji struktury typu jest wymagany, aby przypisać jednoznaczną wartość do każdego pola tworzonej instancji, nie jest konieczne inicjowanie zmiennej tymczasowej. - Konstruktor instancji jest wywoływany zgodnie z regułami wywoływania członków funkcji (§12.6.6). Odwołanie do nowo przydzielonego wystąpienia jest automatycznie przekazywane do konstruktora wystąpienia i można uzyskać do niego dostęp z tego konstruktora jako this.
- Wystąpienie typu
12.8.17.3 Inicjatory obiektów
Inicjator obiektu określa wartości dla zera lub większej liczby pól, właściwości lub indeksowanych elementów obiektu.
object_initializer
: '{' member_initializer_list? '}'
| '{' member_initializer_list ',' '}'
;
member_initializer_list
: member_initializer (',' member_initializer)*
;
member_initializer
: initializer_target '=' initializer_value
;
initializer_target
: identifier
| '[' argument_list ']'
;
initializer_value
: expression
| object_or_collection_initializer
;
Inicjator obiektu składa się z sekwencji inicjatorów składowych, ujętej w tokeny {
i }
i rozdzielone przecinkami. Każdy member_initializer powinien wyznaczać cel inicjalizacji. Identyfikator określa dostępne pole lub właściwość obiektu inicjowanego, natomiast argument_list ujęte w nawiasy kwadratowe określa argumenty dostępnego indeksatora na inicjowanym obiekcie. Błędem inicjatora obiektu jest zawarcie więcej niż jednego inicjatora składowego dla tego samego pola lub właściwości.
Uwaga: Chociaż inicjator obiektu nie może ustawić tego samego pola lub właściwości więcej niż raz, nie ma takich ograniczeń dla indeksatorów. Inicjator obiektu może zawierać wiele obiektów docelowych inicjatora odwołujące się do indeksatorów, a nawet wielokrotnie używać tych samych argumentów indeksatora. uwaga końcowa
Po każdym initializer_target następuje znak równości oraz wyrażenie, inicjalizator obiektu lub inicjalizator kolekcji. Nie jest możliwe, aby wyrażenia w inicjatorze obiektu odwoływały się do nowo utworzonego obiektu, który inicjuje.
Inicjalizator członkowski, określający wyrażenie po znaku równości, jest przetwarzany tak samo jak przypisanie (§12.21.2) do obiektu docelowego.
Inicjalizator składowy, który określa inicjalizację obiektu po znaku równości, jest zagnieżdżonym inicjalizatorem obiektu , tj. inicjalizacją obiektu osadzonego. Zamiast przypisywać nową wartość do pola lub właściwości, przypisania w zagnieżdżonym inicjatorze obiektów są traktowane jako przypisania do elementów członkowskich pola lub właściwości. Zagnieżdżone inicjatory obiektów nie mogą być stosowane do właściwości z typem wartościowym lub do pól tylko do odczytu z typem wartościowym.
Inicjalizator członkowski, który określa inicjalizator kolekcji za pomocą znaku równości, jest inicjalizacją osadzonej kolekcji. Zamiast przypisywać nową kolekcję do pola docelowego, właściwości lub indeksatora, elementy podane w inicjatorze są dodawane do kolekcji przywoływanej przez element docelowy. Celem jest typ kolekcji spełniający wymagania określone w §12.8.17.4.
Gdy docelowy element inicjalizatora odwołuje się do indeksatora, argumenty tego indeksatora powinny być oceniane dokładnie raz. W związku z tym, nawet jeśli argumenty nigdy nie są używane (np. z powodu pustego inicjatora zagnieżdżonego), są oceniane pod kątem ich skutków ubocznych.
Przykład: Następująca klasa reprezentuje punkt z dwiema współrzędnymi:
public class Point { public int X { get; set; } public int Y { get; set; } }
Wystąpienie
Point
można utworzyć i zainicjować w następujący sposób:Point a = new Point { X = 0, Y = 1 };
Ma to taki sam efekt jak
Point __a = new Point(); __a.X = 0; __a.Y = 1; Point a = __a;
gdzie
__a
jest inaczej niewidoczną i niedostępną zmienną tymczasową.Poniższa klasa przedstawia prostokąt utworzony na podstawie dwóch punktów oraz tworzenie i inicjowanie wystąpienia
Rectangle
:public class Rectangle { public Point P1 { get; set; } public Point P2 { get; set; } }
Wystąpienie
Rectangle
można utworzyć i zainicjować w następujący sposób:Rectangle r = new Rectangle { P1 = new Point { X = 0, Y = 1 }, P2 = new Point { X = 2, Y = 3 } };
Ma to taki sam efekt jak
Rectangle __r = new Rectangle(); Point __p1 = new Point(); __p1.X = 0; __p1.Y = 1; __r.P1 = __p1; Point __p2 = new Point(); __p2.X = 2; __p2.Y = 3; __r.P2 = __p2; Rectangle r = __r;
gdzie
__r
,__p1
i__p2
są zmiennymi tymczasowymi, które w przeciwnym razie są niewidoczne i niedostępne.Jeśli konstruktor
Rectangle
przydziela dwa wystąpienia osadzone wPoint
, można je użyć do inicjowania osadzonych wystąpieńPoint
zamiast przypisywania nowych wystąpień.public class Rectangle { public Point P1 { get; } = new Point(); public Point P2 { get; } = new Point(); }
Następująca konstrukcja może służyć do inicjowania osadzonych wystąpień
Point
zamiast przypisywania nowych wystąpień:Rectangle r = new Rectangle { P1 = { X = 0, Y = 1 }, P2 = { X = 2, Y = 3 } };
Ma to taki sam efekt jak
Rectangle __r = new Rectangle(); __r.P1.X = 0; __r.P1.Y = 1; __r.P2.X = 2; __r.P2.Y = 3; Rectangle r = __r;
koniec przykładu
12.8.17.4 Inicjatory kolekcji
Inicjator kolekcji określa elementy kolekcji.
collection_initializer
: '{' element_initializer_list '}'
| '{' element_initializer_list ',' '}'
;
element_initializer_list
: element_initializer (',' element_initializer)*
;
element_initializer
: non_assignment_expression
| '{' expression_list '}'
;
expression_list
: expression
| expression_list ',' expression
;
Inicjator kolekcji to sekwencja inicjatorów elementów, otoczona przez tokeny {
i }
, i oddzielona przecinkami. Każdy inicjator elementu określa element do dodania do inicjowanego obiektu kolekcji i składa się z listy wyrażeń ujętych w {
i }
tokenów i rozdzielonych przecinkami. Inicjator elementu z jednym wyrażeniem może być zapisywany bez nawiasów klamrowych, ale nie może być wyrażeniem przypisania, aby uniknąć niejednoznaczności przy użyciu inicjatorów składowych. Produkcja non_assignment_expression jest zdefiniowana w §12.22.
Przykład: Poniżej przedstawiono przykład wyrażenia tworzenia obiektu zawierającego inicjator kolekcji:
List<int> digits = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
koniec przykładu
Obiekt kolekcji, do którego zastosowano inicjator kolekcji, musi być typu implementującego System.Collections.IEnumerable
, w przeciwnym razie wystąpi błąd czasu kompilacji. Dla każdego określonego elementu w kolejności od lewej do prawej jest stosowane standardowe wyszukiwanie składowych w celu znalezienia składowej o nazwie Add
. Jeśli wynik wyszukiwania elementu członkowskiego nie jest grupą metod, pojawi się błąd czasu kompilacji. W przeciwnym razie rozwiązanie przeciążenia jest stosowane z listą wyrażeń inicjatora elementu jako listy argumentów, a inicjator kolekcji wywołuje wynikową metodę. W związku z tym obiekt kolekcji zawiera odpowiednie wystąpienie lub metodę rozszerzenia o nazwie Add
dla każdego inicjatora elementu.
Przykład:Poniżej przedstawiono klasę reprezentującą kontakt z nazwą i listą numerów telefonów oraz tworzenie i inicjowanie
List<Contact>
:public class Contact { public string Name { get; set; } public List<string> PhoneNumbers { get; } = new List<string>(); } class A { static void M() { var contacts = new List<Contact> { new Contact { Name = "Chris Smith", PhoneNumbers = { "206-555-0101", "425-882-8080" } }, new Contact { Name = "Bob Harris", PhoneNumbers = { "650-555-0199" } } }; } }
który ma taki sam efekt jak
var __clist = new List<Contact>(); Contact __c1 = new Contact(); __c1.Name = "Chris Smith"; __c1.PhoneNumbers.Add("206-555-0101"); __c1.PhoneNumbers.Add("425-882-8080"); __clist.Add(__c1); Contact __c2 = new Contact(); __c2.Name = "Bob Harris"; __c2.PhoneNumbers.Add("650-555-0199"); __clist.Add(__c2); var contacts = __clist;
gdzie
__clist
,__c1
i__c2
są zmiennymi tymczasowymi, które w przeciwnym razie są niewidoczne i niedostępne.koniec przykładu
12.8.17.5 Wyrażenia tworzenia tablicy
Wyrażenie array_creation_expression służy do utworzenia nowego wystąpienia typu array_type.
array_creation_expression
: 'new' non_array_type '[' expression_list ']' rank_specifier*
array_initializer?
| 'new' array_type array_initializer
| 'new' rank_specifier array_initializer
;
Wyrażenie tworzenia tablicy pierwszej formy przydziela instancję tablicy typu, który jest wynikiem usunięcia każdego z poszczególnych wyrażeń z listy wyrażeń.
Przykład: wyrażenie tworzenia tablicy
new int[10,20]
generuje wystąpienie tablicy typuint[,]
, a wyrażenie tworzenia nowej tablicyint[10][,]
generuje wystąpienie tablicy typuint[][,]
. koniec przykładu
Każde wyrażenie na liście wyrażeń ma typ int
, uint
, long
lub ulong
lub niejawnie konwertowane na co najmniej jeden z tych typów. Wartość każdego wyrażenia określa długość odpowiedniego wymiaru w nowo przydzielonym wystąpieniu tablicy. Ponieważ długość wymiaru tablicy musi być nieujemna, wyrażenie stałe z wartością ujemną na liście wyrażeń stanowi błąd czasu kompilacji.
Z wyjątkiem niebezpiecznego kontekstu (§23.2), układ tablic jest nieokreślony.
Jeśli wyrażenie tworzenia tablicy pierwszego formularza zawiera inicjator tablicy, każde wyrażenie na liście wyrażeń jest stałą, a ranga i długość wymiarów określona przez listę wyrażeń jest zgodna z wartościami inicjatora tablicy.
W wyrażeniu tworzenia tablicy drugiej lub trzeciej formy wymiar określonego typu tablicy lub specyfikatora wymiaru musi odpowiadać wymiarowi inicjalizatora tablicy. Indywidualne długości wymiarów są wnioskowane na podstawie liczby elementów w każdym z odpowiednich poziomów zagnieżdżenia inicjalizatora tablicy. W związku z tym wyrażenie inicjujące w następującej deklaracji
var a = new int[,] {{0, 1}, {2, 3}, {4, 5}};
dokładnie odpowiada
var a = new int[3, 2] {{0, 1}, {2, 3}, {4, 5}};
Wyrażenie tworzenia tablicy trzeciego typu jest określane jako niejawnie wpisane wyrażenie tworzenia tablicy . Jest to podobne do drugiej formy, z tą różnicą, że typ elementu tablicy nie jest jawnie podany, lecz określany jako najlepszy wspólny typ (§12.6.3.15) zestawu wyrażeń w inicjatorze tablicy. W przypadku tablicy wielowymiarowej, tj. jednej, w której rank_specifier zawiera co najmniej jeden przecinek, ten zestaw składa się ze wszystkich wyrażeń znalezionych w zagnieżdżonych array_initializers.
Inicjalizatory tablic zostały opisane w §17.7.
Wynik oceny wyrażenia tworzenia tablicy jest klasyfikowany jako wartość, a mianowicie odwołanie do nowo przydzielonego wystąpienia tablicy. Przetwarzanie w czasie wykonywania wyrażenia tworzenia tablicy składa się z następujących kroków:
- Wyrażenia dotyczące długości wymiarów expression_list są obliczane w kolejności od lewej do prawej. Po ocenie każdego wyrażenia wykonywana jest niejawna konwersja (§10.2) do jednego z następujących typów:
int
,uint
,long
,ulong
. Pierwszy typ na tej liście, dla którego istnieje niejawna konwersja, jest wybierany. Jeśli ocena wyrażenia lub kolejnej niejawnej konwersji powoduje wyjątek, żadne dalsze wyrażenia nie są oceniane i nie są wykonywane żadne dalsze kroki. - Obliczone wartości długości wymiarów są weryfikowane w następujący sposób: Jeśli co najmniej jedna z wartości jest mniejsza niż zero, zostanie zgłoszony
System.OverflowException
i nie zostaną wykonane żadne dalsze kroki. - Przydzielone jest wystąpienie tablicy o podanej długości wymiarów. Jeśli nie ma wystarczającej ilości dostępnej pamięci do przydzielenia nowego wystąpienia, zostanie zgłoszony
System.OutOfMemoryException
i żadne dalsze kroki nie zostaną wykonane. - Wszystkie elementy nowego wystąpienia tablicy są inicjowane do wartości domyślnych (§9.3).
- Jeśli wyrażenie tworzenia tablicy zawiera inicjator tablicy, każde wyrażenie w inicjatorze tablicy jest oceniane i przypisywane do odpowiadającego mu elementu tablicy. Ewaluacje i przypisania są wykonywane w kolejności zapisywania wyrażeń w inicjalizatorze tablicy — innymi słowy, elementy są inicjowane w kolejności rosnących indeksów, z rosnącym najpierw wymiarem najbardziej na prawo. Jeśli ocena danego wyrażenia lub późniejszego przypisania do odpowiedniego elementu tablicy powoduje wyjątek, nie zostaną zainicjowane żadne dalsze elementy (a pozostałe elementy będą w ten sposób miały wartości domyślne).
Wyrażenie tworzenia tablicy umożliwia utworzenie instancji tablicy z elementami typu tablicy, ale elementy takiej tablicy są inicjowane ręcznie.
Przykład: Oświadczenie
int[][] a = new int[100][];
tworzy tablicę jednowymiarową z 100 elementami typu
int[]
. Początkowa wartość każdego elementu tonull
. Nie można jednocześnie utworzyć tablicy i zainicjować podtablicy, a instrukcjaint[][] a = new int[100][5]; // Error
powoduje wystąpienie błędu czasu kompilacji. Zamiast tego można ręcznie zainicjować podtablice, tak jak w
int[][] a = new int[100][]; for (int i = 0; i < 100; i++) { a[i] = new int[5]; }
koniec przykładu
Uwaga: Jeśli tablica tablic ma kształt „prostokątny”, to znaczy, że gdy podtablice mają taką samą długość, bardziej wydajne jest użycie tablicy wielowymiarowej. W powyższym przykładzie utworzenie tablicy tablic generuje 101 obiektów — jedną zewnętrzną tablicę i 100 podrzędnych tablic. Natomiast
int[,] a = new int[100, 5];
Tworzy tylko pojedynczy obiekt, tablicę dwuwymiarową i wykonuje alokację w jednej instrukcji.
uwaga końcowa
Przykładowy: Poniżej przedstawiono przykłady niejawnie typowanych wyrażeń tworzenia tablic:
var a = new[] { 1, 10, 100, 1000 }; // int[] var b = new[] { 1, 1.5, 2, 2.5 }; // double[] var c = new[,] { { "hello", null }, { "world", "!" } }; // string[,] var d = new[] { 1, "one", 2, "two" }; // Error
Ostatnie wyrażenie powoduje błąd czasu kompilacji, ponieważ ani
int
, anistring
nie są niejawnie konwertowane jedne na drugie, więc nie ma najlepszego wspólnego typu. W tym przypadku należy użyć jawnie wpisanego wyrażenia tworzenia tablicy, na przykład określając typ, który ma byćobject[]
. Alternatywnie jeden z elementów można rzutować do wspólnego typu podstawowego, który następnie stałby się wywnioskowanym typem elementu.koniec przykładu
Niejawnie wpisane wyrażenia tworzenia tablicy można łączyć z inicjatorami obiektów anonimowych (§12.8.17.7) w celu utworzenia anonimowo typiowanych struktur danych.
Przykład:
var contacts = new[] { new { Name = "Chris Smith", PhoneNumbers = new[] { "206-555-0101", "425-882-8080" } }, new { Name = "Bob Harris", PhoneNumbers = new[] { "650-555-0199" } } };
koniec przykładu
12.8.17.6 Wyrażenia tworzenia delegata
delegate_creation_expression służy do uzyskiwania wystąpienia delegate_type.
delegate_creation_expression
: 'new' delegate_type '(' expression ')'
;
Argumentem wyrażenia tworzenia delegata może być grupa metod, funkcja anonimowa lub wartość typu ustalanego w czasie kompilacji dynamic
albo typu delegata . Jeśli argument jest grupą metod, identyfikuje metodę i, w przypadku metody instancji, obiekt, dla którego ma zostać utworzony delegat. Jeśli argument jest funkcją anonimową, bezpośrednio definiuje parametry i treść metody obiektu docelowego delegata. Jeśli argument jest wartością, identyfikuje wystąpienie delegata, z którego ma zostać utworzona kopia.
Jeśli wyrażenie ma typ czasu kompilacji dynamic
, wyrażenie tworzenia delegata jest dynamicznie powiązane (§12.8.17.6), a poniższe reguły są stosowane podczas wykonywania przy użyciu typu wykonawczego wyrażenia . W przeciwnym razie reguły są stosowane w czasie kompilacji.
Przetwarzanie w czasie powiązania delegate_creation_expression formularza nowego D(E)
, gdzie D
jest delegate_type, a E
jest wyrażeniem , składają się z następujących kroków:
Jeśli
E
jest grupą metod, wyrażenie tworzenia delegata jest przetwarzane w taki sam sposób jak konwersja grupy metod (§10.8) zE
doD
.Jeśli
E
jest funkcją anonimową, wyrażenie tworzenia delegata jest przetwarzane w taki sam sposób jak konwersja funkcji anonimowej (§10.7) zE
doD
.Jeśli
E
jest wartością,E
jest zgodna (§20,2) zD
, a wynikiem jest odwołanie do nowo utworzonego delegata z listą wywołań jednokrotnych, która wywołujeE
.
Przetwarzanie w czasie wykonywania wyrażenia tworzenia delegata o postaci new D(E)
, gdzie D
jest typu delegata , a E
jest wyrażeniem , składa się z następujących kroków:
- Jeśli
E
jest grupą metod, wyrażenie tworzenia delegata jest oceniane jako konwersja grupy metod (§10.8) zE
doD
. - Jeśli
E
jest funkcją anonimową, tworzenie delegata jest oceniane jako anonimowa konwersja funkcji zE
naD
(§10.7). - Jeśli
E
jest wartością typu delegata :-
E
jest oceniana. Jeśli ta ocena powoduje wyjątek, nie są wykonywane żadne dalsze kroki. - Jeśli wartość
E
jest równanull
, zostanie wyrzuconySystem.NullReferenceException
i żadne dalsze kroki nie zostaną wykonane. - Przydzielone jest nowe wystąpienie typu delegata
D
. Jeśli nie ma wystarczającej ilości dostępnej pamięci do przydzielenia nowego wystąpienia, zostanie zgłoszonySystem.OutOfMemoryException
i żadne dalsze kroki nie zostaną wykonane. - Nowe wystąpienie delegata jest inicjowane przy użyciu listy wywołań jednokrotnych, która wywołuje
E
.
-
Lista wywołań delegata jest określana w momencie tworzenia jego wystąpienia i pozostaje niezmienna przez cały czas istnienia tego delegata. Innymi słowy, nie można zmienić docelowych jednostek wywołujących delegata po jego utworzeniu.
Uwaga: Pamiętaj, że gdy dwa delegaty są łączone lub jeden jest usuwany z innego, powstaje nowy delegat; żaden z istniejących delegatów nie zmienia swojej zawartości. uwaga końcowa
Nie można utworzyć delegata, który odwołuje się do właściwości, indeksatora, operatora zdefiniowanego przez użytkownika, konstruktora wystąpienia, finalizatora lub konstruktora statycznego.
Przykład: zgodnie z powyższym opisem, gdy delegat jest tworzony na podstawie grupy metod, lista parametrów i typ zwracany delegata określają, które z przeciążonych metod wybrać. W przykładzie
delegate double DoubleFunc(double x); class A { DoubleFunc f = new DoubleFunc(Square); static float Square(float x) => x * x; static double Square(double x) => x * x; }
pole
A.f
jest inicjowane za pomocą delegata, który odwołuje się do drugiej metodySquare
, ponieważ ta metoda dokładnie odpowiada liście parametrów i zwraca typDoubleFunc
. Gdyby druga metodaSquare
nie była obecna, wystąpił błąd czasu kompilacji.koniec przykładu
12.8.17.7 Anonimowe wyrażenia tworzenia obiektów
anonymous_object_creation_expression służy do tworzenia obiektu typu anonimowego.
anonymous_object_creation_expression
: 'new' anonymous_object_initializer
;
anonymous_object_initializer
: '{' member_declarator_list? '}'
| '{' member_declarator_list ',' '}'
;
member_declarator_list
: member_declarator (',' member_declarator)*
;
member_declarator
: simple_name
| member_access
| null_conditional_projection_initializer
| base_access
| identifier '=' expression
;
Inicjator obiektów anonimowych deklaruje typ anonimowy i zwraca wystąpienie tego typu. Typ anonimowy to typ klasy bez nazw, który dziedziczy bezpośrednio z object
. Elementy członkowskie typu anonimowego są sekwencją właściwości tylko do odczytu wywnioskowanych z inicjatora obiektu anonimowego używanego do tworzenia wystąpienia typu. W szczególności inicjator obiektu anonimowego formularza
new {
p₁=
e₁,
p₂=
e₂,
...
pv=
ev}
deklaruje anonimowy typ formularza
class __Anonymous1
{
private readonly «T1» «f1»;
private readonly «T2» «f2»;
...
private readonly «Tn» «fn»;
public __Anonymous1(«T1» «a1», «T2» «a2»,..., «Tn» «an»)
{
«f1» = «a1»;
«f2» = «a2»;
...
«fn» = «an»;
}
public «T1» «p1» { get { return «f1»; } }
public «T2» «p2» { get { return «f2»; } }
...
public «Tn» «pn» { get { return «fn»; } }
public override bool Equals(object __o) { ... }
public override int GetHashCode() { ... }
}
gdzie każdy «Tx» jest typem odpowiadającego wyrażenia «ex». Wyrażenie używane w member_declarator powinno mieć typ. W związku z tym jest to błąd czasu kompilacji wyrażenia w member_declarator, które jest null
lub funkcją anonimową.
Nazwy typu anonimowego i parametru do metody Equals
są generowane automatycznie przez kompilator i nie można odwoływać się do nich w tekście programu.
W ramach tego samego programu, dwa anonimowe inicjatory obiektów, które określają sekwencję właściwości o tych samych nazwach i typach określonych w czasie kompilacji w tej samej kolejności, spowodują utworzenie wystąpień tego samego typu anonimowego.
Przykład: w przykładzie
var p1 = new { Name = "Lawnmower", Price = 495.00 }; var p2 = new { Name = "Shovel", Price = 26.95 }; p1 = p2;
W ostatnim wierszu przypisanie jest dozwolone, ponieważ
p1
ip2
są takiego samego typu anonimowego.koniec przykładu
Metody Equals
i GetHashcode
dla typów anonimowych zastępują metody dziedziczone z object
i są zdefiniowane pod względem Equals
i GetHashcode
właściwości, tak aby dwa wystąpienia tego samego typu anonimowego są równe i tylko wtedy, gdy wszystkie ich właściwości są równe.
Deklarator składowy może być skrócony do prostej nazwy (§12.8.4), dostępu do składowej (§12.8.7), inicjator projekcji warunkowej o wartości null §12.8.8 lub dostępu podstawowego (§12.8.15). Nazywa się to inicjatorem projekcji oznaczonym jako i jest skrótem na deklarację i przypisanie do właściwości o tej samej nazwie. W szczególności deklaratory składowe formularzy
«identifier»
, «expr» . «identifier»
i «expr» ? . «identifier»
są dokładnie równoważne następującym, odpowiednio:
«identifer» = «identifier»
, «identifier» = «expr» . «identifier»
i «identifier» = «expr» ? . «identifier»
W związku z tym w inicjatorze projekcji identyfikator wybiera zarówno wartość, jak i pole lub właściwość, do której przypisano wartość. Intuicyjnie inicjator projekcji nie tylko przedstawia wartość, ale także nazwę tej wartości.
12.8.18 Operator typeof
Operator typeof
służy do uzyskiwania obiektu System.Type
dla typu.
typeof_expression
: 'typeof' '(' type ')'
| 'typeof' '(' unbound_type_name ')'
| 'typeof' '(' 'void' ')'
;
unbound_type_name
: identifier generic_dimension_specifier?
| identifier '::' identifier generic_dimension_specifier?
| unbound_type_name '.' identifier generic_dimension_specifier?
;
generic_dimension_specifier
: '<' comma* '>'
;
comma
: ','
;
Pierwsza forma typeof_expression składa się z typeof
słowa kluczowego, po którym następuje typ w nawiasach. Wynikiem wyrażenia tego formularza jest obiekt System.Type
dla wskazanego typu. Dla danego typu istnieje tylko jeden obiekt System.Type
. Oznacza to, że w przypadku typu T
wartość typeof(T) == typeof(T)
jest zawsze prawdziwa. Typ nie może być dynamic
.
Druga forma typeof_expression składa się ze słowa kluczowego typeof
, po którym następuje nawiasowa nazwa niepowiązanego typu.
Uwaga: unbound_type_name jest bardzo podobny do type_name (§7.8), z tą różnicą, że unbound_type_name zawiera generic_dimension_specifier, podczas gdy type_name zawiera type_argument_list. uwaga końcowa
Gdy operand typeof_expression jest sekwencją tokenów spełniającą gramatyki zarówno unbound_type_name, jak również type_name, czyli gdy nie zawiera ani generic_dimension_specifier ani type_argument_list, sekwencja tokenów jest uważana za type_name. Znaczenie unbound_type_name jest określane w następujący sposób:
- Przekonwertuj sekwencję tokenów na type_name, zastępując każdy generic_dimension_specifier listą type_argument_list, która ma taką samą liczbę przecinków i zawiera słowo kluczowe
object
, co każdy type_argument. - Oceń wynikowy type_name, ignorując wszystkie ograniczenia parametrów typu.
- unbound_type_name rozwiązywany jest jako niezwiązany typ ogólny skojarzony z wynikowym typem skonstruowanym (§8.4).
Nieprawidłowe jest, aby nazwa typu była typem odwołania dopuszczającym wartość null.
Wynikiem typeof_expression jest obiekt System.Type
dla wynikowego niezwiązanego typu ogólnego.
Trzecia forma typeof_expression składa się z słowa kluczowego typeof
, po którym następuje słowo kluczowe void
w nawiasach. Wynikiem wyrażenia tego formularza jest obiekt System.Type
, który reprezentuje brak typu. Obiekt typu zwracany przez typeof(void)
różni się od obiektu typu zwracanego dla dowolnego typu.
Uwaga: Ten specjalny obiekt
System.Type
jest przydatny w bibliotekach klas, które umożliwiają odbicie na metody w języku, gdzie te metody chcą reprezentować typ zwracany dowolnej metody, w tym metodyvoid
, z wystąpieniemSystem.Type
. uwaga końcowa
Operator typeof
może być używany na parametrze typu. Jest to błąd czasu kompilacji, jeśli nazwa typu jest znane jako typ referencyjny dopuszczający wartość null. Wynikiem jest obiekt System.Type
dla typu czasu wykonywania, który został powiązany z parametrem typu. Jeśli typ czasu wykonywania jest typem odwołania dopuszczanym do wartości null, wynik jest odpowiadającym niepustomym typem odwołania. Operator typeof
może być również stosowany do skonstruowanego typu lub typu ogólnego niepowiązanego (§8.4.4). Obiekt System.Type
dla typu ogólnego niezwiązanego nie jest taki sam jak obiekt System.Type
typu wystąpienia (§15.3.2). Typ wystąpienia jest zawsze zamkniętym typem skonstruowanym w czasie wykonywania, więc jego obiekt System.Type
zależy od używanych argumentów typu czasu wykonywania. Niezwiązany typ ogólny, z drugiej strony, nie ma argumentów typu i zwraca ten sam obiekt System.Type
niezależnie od argumentów typu środowiska uruchomieniowego.
Przykład: przykład
class X<T> { public static void PrintTypes() { Type[] t = { typeof(int), typeof(System.Int32), typeof(string), typeof(double[]), typeof(void), typeof(T), typeof(X<T>), typeof(X<X<T>>), typeof(X<>) }; for (int i = 0; i < t.Length; i++) { Console.WriteLine(t[i]); } } } class Test { static void Main() { X<int>.PrintTypes(); } }
tworzy następujące dane wyjściowe:
System.Int32 System.Int32 System.String System.Double[] System.Void System.Int32 X`1[System.Int32] X`1[X`1[System.Int32]] X`1[T]
Należy pamiętać, że
int
iSystem.Int32
są tego samego typu. Wyniktypeof(X<>)
nie zależy od argumentu typu, ale wyniktypeof(X<T>)
zależy.koniec przykładu
12.8.19 Operator sizeof
Operator sizeof
zwraca liczbę 8-bitowych bajtów zajmowanych przez zmienną danego typu. Typ określony jako operand dla sizeof musi być typu niezarządzanego (unmanaged_type) (§8.8).
sizeof_expression
: 'sizeof' '(' unmanaged_type ')'
;
W przypadku niektórych wstępnie zdefiniowanych typów operator sizeof
zwraca stałą wartość int
, jak pokazano w poniższej tabeli:
ekspresja | wynik |
---|---|
sizeof(sbyte) |
1 |
sizeof(byte) |
1 |
sizeof(short) |
2 |
sizeof(ushort) |
2 |
sizeof(int) |
4 |
sizeof(uint) |
4 |
sizeof(long) |
8 |
sizeof(ulong) |
8 |
sizeof(char) |
2 |
sizeof(float) |
4 |
sizeof(double) |
8 |
sizeof(bool) |
1 |
sizeof(decimal) |
16 |
W przypadku typu wyliczeniowego T
wynik wyrażenia sizeof(T)
jest stałą wartością równą rozmiarowi jego typu bazowego, jak podano powyżej. Dla wszystkich innych typów operandu operator sizeof
jest określony w §23.6.9.
12.8.20 Zaznaczone i niezaznaczone operatory
Operatory checked
i unchecked
służą do kontrolowania kontekstu sprawdzania przepełnienia dla operacji arytmetycznych i konwersji typu całkowitego.
checked_expression
: 'checked' '(' expression ')'
;
unchecked_expression
: 'unchecked' '(' expression ')'
;
Operator checked
oblicza zawarte wyrażenie w zaznaczonym kontekście, a operator unchecked
oblicza zawarte wyrażenie w nieznakowanym kontekście.
checked_expression lub unchecked_expression odpowiada dokładnie parenthesized_expression (§12.8.5), z wyjątkiem tego, że zawarte wyrażenie jest oceniane w danym kontekście sprawdzania przepełnienia.
Kontekst sprawdzania przepełnienia można również kontrolować za pomocą instrukcji checked
i unchecked
(§13.12).
Następujące operacje mają wpływ na kontekst sprawdzania przepełnienia ustanowiony przez zaznaczone i niezaznaczone operatory i instrukcje:
- Wstępnie zdefiniowane operatory
++
i--
(§12.8.16 i §12.9.6), gdy operand jest typu całkowitego lub wyliczeniowego. - Operator jednoargumentowy
-
wstępnie zdefiniowany (§12.9.3), gdy operand jest typu całkowitego. - Wstępnie zdefiniowane operatory binarne
+
,-
,*
i/
(§12.10), gdy oba operandy są typami całkowitymi lub wyliczeniowymi. - Jawne konwersje liczbowe (§10.3.2) z jednego typu wyliczenia lub całkowitego do innego typu wyliczenia lub całkowitego albo z
float
lubdouble
do typu wyliczenia lub całkowitego.
Gdy jedna z powyższych operacji generuje wynik zbyt duży do reprezentowania w typie docelowym, kontekst, w którym wykonywana jest operacja, kontroluje wynikowe zachowanie:
- W kontekście
checked
, jeśli operacja jest wyrażeniem stałym (§12.23), występuje błąd czasu kompilacji. W przeciwnym razie, gdy operacja jest wykonywana w trakcie działania,System.OverflowException
zostanie zgłoszony. - W kontekście
unchecked
wynik jest obcinany przez odrzucenie wszystkich bitów o wysokiej kolejności, które nie mieszczą się w typie docelowym.
W przypadku wyrażeń niestałych (§12.23) (wyrażenia, które są oceniane w czasie wykonywania), które nie są ujęte w żadne operatory checked
lub unchecked
ani instrukcje, domyślny kontekst sprawdzania przepełnienia jest wyłączony, chyba że czynniki zewnętrzne (takie jak przełączniki kompilatora i konfiguracja środowiska wykonawczego) wymagają sprawdzenia.
W przypadku wyrażeń stałych (§12.23) (wyrażenia, które mogą być w pełni oceniane w czasie kompilacji), domyślny kontekst sprawdzania przepełnienia jest zawsze sprawdzany. O ile wyrażenie stałe nie zostanie wyraźnie ustawione w kontekście unchecked
, przepełnienia występujące podczas oceny kompilacji wyrażenia zawsze powodują błędy czasu kompilacji.
Ciało funkcji anonimowej nie jest wpływane przez konteksty checked
lub unchecked
, w których występuje funkcja anonimowa.
Przykład: w poniższym kodzie
class Test { static readonly int x = 1000000; static readonly int y = 1000000; static int F() => checked(x * y); // Throws OverflowException static int G() => unchecked(x * y); // Returns -727379968 static int H() => x * y; // Depends on default }
nie są zgłaszane żadne błędy czasu kompilacji, ponieważ żadne z wyrażeń nie może być oceniane w czasie kompilacji. W czasie wykonywania metoda
F
zgłaszaSystem.OverflowException
, a metodaG
zwraca wartość –727379968 (niższe 32 bity wyniku poza zakresem). Zachowanie metodyH
zależy od domyślnego kontekstu sprawdzania przepełnienia podczas kompilacji; jest to albo takie same jakF
, albo takie same jakG
.koniec przykładu
Przykład: w poniższym kodzie
class Test { const int x = 1000000; const int y = 1000000; static int F() => checked(x * y); // Compile-time error, overflow static int G() => unchecked(x * y); // Returns -727379968 static int H() => x * y; // Compile-time error, overflow }
Przepełnienia, które występują podczas oceniania wyrażeń stałych w
F
iH
, powodują błędy kompilacji, ponieważ wyrażenia te są oceniane w kontekściechecked
. Przepełnienie występuje również podczas obliczania wyrażenia stałego wG
, ale ponieważ ocena odbywa się w kontekścieunchecked
, przepełnienie nie jest zgłaszane.koniec przykładu
Operatory checked
i unchecked
mają wpływ tylko na kontekst sprawdzania przepełnienia dla tych operacji, które są tekstowo zawarte w tokenach "(
" i ")
". Operatory nie mają wpływu na elementy członkowskie funkcji, które są wywoływane w wyniku oceny zawartego wyrażenia.
Przykład: w poniższym kodzie
class Test { static int Multiply(int x, int y) => x * y; static int F() => checked(Multiply(1000000, 1000000)); }
użycie
checked
w F nie ma wpływu na ocenęx * y
wMultiply
, dlategox * y
jest obliczany w domyślnym kontekście sprawdzania przepełnienia.koniec przykładu
Operator unchecked
jest wygodny podczas pisania stałych typów całkowitych ze znakiem w notacji szesnastkowej.
Przykład:
class Test { public const int AllBits = unchecked((int)0xFFFFFFFF); public const int HighBit = unchecked((int)0x80000000); }
Obie powyższe stałe szesnastkowe są typu
uint
. Ponieważ stałe znajdują się poza zakresemint
, bez operatoraunchecked
rzutowania doint
spowodowałyby błędy czasu kompilacji.koniec przykładu
Uwaga: operatory i instrukcje
checked
unchecked
umożliwiają programistom kontrolowanie wybranych aspektów obliczeń liczbowych. Jednak zachowanie niektórych operatorów liczbowych zależy od typów danych operandów. Na przykład pomnożenie dwóch liczb dziesiętnych zawsze powoduje wyjątek przepełnienia, nawet w jawnie niekontrolowanej konstrukcji. Podobnie, pomnożenie dwóch liczb zmiennoprzecinkowych nigdy nie powoduje wyjątku z powodu przepełnienia, nawet w jawnie sprawdzanej konstrukcji. Ponadto inne operatory nigdy nie mają wpływu na tryb sprawdzania, czy jest to ustawienie domyślne, czy jawne. uwaga końcowa
12.8.21 Wyrażenia wartości domyślnej
Domyślne wyrażenie wartości służy do uzyskiwania wartości domyślnej (§9.3) typu.
default_value_expression
: explictly_typed_default
| default_literal
;
explictly_typed_default
: 'default' '(' type ')'
;
default_literal
: 'default'
;
default_literal reprezentuje wartość domyślną (§9.3). Nie ma określonego typu, ale można go przekonwertować na dowolny typ za pomocą domyślnej konwersji literału (§10.2.16).
Wynikiem default_value_expression jest wartość domyślna (§9.3) jawnego typu w explictly_typed_defaultlub typ docelowy konwersji dla default_value_expression.
default_value_expression jest wyrażeniem stałym (§12.23), jeśli typ jest jednym z:
- typ referencyjny
- parametr typu, który znany jest jako typ referencyjny (§8.2);
- jeden z następujących typów wartości:
sbyte
,byte
,short
,ushort
,int
,uint
,long
,ulong
,char
,float
,double
,decimal
,bool,
; lub - dowolny typ wyliczenia.
12.8.22 Alokacja stosu
Wyrażenie alokacji stosu przydziela blok pamięci ze stosu wykonywawczego. Stos wykonawczy to obszar pamięci, w którym są przechowywane zmienne lokalne. Stos wykonywania nie jest częścią zarządzanego sterta. Pamięć używana do przechowywania zmiennych lokalnych jest automatycznie odzyskiwane po powrocie bieżącej funkcji.
Reguły bezpiecznego kontekstu dla wyrażenia alokacji stosu są opisane w §16.4.12.7.
stackalloc_expression
: 'stackalloc' unmanaged_type '[' expression ']'
| 'stackalloc' unmanaged_type? '[' constant_expression? ']' stackalloc_initializer
;
stackalloc_initializer
: '{' stackalloc_initializer_element_list '}'
;
stackalloc_initializer_element_list
: stackalloc_element_initializer (',' stackalloc_element_initializer)* ','?
;
stackalloc_element_initializer
: expression
;
unmanaged_type (§8.8) wskazuje typ elementów, które będą przechowywane w nowo przydzielonej lokalizacji, a wyrażenie wskazuje liczbę tych elementów. Te elementy określają wymagany rozmiar alokacji. Typ wyrażenia jest niejawnie konwertowany na typ int
.
Ponieważ rozmiar alokacji stosu nie może być ujemny, zgłoszenie liczby elementów jako wyrażenia stałego constant_expression, które daje wynik ujemny, jest błędem w czasie kompilacji.
W czasie wykonywania, jeśli liczba elementów do przydzielenia jest wartością ujemną, zachowanie jest niezdefiniowane. Jeśli ma wartość zero, nie zostanie wykonana żadna alokacja, a zwracana wartość jest zdefiniowana przez implementację. Jeśli nie ma wystarczającej ilości dostępnej pamięci, aby przydzielić elementy, zostanie wyrzucony System.StackOverflowException
.
Gdy stackalloc_initializer jest obecny:
- Jeśli unmanaged_type zostanie pominięty, zostanie on wywnioskowany zgodnie z regułami dla najlepszego typu wspólnego (§12.6.3.15) dla zestawu stackalloc_element_initializers.
- Jeśli constant_expression zostanie pominięta, zostanie wywnioskowana liczba stackalloc_element_initializers.
- Jeśli constant_expression jest obecny, musi być równa liczbie stackalloc_element_initializers.
Każdy stackalloc_element_initializer będzie miał niejawną konwersję na unmanaged_type (§10.2). stackalloc_element_initializerinicjuje elementy w przydzielonej pamięci w kolejności rosnącej, zaczynając od elementu o indeksie zero. W przypadku braku stackalloc_initializerzawartość nowo przydzielonej pamięci jest niezdefiniowana.
Jeśli stackalloc_expression występuje bezpośrednio jako wyrażenie inicjujące local_variable_declaration (§13.6.2), gdzie local_variable_type jest albo typem wskaźnika (§23.3) lub wywnioskowany (var
), wówczas wynik stackalloc_expression jest wskaźnikiem typu T*
(§23.9). W takim przypadku stackalloc_expression musi występować w niebezpiecznym kodzie. W przeciwnym razie wynikiem stackalloc_expression jest wystąpienie typu Span<T>
, gdzie T
jest unmanaged_type:
-
Span<T>
(§C.3) jest typem struktury ref (§16.2.3), który przedstawia blok pamięci, tutaj blok przydzielony przez stackalloc_expression, jako kolekcji indeksowalnej typu (T
) elementów. - Właściwość
Length
wyniku zwraca liczbę przydzielonych elementów. - Indeksator wyniku (§15.9) zwraca odwołanie_do_zmiennej (§9.5) do elementu przydzielonego bloku i sprawdza zakres.
Inicjalizatory alokacji stosu nie są dozwolone w blokach catch
lub finally
(§13.11).
Uwaga: nie ma możliwości jawnego zwolnienia pamięci przydzielonej przy użyciu
stackalloc
. uwaga końcowa
Wszystkie bloki pamięci przydzielone do stosu utworzone podczas wykonywania elementu członkowskiego funkcji są automatycznie odrzucane po powrocie tego elementu członkowskiego funkcji.
Z wyjątkiem operatora stackalloc
, język C# nie udostępnia wstępnie zdefiniowanych konstrukcji do zarządzania pamięcią niezbieraną przez odśmiecacz. Takie usługi są zwykle udostępniane przez biblioteki klas pomocniczych lub importowane bezpośrednio z bazowego systemu operacyjnego.
Przykład:
// Memory uninitialized Span<int> span1 = stackalloc int[3]; // Memory initialized Span<int> span2 = stackalloc int[3] { -10, -15, -30 }; // Type int is inferred Span<int> span3 = stackalloc[] { 11, 12, 13 }; // Error; result is int*, not allowed in a safe context var span4 = stackalloc[] { 11, 12, 13 }; // Error; no conversion from Span<int> to Span<long> Span<long> span5 = stackalloc[] { 11, 12, 13 }; // Converts 11 and 13, and returns Span<long> Span<long> span6 = stackalloc[] { 11, 12L, 13 }; // Converts all and returns Span<long> Span<long> span7 = stackalloc long[] { 11, 12, 13 }; // Implicit conversion of Span<T> ReadOnlySpan<int> span8 = stackalloc int[] { 10, 22, 30 }; // Implicit conversion of Span<T> Widget<double> span9 = stackalloc double[] { 1.2, 5.6 }; public class Widget<T> { public static implicit operator Widget<T>(Span<double> sp) { return null; } }
W przypadku
span8
stackalloc
powodujeSpan<int>
, który jest konwertowany przez operator niejawny naReadOnlySpan<int>
. Podobnie dlaspan9
wynikowySpan<double>
jest konwertowany na typ zdefiniowany przez użytkownikaWidget<double>
przy użyciu konwersji, jak pokazano. koniec przykładu
12.8.23 Operator nameof
nameof_expression służy do uzyskiwania nazwy jednostki programu jako ciągu stałego.
nameof_expression
: 'nameof' '(' named_entity ')'
;
named_entity
: named_entity_target ('.' identifier type_argument_list?)*
;
named_entity_target
: simple_name
| 'this'
| 'base'
| predefined_type
| qualified_alias_member
;
Ponieważ nameof
nie jest słowem kluczowym, nameof_expression zawsze jest składniowo niejednoznaczny z wywołaniem prostej nazwy nameof
. Ze względów zgodności, jeśli wyszukiwanie nazw (§12.8.4) nazwy nameof
powiedzie się, wyrażenie jest traktowane jako invocation_expression — niezależnie od tego, czy wywołanie jest prawidłowe. W przeciwnym razie jest to nameof_expression.
Proste wyszukiwania nazw i dostępu do składowych są wykonywane w czasie kompilacji na named_entity, zgodnie z regułami opisanymi w §12.8.4 i §12.8.7. Jednak jeśli wyszukiwanie opisane w §12.8.4 i §12.8.7 powoduje błąd, ponieważ element członkowski wystąpienia został znaleziony w kontekście statycznym, nameof_expression nie generuje takiego błędu.
Jest to błąd czasu kompilacji dla named_entity wyznaczenia grupy metod, aby mieć type_argument_list. Jest to błąd czasu kompilacji dla named_entity_target, by miał typ dynamic
.
nameof_expression jest stałym wyrażeniem typu string
i nie ma wpływu na środowisko uruchomieniowe. W szczególności jego named_entity nie jest oceniana i jest ignorowana do celów analizy określonego przypisania (§9.4.4.22). Jego wartość jest ostatnim identyfikatorem named_entity przed opcjonalną końcową listą argumentów typu , przekształconym w następujący sposób:
- Prefiks "
@
", jeśli jest używany, zostanie usunięty. - Każda sekwencja unikania Unicode jest przekształcana w odpowiadający znak Unicode.
- Wszystkie znaki formatujące są usuwane.
Są to te same przekształcenia stosowane w §6.4.3 podczas testowania równości między identyfikatorami.
Przykład: Poniżej przedstawiono wyniki różnych wyrażeń
nameof
, w założeniu, że typ ogólnyList<T>
jest zadeklarowany w przestrzeni nazwSystem.Collections.Generic
:using TestAlias = System.String; class Program { static void Main() { var point = (x: 3, y: 4); string n1 = nameof(System); // "System" string n2 = nameof(System.Collections.Generic); // "Generic" string n3 = nameof(point); // "point" string n4 = nameof(point.x); // "x" string n5 = nameof(Program); // "Program" string n6 = nameof(System.Int32); // "Int32" string n7 = nameof(TestAlias); // "TestAlias" string n8 = nameof(List<int>); // "List" string n9 = nameof(Program.InstanceMethod); // "InstanceMethod" string n10 = nameof(Program.GenericMethod); // "GenericMethod" string n11 = nameof(Program.NestedClass); // "NestedClass" // Invalid // string x1 = nameof(List<>); // Empty type argument list // string x2 = nameof(List<T>); // T is not in scope // string x3 = nameof(GenericMethod<>); // Empty type argument list // string x4 = nameof(GenericMethod<T>); // T is not in scope // string x5 = nameof(int); // Keywords not permitted // Type arguments not permitted for method group // string x6 = nameof(GenericMethod<Program>); } void InstanceMethod() { } void GenericMethod<T>() { string n1 = nameof(List<T>); // "List" string n2 = nameof(T); // "T" } class NestedClass { } }
Potencjalnie zaskakujące części tego przykładu to mapowanie
nameof(System.Collections.Generic)
do „Generic” zamiast pełnej przestrzeni nazw oraznameof(TestAlias)
do „TestAlias”, a nie „String”. koniec przykładu
12.8.24 Wyrażenia metody anonimowej
anonimowe_wyrażenie_metodowe jest jednym z dwóch sposobów definiowania funkcji anonimowej. Zostały one opisane w §12.19.
12.9 Operatory jednoargumentowe
12.9.1 Ogólne
Operatory +
, -
, !
(tylko negacja logiczna §12.9.4), ~
, ++
, --
, rzutowanie i await
to operatory jednoargumentowe.
Uwaga: operator postfix o wartości null-forgiving (§12.8.9),
!
, ze względu na jego charakterystykę kompilacji oraz brak możliwości przeciążania, jest wykluczony z powyższej listy. uwaga końcowa
unary_expression
: primary_expression
| '+' unary_expression
| '-' unary_expression
| logical_negation_operator unary_expression
| '~' unary_expression
| pre_increment_expression
| pre_decrement_expression
| cast_expression
| await_expression
| pointer_indirection_expression // unsafe code support
| addressof_expression // unsafe code support
;
pointer_indirection_expression (§23.6.2) i addressof_expression (§23.6.5) są dostępne tylko w niebezpiecznym kodzie (§23).
Jeśli operand unary_expression ma typ czasu kompilacji dynamic
, jest on dynamicznie powiązany (§12.3.3). W tym przypadku typ czasu kompilacji unary_expression jest dynamic
, a rozwiązanie opisane poniżej będzie odbywać się w czasie wykonywania przy użyciu typu czasu wykonywania operandu.
Operator jednoargumentowy plus 12.9.2
W przypadku operacji postaci +x
, rozpoznawanie przeciążenia operatora jednoargumentowego (§12.4.4) jest stosowane w celu wybrania konkretnej implementacji operatora. Operand jest konwertowany na typ parametru wybranego operatora, a typ wyniku jest zwracanym typem operatora. Wstępnie zdefiniowane operatory jednoargumentowe plus to:
int operator +(int x);
uint operator +(uint x);
long operator +(long x);
ulong operator +(ulong x);
float operator +(float x);
double operator +(double x);
decimal operator +(decimal x);
Dla każdego z tych operatorów wynik jest po prostu wartością operandu.
Podniesione (§12.4.8) formy niepodniesionych wstępnie zdefiniowanych jednoargumentowych operatorów plus zdefiniowanych powyżej są również wstępnie zdefiniowanymi.
12.9.3 Jednoargumentowy operator minus
W przypadku operacji postaci –x
, rozpoznawanie przeciążenia operatora jednoargumentowego (§12.4.4) jest stosowane w celu wybrania konkretnej implementacji operatora. Operand jest konwertowany na typ parametru wybranego operatora, a typ wyniku jest zwracanym typem operatora. Wstępnie zdefiniowane jednoargumentowe operatory minus to:
Negacja liczb całkowitych:
int operator –(int x); long operator –(long x);
Wynik jest obliczany przez odjęcie
X
od zera. Jeśli wartośćX
jest najmniejszą reprezentującą wartością typu operandu (−2³² dlaint
lub −2⁶³ dlalong
), nie można przedstawić matematycznej negacjiX
w typie operandu. Jeśli to występuje w kontekściechecked
, rzucany jestSystem.OverflowException
; jeśli to się dzieje w kontekścieunchecked
, wynik to wartość operandu, a przepełnienie nie jest zgłaszane.Jeśli operand operatora negacji jest typu
uint
, jest konwertowany na typlong
, a typ wyniku jestlong
. Wyjątkiem jest reguła zezwalająca na zapisanie wartościint
−2147483648
(−2³²) jako literału liczby całkowitej dziesiętnej (§6.4.5.3).Jeśli operand operatora negacji jest typu
ulong
, wystąpi błąd kompilacji. Wyjątkiem jest reguła zezwalająca na zapis wartościlong
−9223372036854775808
(−2⁶³) jako literał liczby całkowitej dziesiętnej (§6.4.5.3)Negacja zmiennoprzecinkowa:
float operator –(float x); double operator –(double x);
Wynikiem jest wartość
X
z odwróconym znakiem. Jeślix
jestNaN
, wynik jest równieżNaN
.Negacja dziesiętna:
decimal operator –(decimal x);
Wynik jest obliczany przez odjęcie
X
od zera. Negacja dziesiętna jest równoważna użyciu jednoargumentowego operatora minus typuSystem.Decimal
.
Podniesione (§12.4.8) formy zdefiniowanych powyżej niepodniesionych jednoargumentowych operatorów minus są również wstępnie zdefiniowane.
12.9.4 Operator negacji logicznej
W przypadku operacji postaci !x
, rozpoznawanie przeciążenia operatora jednoargumentowego (§12.4.4) jest stosowane w celu wybrania konkretnej implementacji operatora. Operand jest konwertowany na typ parametru wybranego operatora, a typ wyniku jest zwracanym typem operatora. Istnieje tylko jeden wstępnie zdefiniowany operator negacji logicznej:
bool operator !(bool x);
Ten operator oblicza logiczną negację operandu: jeśli operand jest true
, wynik jest false
. Jeśli operand jest false
, wynik jest true
.
Lifted (§12.4.8) formularze nieznieszonego operatora negacji logicznej zdefiniowanej powyżej są również wstępnie zdefiniowane.
Uwaga: logiczna negacja prefiksowa i postfiksowe operatory umożliwiające pustą wartość (§12.8.9), choć reprezentowane przez ten sam token leksykalny (!
), są odrębne.
uwaga końcowa
Operator uzupełniania bitowego 12.9.5
W przypadku operacji postaci ~x
, rozpoznawanie przeciążenia operatora jednoargumentowego (§12.4.4) jest stosowane w celu wybrania konkretnej implementacji operatora. Operand jest konwertowany na typ parametru wybranego operatora, a typ wyniku jest zwracanym typem operatora. Wstępnie zdefiniowane operatory uzupełniania bitowego to:
int operator ~(int x);
uint operator ~(uint x);
long operator ~(long x);
ulong operator ~(ulong x);
Dla każdego z tych operatorów wynik operacji jest bitowym uzupełnieniem x
.
Każdy typ wyliczenia E
niejawnie udostępnia następujący operator uzupełniania bitowego:
E operator ~(E x);
Wynik oceny ~x
, gdzie X
jest wyrażeniem typu wyliczenia E
z podstawowym typem U
, jest dokładnie taki sam jak ocena (E)(~(U)x)
, z tą różnicą, że konwersja na E
jest zawsze wykonywana tak, jakby w kontekście unchecked
(§12.8.20).
Podniesione (§12.4.8) wersje wstępnie zdefiniowanych podstawowych operatorów uzupełniania bitowego zdefiniowane powyżej są również wstępnie zdefiniowane.
12.9.6 Operatory przyrostków i dekrementacji
pre_increment_expression
: '++' unary_expression
;
pre_decrement_expression
: '--' unary_expression
;
Operand operacji inkrementacji lub dekrementacji prefiksowej powinien być wyrażeniem sklasyfikowanym jako zmienna, odwołanie do właściwości lub dostęp do indeksatora. Wynikiem działania jest wartość tego samego typu co operandu.
Jeśli operand operacji przyrostka lub dekrementacji jest właściwością lub indeksatorem dostępu, właściwość lub indeksator musi mieć zarówno metodę get, jak i zestaw dostępu. Jeśli tak nie jest, wystąpi błąd powiązania czasowego.
Rozpoznawanie przeciążenia operatora jednoargumentowego (§12.4.4) jest stosowane w celu wybrania określonej implementacji operatora. Istnieją wstępnie zdefiniowane operatory ++
i --
dla następujących typów: sbyte
, byte
, short
, ushort
, int
, uint
, long
, ulong
, char
, float
, double
, decimal
i dowolnego typu wyliczeniowego. Wstępnie zdefiniowane operatory ++
zwracają wartość wygenerowaną przez dodanie 1
do operandu, a wstępnie zdefiniowane operatory --
zwracają wartość wygenerowaną przez odjęcie 1
z operandu. W kontekście checked
, jeśli wynik tego dodawania lub odejmowania znajduje się poza zakresem typu wyniku i typ wyniku jest typem całkowitym lub typem wyliczenia, zgłaszany jest wyjątek System.OverflowException
.
Musi nastąpić niejawna konwersja z typu zwrotnego wybranego operatora jednoargumentowego do typu unary_expression, w przeciwnym razie pojawi się błąd kompilacji.
Przetwarzanie w czasie działania operacji przyrostu prefiksu lub dekrementacji w formie ++x
lub --x
składa się z następujących kroków:
- Jeśli
x
jest klasyfikowana jako zmienna:-
x
jest obliczana w celu utworzenia zmiennej. - Wartość
x
jest konwertowana na typ operandu wybranego operatora, a operator jest wywoływany z tą wartością jako argumentem. - Wartość zwracana przez operatora jest konwertowana na typ
x
. Wynikowa wartość jest przechowywana w lokalizacji podanej przez ocenęx
. - i staje się wynikiem operacji.
-
- Jeśli
x
jest klasyfikowana jako właściwość lub dostęp indeksatora:- Wyrażenie wystąpienia (jeśli
x
nie jeststatic
) oraz lista argumentów (jeślix
jest dostępem indeksatora) skojarzona zx
są ewaluowane, a ich wyniki są następnie używane w kolejnych wywołaniach metod get i set. - Wywołano metodę get
x
. - Wartość zwracana przez metodę pobierania jest konwertowana na typ operandu wybranego operatora, a operator jest wywoływany z tą wartością jako argumentem.
- Wartość zwracana przez operatora jest konwertowana na typ
x
. Akcesor ustawiającyx
jest wywoływany z tą wartością jako argumentem o wartości. - Ta wartość staje się również wynikiem operacji.
- Wyrażenie wystąpienia (jeśli
Operatory ++
i --
obsługują również notację postfiksu (§12.8.16). Wynikiem x++
lub x--
jest wartość x
przed operacją, natomiast wynikiem ++x
lub --x
jest wartość x
po operacji. W obu przypadkach sama x
ma tę samą wartość po operacji.
Implementację operatora ++
lub operatora --
można wywołać, używając notacji postfiksowej lub prefiksowej. Nie można mieć oddzielnych implementacji operatorów dla dwóch notacji.
Uniesione (§12.4.8) formy nieuniesionych wstępnie zdefiniowanych operatorów inkrementacji i dekrementacji, które zostały zdefiniowane powyżej, są również wstępnie zdefiniowane.
12.9.7 Wyrażenia rzutowe
cast_expression służy do jawnego konwertowania wyrażenia na dany typ.
cast_expression
: '(' type ')' unary_expression
;
cast_expression w formie (T)E
, gdzie T
jest typem, a E
jest wyrażeniem jednoargumentowym , wykonuje jawną konwersję (§10.3) wartości E
do typu T
. Jeśli nie istnieje jawna konwersja z E
do T
, wystąpi błąd czasu powiązania. W przeciwnym razie wynikiem jest wartość wygenerowana przez jawną konwersję. Wynik jest zawsze klasyfikowany jako wartość, nawet jeśli E
oznacza zmienną.
Gramatyka wyrażenia cast_expression powoduje pewne niejednoznaczności składniowe.
Przykład: wyrażenie
(x)–y
może być interpretowane jako cast_expression (rzut–y
do wpisywaniax
) lub jako additive_expression połączone z parenthesized_expression (która oblicza wartośćx – y
). koniec przykładu
Aby rozwiązać cast_expression niejednoznaczności, istnieje następująca reguła: Sekwencja co najmniej jednego tokenu (§6.4) ujęta w nawiasy jest traktowana jako początek cast_expression tylko wtedy, gdy co najmniej jedna z następujących wartości jest prawdziwa:
- Sekwencja tokenów jest poprawną gramatyką dla typu, ale nie dla wyrażenia.
- Sekwencja tokenów jest poprawną składnią dla typu, i token bezpośrednio po zamykających nawiasach jest tokenem "
~
", tokenem "!
", tokenem "(
", identyfikatorem (§6.4.3), literałem (§6.4.5) lub dowolnym słowem kluczowym (§6.4.4) z wyjątkiemas
iis
.
Termin "poprawna gramatyka" powyżej oznacza tylko, że sekwencja tokenów jest zgodna z określoną produkcją gramatyczną. W szczególności nie uwzględnia rzeczywistego znaczenia żadnych identyfikatorów składowych.
Przykład: jeśli
x
iy
są identyfikatorami,x.y
jest poprawną gramatyką dla typu, nawet jeślix.y
w rzeczywistości nie oznacza typu. koniec przykładu
Uwaga: z reguły uściślania wynika, że jeśli
x
iy
są identyfikatorami,(x)y
,(x)(y)
i(x)(-y)
są cast_expressions, ale(x)-y
nie jest, nawet jeślix
identyfikuje typ. Jeśli jednakx
jest słowem kluczowym identyfikującym wstępnie zdefiniowany typ (taki jakint
), wszystkie cztery formularze są cast_expressions (ponieważ takie słowo kluczowe nie może być wyrażeniem samym w sobie). uwaga końcowa
12.9.8 Await — wyrażenia
12.9.8.1 Ogólne
Operator await
służy do wstrzymania oceny otaczającej funkcji asynchronicznej do momentu zakończenia operacji asynchronicznej reprezentowanej przez operand.
await_expression
: 'await' unary_expression
;
await_expression jest dozwolona tylko w treści funkcji asynchronicznej (§15.15). W najbliższej otaczającej funkcji asynchronicznej await_expression nie może występować w następujących miejscach:
- Wewnątrz zagnieżdżonej funkcji anonimowej (nieasynchronicznej)
- Wewnątrz bloku lock_statement
- W konwersji funkcji anonimowej na typ drzewa wyrażeń (§10.7.3)
- W niebezpiecznym kontekście
Uwaga: await_expression nie może wystąpić w większości miejsc w query_expression, ponieważ są one syntatycznie przekształcone w celu używania niesynchronicznych wyrażeń lambda. uwaga końcowa
Wewnątrz funkcji asynchronicznej nie należy używać await
jako available_identifier, chociaż identyfikator dosłowny @await
może być użyty. W związku z tym nie ma niejednoznaczności składni między await_expressions i różnymi wyrażeniami obejmującymi identyfikatory. Poza funkcjami asynchronicznych await
pełni rolę normalnego identyfikatora.
Operand await_expression nazywa się zadaniem . Reprezentuje on operację asynchroniczną, która może lub nie może zostać ukończona w momencie oceny await_expression. Celem operatora await
jest wstrzymanie wykonywania otaczającej funkcji asynchronicznej do momentu ukończenia tego zadania, a następnie uzyskania jego wyniku.
12.9.8.2 Oczekiwane wyrażenia
Zadanie await_expression musi być oczekiwalne. Wyrażenie t
jest oczekiwalne, jeśli jest spełniony jeden z następujących warunków:
-
t
jest typu czasu kompilacjidynamic
-
t
ma dostępną metodę instancji lub metodę rozszerzającą o nazwieGetAwaiter
bez parametrów ani parametrów typu ze zwracanym typemA
, dla którego wszystkie poniższe warunki są spełnione:-
A
implementuje interfejsSystem.Runtime.CompilerServices.INotifyCompletion
(zwany dalejINotifyCompletion
na potrzeby zwięzłości) -
A
ma dostępną, czytelną właściwość wystąpieniaIsCompleted
typubool
-
A
ma dostępną metodę instancjiGetResult
bez parametrów ani parametrów typu
-
Celem metody GetAwaiter
jest uzyskanie awaiter dla zadania. Typ A
nazywany jest typem awaiter dla wyrażenia await.
Celem właściwości IsCompleted
jest określenie, czy zadanie zostało już ukończone. Jeśli tak, nie ma potrzeby zawieszania oceny.
Celem metody INotifyCompletion.OnCompleted
jest zarejestrowanie "kontynuacji" zadania; tj. delegat (typu System.Action
), który zostanie wywołany po zakończeniu zadania.
Celem metody GetResult
jest uzyskanie wyniku zadania po jego zakończeniu. Wynik ten może być pomyślnym zakończeniem, możliwie z wartością wyniku, lub może to być wyjątek rzucany przez metodę GetResult
.
12.9.8.3 Klasyfikacja wyrażeń await
Wyrażenie await t
jest klasyfikowane tak samo jak wyrażenie (t).GetAwaiter().GetResult()
. W związku z tym, jeśli zwracany typ GetResult
jest void
, await_expression jest klasyfikowany jako nic. Jeśli ma typ zwracany inny niżvoid
T
, wyrażenie oczekujące jest klasyfikowane jako wartość typu T
.
12.9.8.4 Ocena czasu wykonywania wyrażeń await
W czasie wykonywania wyrażenie await t
jest oceniane w następujący sposób:
- Awaiter
a
jest uzyskiwany przez obliczenie wyrażenia(t).GetAwaiter()
. -
bool
b
uzyskuje się przez obliczenie wyrażenia(a).IsCompleted
. - Jeśli
b
jestfalse
, ocena zależy od tego, czya
implementuje interfejsSystem.Runtime.CompilerServices.ICriticalNotifyCompletion
(określaną dalej jakoICriticalNotifyCompletion
dla zwięzłości). Ta kontrola jest wykonywana w momencie wiązania; czyli w czasie wykonywania, jeślia
ma typ określony w czasie kompilacji jakodynamic
, lub w czasie kompilacji w przeciwnym razie. Niechr
oznacza delegata wznowienia (§15.15):- Jeśli
a
nie implementujeICriticalNotifyCompletion
, zostanie obliczone wyrażenie((a) as INotifyCompletion).OnCompleted(r)
. - Jeśli
a
implementujeICriticalNotifyCompletion
, zostanie obliczone wyrażenie((a) as ICriticalNotifyCompletion).UnsafeOnCompleted(r)
. - Ocena jest następnie zawieszona, a kontrolka jest zwracana do bieżącego obiektu wywołującego funkcji async.
- Jeśli
- Albo natychmiast po (jeśli
b
jesttrue
), lub po późniejszym wywołaniu delegata wznowienia (jeślib
jestfalse
), wyrażenie(a).GetResult()
jest obliczane. Jeśli zwracana jest jakaś wartość, to jest ona wynikiem wyrażenia await_expression. W przeciwnym razie wynik nie jest niczym.
Implementacja metod interfejsu INotifyCompletion.OnCompleted
i ICriticalNotifyCompletion.UnsafeOnCompleted
powinna spowodować, że delegat r
zostanie wywołany co najwyżej raz. W przeciwnym razie zachowanie funkcji asynchronicznej otaczającej jest niezdefiniowane.
12.10 Operatory arytmetyczne
12.10.1 Ogólne
Operatory *
, /
, %
, +
i -
są nazywane operatorami arytmetycznymi.
multiplicative_expression
: unary_expression
| multiplicative_expression '*' unary_expression
| multiplicative_expression '/' unary_expression
| multiplicative_expression '%' unary_expression
;
additive_expression
: multiplicative_expression
| additive_expression '+' multiplicative_expression
| additive_expression '-' multiplicative_expression
;
Jeśli operand operatora arytmetycznego ma typ czasu kompilacji dynamic
, wyrażenie jest dynamicznie powiązane (§12.3.3). W tym przypadku typ czasu kompilacji wyrażenia jest dynamic
, a rozwiązanie opisane poniżej będzie odbywać się w czasie wykonywania przy użyciu typu czasu wykonywania tych operandów, które mają typ czasu kompilacji dynamic
.
Operator mnożenia 12.10.2
W przypadku operacji formularza x * y
funkcja rozpoznawania przeciążenia operatora binarnego (§12.4.5) jest stosowana w celu wybrania określonej implementacji operatora. Operandy są konwertowane na typy parametrów wybranego operatora, a typ wyniku jest zwracanym typem operatora.
Poniżej wymieniono wstępnie zdefiniowane operatory mnożenia. Wszystkie operatory obliczają produkt x
oraz y
.
Mnożenie całkowite:
int operator *(int x, int y); uint operator *(uint x, uint y); long operator *(long x, long y); ulong operator *(ulong x, ulong y);
W kontekście
checked
, jeśli produkt znajduje się poza zakresem typu wyniku, zostanie zgłoszonySystem.OverflowException
. W kontekścieunchecked
przepełnienia nie są zgłaszane, a wszystkie znaczące bity o wysokiej kolejności poza zakresem typu wyniku są odrzucane.Mnożenie zmiennoprzecinkowe:
float operator *(float x, float y); double operator *(double x, double y);
Produkt jest obliczany zgodnie z regułami arytmetyki IEC 60559. W poniższej tabeli wymieniono wyniki wszystkich możliwych kombinacji wartości niezerowych, zer, nieskończoności i sieci NaNs. W tabeli
x
iy
są dodatnimi wartościami skończonymi.z
jest wynikiemx * y
, zaokrąglone do najbliższej reprezentowalnej wartości. Jeśli wartość wyniku jest zbyt duża dla docelowego typu,z
jest nieskończonością. Ze względu na zaokrąglenie,z
może być zerem, mimo że anix
, aniy
nie są zerami.+y
-y
+0
-0
+∞
-∞
NaN
+x
+z
-z
+0
-0
+∞
-∞
NaN
-x
-z
+z
-0
+0
-∞
+∞
NaN
+0
+0
-0
+0
-0
NaN
NaN
NaN
-0
-0
+0
-0
+0
NaN
NaN
NaN
+∞
+∞
-∞
NaN
NaN
+∞
-∞
NaN
-∞
-∞
+∞
NaN
NaN
-∞
+∞
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
(Z wyjątkiem sytuacji, w których w tabelach zmiennoprzecinkowych w §12.10.2–§12.10.6 użycie wartości "
+
" oznacza, że wartość jest dodatnia; użycie wartości "-
" oznacza, że wartość jest ujemna; a brak znaku oznacza, że wartość może być dodatnia lub ujemna lub nie ma znaku (NaN).Mnożenie dziesiętne:
decimal operator *(decimal x, decimal y);
Jeśli wynikowa wartość jest zbyt duża, aby przedstawić ją w formacie dziesiętnym, zgłaszany jest
System.OverflowException
. Ze względu na zaokrąglenie wynik może być zerowy, mimo że żaden operand nie jest zerowy. Skala wyniku, przed zaokrągleniami, jest sumą skali dwóch operandów. Mnożenie dziesiętne jest równoważne użyciu operatora mnożenia typuSystem.Decimal
.
Podniesione (§12.4.8) formy niepodniesionych wcześniej zdefiniowanych operatorów mnożenia zdefiniowanych powyżej są również wstępnie określone.
Operator dzielenia 12.10.3
W przypadku operacji formularza x / y
funkcja rozpoznawania przeciążenia operatora binarnego (§12.4.5) jest stosowana w celu wybrania określonej implementacji operatora. Operandy są konwertowane na typy parametrów wybranego operatora, a typ wyniku jest zwracanym typem operatora.
Poniżej wymieniono wstępnie zdefiniowane operatory dzielenia. Wszyscy operatorzy obliczają iloraz x
i y
.
Dzielenie liczb całkowitych:
int operator /(int x, int y); uint operator /(uint x, uint y); long operator /(long x, long y); ulong operator /(ulong x, ulong y);
Jeśli wartość prawego operandu wynosi zero, zostanie zgłoszony
System.DivideByZeroException
.Podział zaokrągla wynik do zera. W związku z tym wartość bezwzględna wyniku jest największą możliwą liczbą całkowitą, która jest mniejsza lub równa wartości bezwzględnej ilorazu dwóch operandów. Wynik jest zerowy lub dodatni, gdy dwa operandy mają ten sam znak i zero lub ujemne, gdy dwa operandy mają przeciwstawne znaki.
Jeśli lewy operand jest najmniejszą reprezentowaną wartością
int
lublong
, a prawy operand jest–1
, występuje przepełnienie. W kontekściechecked
, powoduje to zgłoszenieSystem.ArithmeticException
(lub jego podklasy). W kontekścieunchecked
definiowane przez implementację jest to, czy zgłaszana jestSystem.ArithmeticException
(lub jej podklasa), czy przepełnienie pozostaje niezauważone, a wartość wynikowa jest wartością lewego operandu.Dzielenie zmiennoprzecinkowe:
float operator /(float x, float y); double operator /(double x, double y);
Iloraz jest obliczany zgodnie z regułami arytmetyki IEC 60559. W poniższej tabeli wymieniono wyniki wszystkich możliwych kombinacji wartości niezerowych, zer, nieskończoności i sieci NaNs. W tabeli
x
iy
są dodatnimi wartościami skończonymi.z
jest wynikiemx / y
, zaokrąglone do najbliższej reprezentowalnej wartości.+y
-y
+0
-0
+∞
-∞
NaN
+x
+z
-z
+∞
-∞
+0
-0
NaN
-x
-z
+z
-∞
+∞
-0
+0
NaN
+0
+0
-0
NaN
NaN
+0
-0
NaN
-0
-0
+0
NaN
NaN
-0
+0
NaN
+∞
+∞
-∞
+∞
-∞
NaN
NaN
NaN
-∞
-∞
+∞
-∞
+∞
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
Dzielenie dziesiętne:
decimal operator /(decimal x, decimal y);
Jeśli wartość prawego operandu wynosi zero, zostanie zgłoszony
System.DivideByZeroException
. Jeśli wynikowa wartość jest zbyt duża, aby przedstawić ją w formacie dziesiętnym, zgłaszany jestSystem.OverflowException
. Ze względu na zaokrąglenie wynik może być zerowy, mimo że pierwszy operand nie jest zerowy. Skala wyniku, przed zaokrągleniami, jest najbliżej preferowanej skali, która zachowa wynik równy dokładnemu wynikowi. Preferowana skala to skalax
mniejszej skaliy
.Dzielenie dziesiętne jest równoważne użyciu operatora dzielenia typu
System.Decimal
.
Podniesione (§12.4.8) formularze niepodniesionych wstępnie zdefiniowanych operatorów dzielenia ustalone powyżej są również wstępnie zdefiniowane.
12.10.4 Operator reszty
W przypadku operacji formularza x % y
funkcja rozpoznawania przeciążenia operatora binarnego (§12.4.5) jest stosowana w celu wybrania określonej implementacji operatora. Operandy są konwertowane na typy parametrów wybranego operatora, a typ wyniku jest zwracanym typem operatora.
Poniżej wymieniono wstępnie zdefiniowane operatory pozostałe. Operatory obliczają pozostałą część podziału między x
a y
.
Pozostała liczba całkowita:
int operator %(int x, int y); uint operator %(uint x, uint y); long operator %(long x, long y); ulong operator %(ulong x, ulong y);
Wynikiem
x % y
jest wartość wygenerowana przezx – (x / y) * y
. Jeśliy
ma wartość zero, zostanie zgłoszonySystem.DivideByZeroException
.Jeśli lewy operand jest najmniejszą wartością
int
lublong
, a prawy operand jest–1
,System.OverflowException
jest rzucany tylko wtedy, gdyx / y
zgłosi wyjątek.Reszta zmiennoprzecinkowa
float operator %(float x, float y); double operator %(double x, double y);
W poniższej tabeli wymieniono wyniki wszystkich możliwych kombinacji wartości niezerowych, zer, nieskończoności i sieci NaNs. W tabeli
x
iy
są dodatnimi wartościami skończonymi.z
jest wynikiemx % y
i jest obliczany jakox – n * y
, gdzie n jest największą możliwą liczbą całkowitą mniejszą lub równąx / y
. Ta metoda obliczania pozostałej części jest analogiczna do tej używanej dla operacji całkowitych, ale różni się od definicji IEC 60559 (w którejn
jest liczbą całkowitą znajdującą się najbliżejx / y
).+y
-y
+0
-0
+∞
-∞
NaN
+x
+z
+z
NaN
NaN
+x
+x
NaN
-x
-z
-z
NaN
NaN
-x
-x
NaN
+0
+0
+0
NaN
NaN
+0
+0
NaN
-0
-0
-0
NaN
NaN
-0
-0
NaN
+∞
NaN
NaN
NaN
NaN
NaN
NaN
NaN
-∞
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
Reszta dziesiętna
decimal operator %(decimal x, decimal y);
Jeśli wartość prawego operandu wynosi zero, zostanie zgłoszony
System.DivideByZeroException
. Jest określone przez implementację, kiedySystem.ArithmeticException
(lub jego podklasa) jest zgłaszane. Implementacja zgodna z normą nie powinna zgłaszać wyjątku dlax % y
w żadnym przypadku, gdyx / y
nie zgłasza wyjątku. Skala wyniku, przed zaokrągleniami, jest większa od skali dwóch operandów, a znak wyniku, jeśli niezerowy, jest taki sam jak w przypadkux
.Pozostała część dziesiętna jest równoważna użyciu operatora reszty typu
System.Decimal
.Uwaga: te reguły zapewniają, że dla wszystkich typów wynik nigdy nie ma odwrotnego znaku lewego operandu. uwaga końcowa
Rozszerzone (§12.4.8) formy uprzednio zdefiniowanych operatorów reszty, które nie są rozszerzone, są również uprzednio zdefiniowane powyżej.
12.10.5 Operator dodawania
W przypadku operacji formularza x + y
funkcja rozpoznawania przeciążenia operatora binarnego (§12.4.5) jest stosowana w celu wybrania określonej implementacji operatora. Operandy są konwertowane na typy parametrów wybranego operatora, a typ wyniku jest zwracanym typem operatora.
Poniżej wymieniono wstępnie zdefiniowane operatory dodawania. Dla typów liczbowych i wyliczeniowych zdefiniowane operatory dodawania obliczają sumę dwóch operandów. Gdy jeden lub oba operandy są typu string
, wstępnie zdefiniowane operatory dodawania łączą reprezentację ciągu znaków operandów.
Dodawanie liczb całkowitych
int operator +(int x, int y); uint operator +(uint x, uint y); long operator +(long x, long y); ulong operator +(ulong x, ulong y
W kontekście
checked
, jeśli suma znajduje się poza zakresem dozwolonym dla danego typu wyniku, rzucany jestSystem.OverflowException
. W kontekścieunchecked
przepełnienia nie są zgłaszane, a wszystkie znaczące bity o wysokiej kolejności poza zakresem typu wyniku są odrzucane.Dodawanie zmiennoprzecinkowe:
float operator +(float x, float y); double operator +(double x, double y);
Suma jest obliczana zgodnie z regułami arytmetyki IEC 60559. W poniższej tabeli wymieniono wyniki wszystkich możliwych kombinacji wartości niezerowych, zer, nieskończoności i sieci NaNs. W tabeli
x
iy
są wartościami niezerowymi, az
jest wynikiemx + y
. Jeślix
iy
mają taką samą wielkość, ale przeciwległe znaki,z
jest dodatnim zerem. Jeślix + y
jest zbyt duża do reprezentowania w typie docelowym,z
jest nieskończonością z tym samym znakiem cox + y
.y
+0
-0
+∞
-∞
NaN
x
z
x
x
+∞
-∞
NaN
+0
y
+0
+0
+∞
–∞
NaN
-0
y
+0
-0
+∞
-∞
NaN
+∞
+∞
+∞
+∞
+∞
NaN
NaN
-∞
-∞
-∞
-∞
NaN
-∞
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
Dodawanie dziesiętne:
decimal operator +(decimal x, decimal y);
Jeśli wynikowa wartość jest zbyt duża, aby przedstawić ją w formacie dziesiętnym, zgłaszany jest
System.OverflowException
. Skala wyniku, przed jakimkolwiek zaokrągleniem, jest równa większej ze skali dwóch operandów.Dodawanie dziesiętne jest równoważne użyciu operatora dodawania typu
System.Decimal
.Dodawanie wyliczenia. Każdy typ wyliczenia niejawnie udostępnia następujące wstępnie zdefiniowane operatory, gdzie
E
jest typem wyliczenia, aU
jest podstawowym typemE
:E operator +(E x, U y); E operator +(U x, E y);
W czasie wykonywania te operatory są oceniane dokładnie jako
(E)((U)x + (U)y
).Konkatenacja ciągów znaków
string operator +(string x, string y); string operator +(string x, object y); string operator +(object x, string y);
Te przeciążenia binarnego operatora
+
wykonują łączenie ciągów. Jeśli operand łączenia ciągów jestnull
, jest zastępowany pusty ciąg. W przeciwnym razie każdy operand inny niżstring
jest konwertowany na reprezentację ciągu przez wywołanie metody wirtualnejToString
dziedziczonej z typuobject
. JeśliToString
zwracanull
, zostanie zastąpiony pusty ciąg.Przykład:
class Test { static void Main() { string s = null; Console.WriteLine("s = >" + s + "<"); // Displays s = >< int i = 1; Console.WriteLine("i = " + i); // Displays i = 1 float f = 1.2300E+15F; Console.WriteLine("f = " + f); // Displays f = 1.23E+15 decimal d = 2.900m; Console.WriteLine("d = " + d); // Displays d = 2.900 } }
Dane wyjściowe wyświetlane w komentarzach są typowym wynikiem systemu US-English. Dokładne dane wyjściowe mogą zależeć od ustawień regionalnych środowiska wykonawczego. Sam operator łączenia ciągów zachowuje się tak samo w każdym przypadku, ale metody
ToString
niejawnie wywoływane podczas wykonywania mogą mieć wpływ na ustawienia regionalne.koniec przykładu
Wynikiem operatora łączenia ciągów jest
string
, który składa się z znaków lewego operandu, a następnie znaków prawego operandu. Operator łączenia ciągów nigdy nie zwraca wartościnull
.System.OutOfMemoryException
może zostać zgłoszony, jeśli dostępnej pamięci jest za mało, aby możliwe było przydzielenie wynikowego ciągu.Kombinacja delegatów. Każdy typ delegata niejawnie udostępnia następujący wstępnie zdefiniowany operator, gdzie
D
jest typem delegata:D operator +(D x, D y);
Jeśli pierwszy operand jest
null
, wynikiem operacji jest wartość drugiego operandu (nawet jeśli jest to równieżnull
). W przeciwnym razie, jeśli drugi operand jestnull
, wynikiem operacji jest wartość pierwszego operandu. W przeciwnym razie wynikiem operacji jest nowe wystąpienie delegata, którego lista wywołań składa się z elementów na liście wywołań pierwszego operandu, a następnie elementów na liście wywołań drugiego operandu. Oznacza to, że lista wywołań wynikowego delegata jest łączeniem list wywołań dwóch operandów.Uwaga: Przykłady kombinacji delegatów można znaleźć §12.10.6 i §20.6. Ponieważ
System.Delegate
nie jest typem delegata, operator + nie został dla niego zdefiniowany. koniec przypisu
Podniesione (§12.4.8) formy niepodniesionych wstępnie zdefiniowanych operatorów dodawania zdefiniowanych powyżej również są wstępnie zdefiniowane.
12.10.6 Operator odejmowania
W przypadku operacji formularza x – y
funkcja rozpoznawania przeciążenia operatora binarnego (§12.4.5) jest stosowana w celu wybrania określonej implementacji operatora. Operandy są konwertowane na typy parametrów wybranego operatora, a typ wyniku jest zwracanym typem operatora.
Poniżej wymieniono wstępnie zdefiniowane operatory odejmowania. Wszystkie operatory odejmują y
z x
.
Odejmowanie liczb całkowitych:
int operator –(int x, int y); uint operator –(uint x, uint y); long operator –(long x, long y); ulong operator –(ulong x, ulong y
W kontekście
checked
, jeśli różnica znajduje się poza zakresem typu wyników, zostanie zgłoszonySystem.OverflowException
. W kontekścieunchecked
przepełnienia nie są zgłaszane, a wszystkie znaczące bity o wysokiej kolejności poza zakresem typu wyniku są odrzucane.Odejmowanie liczb zmiennoprzecinkowych:
float operator –(float x, float y); double operator –(double x, double y);
Różnica jest obliczana zgodnie z regułami arytmetyki IEC 60559. W poniższej tabeli wymieniono wyniki wszystkich możliwych kombinacji wartości niezerowych, zer, nieskończoności i sieci NaNs. W tabeli
x
iy
są wartościami niezerowymi, az
jest wynikiemx – y
. Jeślix
iy
są równe,z
jest dodatnie zero. Jeślix – y
jest zbyt duża do reprezentowania w typie docelowym,z
jest nieskończonością z tym samym znakiem cox – y
.y
+0
-0
+∞
-∞
NaN
x
z
x
x
-∞
+∞
NaN
+0
-y
+0
+0
-∞
+∞
NaN
-0
-y
-0
+0
-∞
+∞
NaN
+∞
+∞
+∞
+∞
NaN
+∞
NaN
-∞
-∞
-∞
-∞
-∞
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
(W powyższej tabeli wpisy
-y
oznaczają negację względem iy
, a nie, że wartość jest ujemna.)Odejmowanie dziesiętne:
decimal operator –(decimal x, decimal y);
Jeśli wynikowa wartość jest zbyt duża, aby przedstawić ją w formacie dziesiętnym, zgłaszany jest
System.OverflowException
. Skala wyniku, przed jakimkolwiek zaokrągleniem, jest równa większej ze skali dwóch operandów.Odejmowanie dziesiętne jest równoważne użyciu operatora odejmowania typu
System.Decimal
.Odejmowanie enumeracji. Każdy typ wyliczenia niejawnie udostępnia następujący wstępnie zdefiniowany operator, gdzie
E
jest typem wyliczenia, aU
jest podstawowym typemE
:U operator –(E x, E y);
Ten operator jest oceniany dokładnie tak, jak
(U)((U)x – (U)y)
. Innymi słowy, operator oblicza różnicę między wartościami porządkowymix
iy
, a typem wyniku jest podstawowy typ wyliczenia.E operator –(E x, U y);
Ten operator jest oceniany dokładnie tak, jak
(E)((U)x – y)
. Innymi słowy, operator odejmuje wartość z bazowego typu wyliczenia, co daje wartość wyliczenia.Deleguj usunięcie. Każdy typ delegata niejawnie udostępnia następujący wstępnie zdefiniowany operator, gdzie
D
jest typem delegata:D operator –(D x, D y);
Semantyka jest następująca:
- Jeśli pierwszy operand jest
null
, wynikiem operacji jestnull
. - W przeciwnym razie, jeśli drugi operand jest
null
, wynikiem operacji jest wartość pierwszego operandu. - W przeciwnym razie oba operandy reprezentują niepuste listy wywołań (§20.2).
- Jeśli listy są równe w wyniku porównania, określone przez operatora równości delegatów (§12.12.9), wynikiem operacji jest
null
. - W przeciwnym razie wynikiem operacji jest nowa lista wywołań składająca się z listy pierwszego operandu z usuniętymi wpisami drugiego operandu, pod warunkiem, że lista drugiego operandu jest podlistą pierwszego. (Aby określić równość podlisty, odpowiednie wpisy są porównywane tak jak dla operatora równości delegata). Jeśli lista drugiego operanda pasuje do wielu podlist ciągłych wpisów na liście pierwszego operanda, ostatnia pasująca podlista ciągłych wpisów zostanie usunięta.
- W przeciwnym razie wynikiem operacji jest wartość lewego operandu.
- Jeśli listy są równe w wyniku porównania, określone przez operatora równości delegatów (§12.12.9), wynikiem operacji jest
Żadna z list operandów (jeśli istnieje) nie została zmieniona w procesie.
Przykład:
delegate void D(int x); class C { public static void M1(int i) { ... } public static void M2(int i) { ... } } class Test { static void Main() { D cd1 = new D(C.M1); D cd2 = new D(C.M2); D list = null; list = null - cd1; // null list = (cd1 + cd2 + cd2 + cd1) - null; // M1 + M2 + M2 + M1 list = (cd1 + cd2 + cd2 + cd1) - cd1; // M1 + M2 + M2 list = (cd1 + cd2 + cd2 + cd1) - (cd1 + cd2); // M2 + M1 list = (cd1 + cd2 + cd2 + cd1) - (cd2 + cd2); // M1 + M1 list = (cd1 + cd2 + cd2 + cd1) - (cd2 + cd1); // M1 + M2 list = (cd1 + cd2 + cd2 + cd1) - (cd1 + cd1); // M1 + M2 + M2 + M1 list = (cd1 + cd2 + cd2 + cd1) - (cd1 + cd2 + cd2 + cd1); // null } }
koniec przykładu
- Jeśli pierwszy operand jest
Podniesione (§12.4.8) formy niepodniesionych wstępnie zdefiniowanych operatorów odejmowania zdefiniowanych powyżej są również wstępnie zdefiniowane.
12.11 Operatory przesunięcia
Operatory <<
i >>
służą do wykonywania operacji przesunięcia bitowego.
shift_expression
: additive_expression
| shift_expression '<<' additive_expression
| shift_expression right_shift additive_expression
;
Jeśli operand shift_expression ma typ w czasie kompilacji dynamic
, wtedy wyrażenie jest powiązane dynamicznie (§12.3.3). W tym przypadku typ czasu kompilacji wyrażenia jest dynamic
, a rozwiązanie opisane poniżej będzie odbywać się w czasie wykonywania przy użyciu typu czasu wykonywania tych operandów, które mają typ czasu kompilacji dynamic
.
W przypadku operacji formularza x << count
lub x >> count
funkcja rozpoznawania przeciążenia operatora binarnego (§12.4.5) jest stosowana w celu wybrania określonej implementacji operatora. Operandy są konwertowane na typy parametrów wybranego operatora, a typ wyniku jest zwracanym typem operatora.
Podczas deklarowania przeciążonego operatora przesunięcia typ pierwszego operandu zawsze jest klasą lub strukturą zawierającą deklarację operatora, a typ drugiego operandu zawsze jest int
.
Poniżej wymieniono wstępnie zdefiniowane operatory przesunięcia.
Przesuń w lewo
int operator <<(int x, int count); uint operator <<(uint x, int count); long operator <<(long x, int count); ulong operator <<(ulong x, int count);
Operator
<<
przesuwax
w lewo o liczbę bitów obliczoną zgodnie z poniższym opisem.Bity o wysokiej kolejności poza zakresem typu wyniku
x
są odrzucane, pozostałe bity są przesunięte w lewo, a puste pozycje bitów o niskiej kolejności są ustawione na zero.Shift w prawo:
int operator >>(int x, int count); uint operator >>(uint x, int count); long operator >>(long x, int count); ulong operator >>(ulong x, int count);
Operator
>>
zmieniax
w prawo przez kilka bitów obliczonych zgodnie z poniższym opisem.Gdy
x
jest typuint
lublong
, bity o niskiej kolejnościx
są odrzucane, pozostałe bity są przesunięte w prawo, a puste pozycje bitów o wysokiej kolejności są ustawione na zero, jeślix
jest nieujemna i ustawiona na jedną, jeślix
jest ujemna.Gdy
x
jest typuuint
lubulong
, bity o niskiej kolejnościx
są odrzucane, pozostałe bity są przesunięte w prawo, a puste pozycje bitów o wysokiej kolejności są ustawione na zero.
W przypadku wstępnie zdefiniowanych operatorów liczba bitów do przesunięcia jest obliczana w następujący sposób:
- Gdy typ
x
jestint
lubuint
, liczba zmian jest podawana przez pięć bitów o niskiej kolejnościcount
. Innymi słowy, licznik zmian jest obliczany zcount & 0x1F
. - Gdy typ
x
jestlong
lubulong
, liczba zmian jest podawana przez sześć bitów o niskiej kolejnościcount
. Innymi słowy, licznik zmian jest obliczany zcount & 0x3F
.
Jeśli wynikowa liczba przesunięć wynosi zero, operatory przesunięcia zwracają po prostu wartość x
.
Operacje przesunięcia nigdy nie powodują przepełnienia i generują te same wyniki w sprawdzonych i niesprawdzonych kontekstach.
Gdy lewy operand operatora >>
jest znakiem typu całkowitego, operator wykonuje arytmetyczne przesunięcie w prawo, gdzie wartość najbardziej znaczącego bitu (bit znaku) operandu jest propagowana do pustych pozycji bitów o wysokiej kolejności. Gdy lewy operand operatora >>
jest typem całkowitym bez znaku, operator wykonuje logiczne przesunięcie w prawo , gdzie puste pozycje bitowe o wysokiej kolejności są zawsze ustawione na zero. Aby wykonać operację odwrotną do tej wynikającej z typu operandu, można użyć jawnego rzutowania.
Przykład: jeśli
x
jest zmienną typuint
, operacjaunchecked ((int)((uint)x >> y))
wykonuje przesunięcie logiczne po prawej stroniex
. koniec przykładu
Podniesione (§12.4.8) postaci niepodniesionych, wstępnie zdefiniowanych operatorów przesunięcia zdefiniowane powyżej są również wstępnie zdefiniowane.
12.12 Operatory relacyjne i testujące typy
12.12.1 Ogólne
Operatory ==
, !=
, <
, >
, <=
, >=
, is
i as
są nazywane operatorami testowania relacyjnego i typowego.
relational_expression
: shift_expression
| relational_expression '<' shift_expression
| relational_expression '>' shift_expression
| relational_expression '<=' shift_expression
| relational_expression '>=' shift_expression
| relational_expression 'is' type
| relational_expression 'is' pattern
| relational_expression 'as' type
;
equality_expression
: relational_expression
| equality_expression '==' relational_expression
| equality_expression '!=' relational_expression
;
Uwaga: Wyszukiwanie prawego operandu operatora
is
powinno najpierw sprawdzić, czy jest jako typ , a następnie jako wyrażenie , które może obejmować wiele tokenów. W przypadku, gdy operand jest wyrażeniem , wyrażenie wzorcowe musi mieć priorytet co najmniej tak wysoki, jak shift_expression. uwaga końcowa
Operator is
jest opisany w §12.12.12.12, a operator as
jest opisany w §12.12.13.
Operatory ==
, !=
, <
, >
, <=
i >=
są operatorami porównania .
Jeśli default_literal (§12.8.21) jest używany jako operand <
, >
, <=
lub operator >=
, występuje błąd czasu kompilacji.
Jeśli default_literal jest używana jako oba operandy operatora ==
albo !=
, wystąpi błąd czasu kompilacji. Jeśli default_literal jest używany jako lewy operand operatora is
lub as
, wystąpi błąd czasu kompilacji.
Jeśli operand operatora porównania ma typ czasu kompilacji dynamic
, wyrażenie jest dynamicznie powiązane (§12.3.3). W tym przypadku typ czasu kompilacji wyrażenia jest dynamic
, a rozwiązanie opisane poniżej będzie odbywać się w czasie wykonywania przy użyciu typu czasu wykonywania tych operandów, które mają typ czasu kompilacji dynamic
.
W przypadku operacji formularza x «op» y
, gdzie «op» jest operatorem porównania, rozpoznawanie przeciążenia (§12.4.5) jest stosowane w celu wybrania określonej implementacji operatora. Operandy są konwertowane na typy parametrów wybranego operatora, a typ wyniku jest zwracanym typem operatora. Jeśli oba operandy w wyrażeniu równości są tymi literałami null
, nie jest wykonywane rozpoznawanie przeciążenia, a wyrażenie przyjmuje stałą wartość true
lub false
, w zależności od tego, czy operatorem jest ==
czy !=
.
Wstępnie zdefiniowane operatory porównania są opisane w następujących podklasach. Wszystkie wstępnie zdefiniowane operatory porównania zwracają wynik typu bool, zgodnie z opisem w poniższej tabeli.
operacja | wynik |
---|---|
x == y |
true jeśli x jest równa y , false w przeciwnym razie |
x != y |
true jeśli x nie jest równa y , false w przeciwnym razie |
x < y |
true , jeśli x jest mniejsza niż y , false w przeciwnym razie |
x > y |
true jeśli x jest większa niż y , false w przeciwnym razie |
x <= y |
true jeśli x jest mniejsza lub równa y , false w przeciwnym razie |
x >= y |
true jeśli x jest większa lub równa y , false w przeciwnym razie |
12.12.2 Operatory porównania liczb całkowitych
Wstępnie zdefiniowane operatory porównania liczb całkowitych to:
bool operator ==(int x, int y);
bool operator ==(uint x, uint y);
bool operator ==(long x, long y);
bool operator ==(ulong x, ulong y);
bool operator !=(int x, int y);
bool operator !=(uint x, uint y);
bool operator !=(long x, long y);
bool operator !=(ulong x, ulong y);
bool operator <(int x, int y);
bool operator <(uint x, uint y);
bool operator <(long x, long y);
bool operator <(ulong x, ulong y);
bool operator >(int x, int y);
bool operator >(uint x, uint y);
bool operator >(long x, long y);
bool operator >(ulong x, ulong y);
bool operator <=(int x, int y);
bool operator <=(uint x, uint y);
bool operator <=(long x, long y);
bool operator <=(ulong x, ulong y);
bool operator >=(int x, int y);
bool operator >=(uint x, uint y);
bool operator >=(long x, long y);
bool operator >=(ulong x, ulong y);
Każdy z tych operatorów porównuje wartości liczbowe dwóch operandów liczb całkowitych i zwraca wartość bool
wskazującą, czy określona relacja jest true
, czy false
.
Podniesione (§12.4.8) formy wstępnie zdefiniowanych operatorów porównania liczb całkowitych niezawierających podniesień, zdefiniowane powyżej, są również wstępnie zdefiniowane.
12.12.3 Operatory porównań zmiennoprzecinkowych
Wstępnie zdefiniowane operatory porównywania liczb zmiennoprzecinkowych to:
bool operator ==(float x, float y);
bool operator ==(double x, double y);
bool operator !=(float x, float y);
bool operator !=(double x, double y);
bool operator <(float x, float y);
bool operator <(double x, double y);
bool operator >(float x, float y);
bool operator >(double x, double y);
bool operator <=(float x, float y);
bool operator <=(double x, double y);
bool operator >=(float x, float y);
bool operator >=(double x, double y);
Operatorzy porównują operandy zgodnie z regułami standardu IEC 60559:
Jeśli którykolwiek operand ma wartość NaN, wynik jest false
dla wszystkich operatorów z wyjątkiem !=
, dla których wynik jest true
. W przypadku dwóch operandów x != y
zawsze generuje ten sam wynik co !(x == y)
. Jednak gdy jeden lub oba operandy są naN, operatory <
, >
, <=
i >=
nie generują takie same wyniki jak logiczne negację operatora przeciwnego.
Przykład: jeśli którykolwiek z
x
iy
to NaN,x < y
jestfalse
, ale!(x >= y)
jesttrue
. koniec przykładu
Jeśli żaden operand nie ma wartości NaN, operatory porównują wartości dwóch operandów zmiennoprzecinkowych w odniesieniu do kolejności
–∞ < –max < ... < –min < –0.0 == +0.0 < +min < ... < +max < +∞
gdzie min
i max
są najmniejszymi i największymi dodatnimi wartościami skończonymi, które mogą być reprezentowane w danym formacie zmiennoprzecinkowym. Istotne skutki tego porządkowania są następujące:
- Wartości ujemne i dodatnie są traktowane jako równe.
- Nieskończoność ujemna jest uznawana za mniejszą niż wszystkie inne wartości, ale równą innej nieskończoności ujemnej.
- Nieskończoność dodatnia jest uważana za większą niż wszystkie inne wartości, ale równą innej nieskończoności dodatniej.
Przeniesione (§12.4.8) formy wstępnie zdefiniowanych operatorów porównania zmiennoprzecinkowego, które nie są przeniesione, również są wstępnie zdefiniowane.
12.12.4 Operatory porównania dziesiętnego
Wstępnie zdefiniowane operatory porównania dziesiętnego to:
bool operator ==(decimal x, decimal y);
bool operator !=(decimal x, decimal y);
bool operator <(decimal x, decimal y);
bool operator >(decimal x, decimal y);
bool operator <=(decimal x, decimal y);
bool operator >=(decimal x, decimal y);
Każdy z tych operatorów porównuje wartości liczbowe dwóch operandów dziesiętnych i zwraca wartość bool
wskazującą, czy określona relacja jest true
, czy false
. Każde porównanie dziesiętne jest równoważne użyciu odpowiedniego operatora relacyjnego lub równości typu System.Decimal
.
Podniesione (§12.4.8) wersje niepodniesionych wstępnie zdefiniowanych operatorów porównania dziesiętnego, zdefiniowanych powyżej, są również wstępnie zdefiniowane.
12.12.5 Operatory równości logicznej
Z góry zdefiniowane operatory równości typu Boolean to:
bool operator ==(bool x, bool y);
bool operator !=(bool x, bool y);
Wynik ==
jest true
, jeśli x
i y
są true
lub x
i y
są false
. W przeciwnym razie wynik jest false
.
Wynik !=
jest false
, jeśli x
i y
są true
lub x
i y
są false
. W przeciwnym razie wynik jest true
. Gdy operandy są typu bool
, operator !=
generuje taki sam wynik jak operator ^
.
Podniesione (§12.4.8) formy niepodniesionych wstępnie zdefiniowanych operatory równości boolowskiej zdefiniowane powyżej są również wstępnie zdefiniowane.
12.12.6 Operatory porównania wyliczenia
Każdy typ wyliczenia niejawnie udostępnia następujące wstępnie zdefiniowane operatory porównania
bool operator ==(E x, E y);
bool operator !=(E x, E y);
bool operator <(E x, E y);
bool operator >(E x, E y);
bool operator <=(E x, E y);
bool operator >=(E x, E y);
Wynikiem oceny x «op» y
, gdzie x i y są wyrażeniami typu wyliczenia E
z podstawowym typem U
, a «op» jest jednym z operatorów porównania, jest dokładnie taki sam jak ocena ((U)x) «op» ((U)y)
. Innymi słowy, operatory porównania typów wyliczenia po prostu porównują bazowe wartości całkowite dwóch operandów.
Podniesione (§12.4.8) warianty niepodniesionych z góry zdefiniowanych operatorów porównań wyliczeniowych zdefiniowanych powyżej są również z góry zdefiniowane.
12.12.7 Operatory równości typów odwołań
Każdy typ klasy C
niejawnie udostępnia następujące wstępnie zdefiniowane operatory równości dla typów referencyjnych:
bool operator ==(C x, C y);
bool operator !=(C x, C y);
chyba że istnieją wstępnie zdefiniowane operatory równości dla C
(na przykład gdy C
jest string
lub System.Delegate
).
Operatory zwracają wynik porównania dwóch odwołań pod kątem równości lub nierówności.
operator ==
zwraca true
tylko wtedy, gdy x
i y
odnoszą się do tej samej instancji lub są null
, a operator !=
zwraca true
tylko wtedy, gdy operator ==
z tymi samymi operandami zwróci false
.
Oprócz normalnych zasad stosowania (§12.6.4.2), wstępnie zdefiniowane operatory porównania typów referencyjnych wymagają jednego z następujących, aby były stosowane:
- Oba operandy są wartościami typu znanego jako reference_type lub jako literał
null
. Ponadto istnieje konwersja tożsamości lub jawna konwersja referencji (§10.3.5) z każdego z operandów do typu drugiego operandu. - Jednym z operandów jest literał
null
, a drugim jest wartość typuT
, gdzieT
jest parametrem typu , który nie jest znany jako typ wartości i nie ma ograniczenia typu wartości.- Jeśli w czasie wykonywania
T
jest typem wartości niemającym wartości null, wynik==
jestfalse
, a wynik!=
jesttrue
. - Jeśli w czasie wykonywania
T
jest typem wartości dopuszczającym null, wynik jest obliczany z właściwościHasValue
operandu, zgodnie z opisem w sekcji (§12.12.10). - Jeśli w czasie wykonywania
T
jest typem referencyjnym, wynik jesttrue
, jeśli operand jestnull
, a w przeciwnym raziefalse
.
- Jeśli w czasie wykonywania
O ile jeden z tych warunków nie zostanie spełniony, wystąpi błąd czasu powiązania.
Uwaga: Istotne implikacje tych reguł są następujące:
- Jest to błąd w czasie wiązania użyć predefiniowanych operatorów równości typów referencyjnych do porównywania dwóch odwołań, o których wiadomo, że są różne w czasie wiązania. Jeśli na przykład typy czasu powiązania operandów są dwoma typami klas, a jeśli żaden z nich nie pochodzi z drugiej, nie będzie możliwe, aby dwa operandy odwoływały się do tego samego obiektu. W związku z tym operacja jest uważana za błąd czasu wiązania.
- Zdefiniowane z góry operatory równości typów referencyjnych nie zezwalają na porównywanie operandów typów wartościowych (z wyjątkiem sytuacji, gdy parametry szablonu typów są porównywane z
null
, co jest obsługiwane w sposób szczególny).- Operandy wstępnie zdefiniowanych operatorów równości typów odwołań nigdy nie są w polu. Wykonywanie takich operacji opakowywania byłoby bez znaczenia, ponieważ odwołania do nowo przydzielonych wystąpień opakowanych różniłyby się od wszystkich innych odwołań.
W przypadku operacji w formie
x == y
lubx != y
, jeśli istnieje dowolna zdefiniowana przez użytkownikaoperator ==
luboperator !=
, reguły rozwiązywania przeciążenia operatora (§12.4.5) wybiorą tego operatora zamiast wstępnie zdefiniowanego operatora równości typu referencyjnego. Zawsze można wybrać wbudowany operator równości typów referencyjnych, jawnie rzutując jeden lub oba operandy na typobject
.uwaga końcowa
Przykład: Poniższy przykład sprawdza, czy argument o typie bez ograniczeń jest
null
.class C<T> { void F(T x) { if (x == null) { throw new ArgumentNullException(); } ... } }
Konstrukcja
x == null
jest dozwolona, mimo żeT
może reprezentować typ wartości nieprzechowujący wartości null, a wynik jest po prostu zdefiniowany jakofalse
, gdyT
jest typem wartości nieprzechowującym wartości null.koniec przykładu
W przypadku operacji w formie x == y
lub x != y
, jeśli istnieje jakakolwiek odpowiednia operator ==
lub operator !=
, reguły rozwiązywania przeciążenia operatora (§12.4.5) wybiorą ten operator zamiast predefiniowanego operatora równości dla typów referencyjnych.
Uwaga: można zawsze wybrać wstępnie zdefiniowany operator równości dla typów referencyjnych, jawnie rzutując oba operandy na typ
object
. uwaga końcowa
Przykład: przykład
class Test { static void Main() { string s = "Test"; string t = string.Copy(s); Console.WriteLine(s == t); Console.WriteLine((object)s == t); Console.WriteLine(s == (object)t); Console.WriteLine((object)s == (object)t); } }
generuje dane wyjściowe
True False False False
Zmienne
s
it
odwołują się do dwóch odrębnych wystąpień ciągów zawierających te same znaki. Wynik pierwszego porównania toTrue
, ponieważ wybierany jest z góry zdefiniowany operator równości ciągów (§12.12.8), gdy oba operandy są typustring
. Pozostałe porównania dają w wynikuFalse
, ponieważ przeciążenieoperator ==
w typiestring
nie ma zastosowania, gdy którykolwiek operand ma typ czasu powiązaniaobject
.Należy pamiętać, że powyższe techniki nie mają znaczenia dla typów wartości. Przykład
class Test { static void Main() { int i = 123; int j = 123; Console.WriteLine((object)i == (object)j); } }
Dane wyjściowe to
False
, ponieważ rzutowania tworzą odwołania do dwóch oddzielnych instancji zapakowanych wartościint
.koniec przykładu
12.12.8 Operatory równości ciągów
Wstępnie zdefiniowane operatory równości ciągów to:
bool operator ==(string x, string y);
bool operator !=(string x, string y);
Dwie string
wartości są traktowane jako równe, gdy jedna z następujących wartości ma wartość true:
- Obie wartości są
null
. - Obie wartości to nie-
null
odwołania do wystąpień ciągów, które mają identyczne długości i identyczne znaki w każdej pozycji znaku.
Operatory równości ciągów porównują wartości ciągów, a nie odwołania do ciągów. Gdy dwa oddzielne wystąpienia ciągów zawierają dokładnie tę samą sekwencję znaków, wartości ciągów są równe, ale odwołania są różne.
Uwaga: Zgodnie z opisem w §12.12.7operatory równości typów odwołań mogą służyć do porównywania odwołań do ciągów zamiast wartości ciągu. uwaga końcowa
12.12.9 Delegowanie operatorów równości
Wstępnie zdefiniowane operatory równości delegatów to:
bool operator ==(System.Delegate x, System.Delegate y);
bool operator !=(System.Delegate x, System.Delegate y);
Dwa wystąpienia delegatów są traktowane jako równe w następujący sposób:
- Jeśli którakolwiek z instancji delegatów jest
null
, są równe, jeśli i tylko wtedy, gdy obie sąnull
. - Jeśli delegaty mają różne typy czasu wykonywania, nigdy nie są równi.
- Jeśli oba wystąpienia delegatów mają listę wywołań (§20.2), wystąpienia te są równe, jeśli i tylko wtedy, gdy ich listy wywołań są takie same, a każdy wpis na liście wywołań jednego z nich jest równy (zgodnie z definicją poniżej) do odpowiadającego wpisu w kolejności na liście wywołań drugiej.
Następujące reguły określają równość wpisów listy wywołań:
- Jeśli dwa wpisy listy wywołań odwołują się do tej samej metody statycznej, wpisy są równe.
- Jeśli dwa wpisy listy wywołań odwołują się do tej samej metody niestatycznej na tym samym obiekcie docelowym (zdefiniowanym przez operatory równości odwołań), wpisy są równe.
- Wpisy listy wywołań, utworzone z ocen semantycznie identycznych funkcji anonimowych (§12.19) z tym samym (prawdopodobnie pustym) zestawem przechwyconych instancji zmiennych zewnętrznych, mogą być równe (ale nie muszą).
Jeśli rozpoznawanie przeciążenia operatora prowadzi do wybrania jednego z operatorów równości delegatów, a typy czasu powiązania obu operandów są typami delegatów zgodnie z opisem w §20 zamiast System.Delegate
, i jeśli nie istnieje konwersja tożsamości między typami operandów w czasie powiązania, występuje błąd czasu powiązania.
Uwaga: ta reguła uniemożliwia porównywanie, które nigdy nie mogą uwzględniać wartości innych niż
null
z powodu odwoływania się do wystąpień różnych typów delegatów. uwaga końcowa
12.12.10 Operatory równości pomiędzy typami wartości dopuszczonymi do null a literałem null
Operatory ==
i !=
zezwalają na to, aby jeden operand był wartością typu wartości dopuszczanej wartości null, a drugi jako literał null
, nawet jeśli dla operacji nie istnieje żaden wstępnie zdefiniowany operator lub operator zdefiniowany przez użytkownika (w postaci nieznoszonej lub podniesionej).
W przypadku operacji jednej z form
x == null null == x x != null null != x
gdzie x
jest wyrażeniem typu wartości, która może być null, jeśli rozwiązywanie przeciążenia operatora (§12.4.5) nie znajduje odpowiedniego operatora, wynik jest obliczany z właściwości HasValue
x
. W szczególności pierwsze dwie formy są tłumaczone na !x.HasValue
, a ostatnie dwie formy są tłumaczone na x.HasValue
.
12.12.11 Operatory równości krotek
Operatory równości krotki są stosowane parowo do elementów operandów krotki w porządku leksykalnym.
Jeśli każdy z operandów x
i y
operatora ==
lub !=
jest klasyfikowany jako krotka lub jako wartość z typem krotki (§8.3.11), operator jest operatorem równości krotki .
Jeśli operand e
jest klasyfikowany jako krotka, elementy e1...en
powinny być wynikami oceny wyrażeń elementów wyrażenia krotki. W przeciwnym razie jeśli e
jest wartością typu krotki, elementy będą t.Item1...t.Itemn
, gdy t
jest wynikiem oceny e
.
Operandy x
i y
operatora równości krotki muszą mieć taką samą krotność, w przeciwnym razie wystąpi błąd czasu kompilacji. Dla każdej pary elementów xi
i yi
stosuje się ten sam operator równości i zwraca wynik typu bool
, dynamic
, typu, który ma niejawną konwersję na bool
, lub typ definiujący operatory true
i false
.
Operator równości krotki x == y
jest oceniany w następujący sposób:
- Lewy operand
x
jest oceniany. - Operand z prawej strony
y
jest obliczany. - Dla każdej pary elementów
xi
iyi
w kolejności leksykalnej:- Operator
xi == yi
jest obliczany, a wynik typubool
jest uzyskiwany w następujący sposób:- Jeśli porównanie przyniosło
bool
, to jest to wynik. - W przeciwnym razie jeśli porównanie daje
dynamic
operatorfalse
jest dynamicznie wywoływany, a wynikowa wartośćbool
jest negowana za pomocą operatora logicznego negacji (!
). - W przeciwnym razie, jeśli typ porównania ma niejawną konwersję na
bool
, ta konwersja jest stosowana. - W przeciwnym razie, jeśli typ porównania ma operator
false
, ten operator jest wywoływany, a wynikowa wartośćbool
jest negowana za pomocą operatora logicznego negacji (!
).
- Jeśli porównanie przyniosło
- Jeśli wynik
bool
wynosifalse
, nie przeprowadza się dalszej oceny, a wynik operatora równości krotki tofalse
.
- Operator
- Jeśli wszystkie porównania elementów przyniosły
true
, wynikiem operatora równości krotki jesttrue
.
Operator równości krotki x != y
jest oceniany w następujący sposób:
- Lewy operand
x
jest oceniany. - Operand z prawej strony
y
jest obliczany. - Dla każdej pary elementów
xi
iyi
w kolejności leksykalnej:- Operator
xi != yi
jest obliczany, a wynik typubool
jest uzyskiwany w następujący sposób:- Jeśli porównanie przyniosło
bool
, to jest to wynik. - W przeciwnym razie, jeśli wynik porównania to
dynamic
, operatortrue
jest dynamicznie wywoływany na nim, a otrzymana wartośćbool
jest wynikiem. - W przeciwnym razie, jeśli typ porównania ma niejawną konwersję na
bool
, ta konwersja jest stosowana. - W przeciwnym razie, jeśli typ porównania ma operator
true
, ten operator jest wywoływany, a wynikowa wartośćbool
jest wynikiem.
- Jeśli porównanie przyniosło
- Jeśli wynik
bool
wynositrue
, nie przeprowadza się dalszej oceny, a wynik operatora równości krotki totrue
.
- Operator
- Jeśli wszystkie porównania elementów przyniosły
false
, wynikiem operatora równości krotki jestfalse
.
12.12.12 Operator „is”
Istnieją dwie formy operatora is
. Jednym z nich jest operator typu 'jest-typ' , który ma typ po prawej stronie. Drugi to operator is-pattern, który ma wzorzec po prawej stronie.
12.12.12.1 Operator typu is
Operator is-type służy do sprawdzania, czy czas wykonywania obiektu jest zgodny z określonym typem. Sprawdzanie jest wykonywane w czasie działania. Wynikiem operacji E is T
, gdzie E
jest wyrażeniem, a T
jest typem innym niż dynamic
, jest wartość logiczna wskazująca, czy E
jest różne od null i można pomyślnie przekonwertować na typ T
przez konwersję odwołania, konwersję bokserską, konwersję rozpakowania, konwersję owijania lub konwersję rozpakowywania.
Operacja jest oceniana w następujący sposób:
- Jeśli
E
jest anonimową funkcją lub grupą metod, wystąpi błąd czasu kompilacji - Jeśli
E
jest literałemnull
lub jeśli wartośćE
wynosinull
, rezultatem jestfalse
. - Inaczej:
- Niech
R
być typem środowiska uruchomieniowegoE
. - Niech
D
będzie wyprowadzony zR
w następujący sposób: - Jeśli
R
jest typem wartości dopasowanej do null,D
jest pierwotnym typemR
. - W przeciwnym razie
D
jestR
. - Wynik zależy od
D
iT
w następujący sposób: - Jeśli
T
jest typem referencyjnym, wynik jesttrue
, jeśli:- istnieje konwersja tożsamości między
D
aT
, -
D
jest typem odwołania i istnieje niejawna konwersja odwołania zD
doT
lub - Albo:
D
jest typem wartości, a konwersja typu boxing zD
naT
jest możliwa.
Lub:D
jest typem wartości, aT
jest typem interfejsu zaimplementowanym przezD
.
- istnieje konwersja tożsamości między
- Jeśli
T
jest typem wartości dopuszczającej null, wynik totrue
, jeśliD
jest podstawowym typemT
. - Jeśli
T
jest typem wartości innej niż null, wynik jesttrue
, jeśliD
iT
są tego samego typu. - W przeciwnym razie wynik jest
false
.
Konwersje zdefiniowane przez użytkownika nie są brane pod uwagę przez operator is
.
Uwaga: ponieważ operator
is
jest oceniany w czasie wykonywania, wszystkie argumenty typu zostały zastąpione i nie ma otwartych typów (§8.4.3) do rozważenia. uwaga końcowa
Uwaga: Operator
is
można zrozumieć pod względem typów i konwersji w czasie kompilacji w następujący sposób, gdzieC
jest typem czasu kompilacjiE
:
- Jeśli typ czasu kompilacji
e
jest taki sam jakT
, lub jeśli istnieje niejawna konwersja odwołania (§10.2.8), konwersja przez boksowanie (§10.2.9), konwersja przez zawijanie (§10.6) lub jawna konwersja przez rozpakowywanie (§10.6) z typu czasu kompilacjiE
doT
:
- Jeśli
C
jest typu wartości innej niż null, wynik operacji jesttrue
.- W przeciwnym razie wynik operacji jest odpowiednikiem oceny
E != null
.- W przeciwnym razie, jeśli jawna konwersja odwołania (§10.3.5) lub konwersja rozpakowywania (§10.3.7) istnieje z
C
doT
, lub jeśliC
lubT
jest typem otwartym (§8.4.3), kontrole w czasie wykonywania, jak powyżej, są wykonywane.- W przeciwnym razie brak odniesienia, boxing, zawijanie lub rozpakowywanie konwersji
E
na typT
jest możliwy, a wynikiem operacji jestfalse
. Kompilator może implementować optymalizacje na podstawie typu czasu kompilacji.uwaga końcowa
12.12.12.2 Operator is-pattern
Operator is-pattern służy do sprawdzania, czy wartość obliczona przez wyrażenie pasuje danego wzorca (§11). Sprawdzanie jest wykonywane w czasie działania. Wynik operatora is-pattern ma wartość true, jeśli wartość jest zgodna ze wzorcem; w przeciwnym razie jest to fałsz.
W przypadku wyrażenia postaci E is P
, gdzie E
jest wyrażeniem relacyjnym typu T
, a P
jest wzorcem, jest to błąd kompilacji, jeśli zachodzi którykolwiek z następujących przypadków:
-
E
nie wyznacza wartości lub nie ma typu. - Wzorzec
P
nie ma zastosowania (§11.2) do typuT
.
12.12.13 Operator as
Operator as
służy do jawnego konwertowania wartości na dany typ odwołania lub typ wartości, która może być null. W przeciwieństwie do wyrażenia rzutowania (§12.9.7), operator as
nigdy nie zgłasza wyjątku. Zamiast tego, jeśli wskazana konwersja nie jest możliwa, wynikowa wartość jest null
.
W operacji w formie E as T
, E
powinno być wyrażeniem, a T
powinno być typem referencyjnym, parametrem typu, o którym wiadomo, że jest typem referencyjnym, lub typem wartości dopuszczającym wartość null. Ponadto co najmniej jeden z następujących warunków ma wartość true lub w przeciwnym razie występuje błąd czasu kompilacji:
- Tożsamość (§10.2.2), niejawna wartość null (§10.2.6), niejawne odwołanie (§10.2.8), boxing (§10.2.9), jawne dopuszczanie wartości null (§10.3.4), jawne odwołanie (§10.3.5) lub opakowywanie (§8.3.12) konwersja istnieje z
E
doT
. - Typ
E
lubT
jest typem otwartym. -
E
jest literałemnull
.
Jeśli typ czasu kompilacji E
nie jest dynamic
, operacja E as T
generuje ten sam wynik co
E is T ? (T)(E) : (T)null
z tą różnicą, że E
jest obliczana tylko raz. Można oczekiwać, że kompilator zoptymalizuje E as T
, wykonując co najwyżej jedno sprawdzanie typu środowiska uruchomieniowego, w przeciwieństwie do dwóch sprawdzań typu implikowanych przez powyższe rozszerzenie.
Jeśli typ czasu kompilacji E
jest dynamic
, to w przeciwieństwie do operatora rzutowania, operator as
nie jest dynamicznie wiązany (§12.3.3). W związku z tym rozszerzenie w tym przypadku jest następujące:
E is T ? (T)(object)(E) : (T)null
Należy pamiętać, że niektóre konwersje, takie jak konwersje zdefiniowane przez użytkownika, nie są możliwe z operatorem as
i zamiast tego powinny być wykonywane przy użyciu wyrażeń rzutowania.
Przykład: w przykładzie
class X { public string F(object o) { return o as string; // OK, string is a reference type } public T G<T>(object o) where T : Attribute { return o as T; // Ok, T has a class constraint } public U H<U>(object o) { return o as U; // Error, U is unconstrained } }
Parametr typu
T
wG
jest znany jako parametr o typie referencyjnym, ponieważ ma ograniczenie klasy. Parametr typuU
dlaH
nie jest poprawny; w związku z tym korzystanie z operatoraas
wH
jest niedozwolone.koniec przykładu
12.13 Operatory logiczne
12.13.1 Ogólne
Operatory &,
^
i |
są nazywane operatorami logicznymi.
and_expression
: equality_expression
| and_expression '&' equality_expression
;
exclusive_or_expression
: and_expression
| exclusive_or_expression '^' and_expression
;
inclusive_or_expression
: exclusive_or_expression
| inclusive_or_expression '|' exclusive_or_expression
;
Jeśli operand operatora logicznego ma typ czasu kompilacji dynamic
, wyrażenie jest dynamicznie powiązane (§12.3.3). W tym przypadku typ czasu kompilacji wyrażenia jest dynamic
, a rozwiązanie opisane poniżej będzie odbywać się w czasie wykonywania przy użyciu typu czasu wykonywania tych operandów, które mają typ czasu kompilacji dynamic
.
W przypadku operacji formularza x «op» y
, gdzie «op» jest jednym z operatorów logicznych, rozpoznawanie przeciążenia (§12.4.5) jest stosowane w celu wybrania określonej implementacji operatora. Operandy są konwertowane na typy parametrów wybranego operatora, a typ wyniku jest zwracanym typem operatora.
Wstępnie zdefiniowane operatory logiczne są opisane w następujących podklasach.
12.13.2 Operatory logiczne dla liczb całkowitych
Wstępnie zdefiniowane operatory logiczne liczby całkowitej to:
int operator &(int x, int y);
uint operator &(uint x, uint y);
long operator &(long x, long y);
ulong operator &(ulong x, ulong y);
int operator |(int x, int y);
uint operator |(uint x, uint y);
long operator |(long x, long y);
ulong operator |(ulong x, ulong y);
int operator ^(int x, int y);
uint operator ^(uint x, uint y);
long operator ^(long x, long y);
ulong operator ^(ulong x, ulong y);
Operator &
oblicza bitowe logiczne AND dwóch operandów, operator |
oblicza bitowo logiczne OR dwóch operandów, a operator ^
oblicza bitowe logiczne wyłączność OR dwóch operandów. Nie ma możliwości przepełnienia podczas tych operacji.
Podniesione (§12.4.8) formy wstępnie zdefiniowanych operatorów logicznych dla liczb całkowitych, zdefiniowanych powyżej, są również wstępnie zdefiniowane.
12.13.3 Operatory logiczne wyliczania
Każdy typ wyliczenia E
niejawnie udostępnia następujące wstępnie zdefiniowane operatory logiczne:
E operator &(E x, E y);
E operator |(E x, E y);
E operator ^(E x, E y);
Wynikiem ewaluacji x «op» y
, gdzie x
i y
są wyrażeniami typu wyliczenia E
z podstawowym typem U
, a «op» jest jednym z operatorów logicznych, jest dokładnie takie samo jak ocena (E)((U)x «op» (U)y)
. Innymi słowy, operatory logiczne typu wyliczenia po prostu wykonują operację logiczną na podstawowym typie dwóch operandów.
Podniesione (§12.4.8) formy wcześniej zdefiniowanych operatorów logicznych dla typów wyliczeniowych, które nie są podniesione, są również wstępnie zdefiniowane.
12.13.4 Operatory logiczne Boolean
Wstępnie zdefiniowane operatory logiczne to:
bool operator &(bool x, bool y);
bool operator |(bool x, bool y);
bool operator ^(bool x, bool y);
Resultatem x & y
jest true
, jeśli zarówno x
, jak i y
są true
. W przeciwnym razie wynik jest false
.
Wynik x | y
jest true
, jeśli x
lub y
jest true
. W przeciwnym razie wynik jest false
.
Wynikiem x ^ y
jest true
, jeśli x
jest true
, a y
jest false
lub x
jest false
, a y
jest true
. W przeciwnym razie wynik jest false
. Gdy operandy są typu bool
, operator ^
oblicza ten sam wynik co operator !=
.
12.13.5 Boolowska dopuszczająca wartość null & i | operatorzy
Typ logiczny nullable bool?
może reprezentować trzy wartości: true
, false
i null
.
Podobnie jak w przypadku innych operatorów binarnych, zniesione formy operatorów logicznych &
i |
(§12.13.4) są również wstępnie zdefiniowane:
bool? operator &(bool? x, bool? y);
bool? operator |(bool? x, bool? y);
Semantyka zniesionych operatorów &
i |
jest definiowana przez następującą tabelę:
x |
y |
x & y |
x \| y |
---|---|---|---|
true |
true |
true |
true |
true |
false |
false |
true |
true |
null |
null |
true |
false |
true |
false |
true |
false |
false |
false |
false |
false |
null |
false |
null |
null |
true |
null |
true |
null |
false |
false |
null |
null |
null |
null |
null |
Uwaga: typ
bool?
jest koncepcyjnie podobny do trójwartościowego typu używanego w wyrażeniach logicznych w języku SQL. W powyższej tabeli przedstawiono te same semantyki co SQL, podczas gdy stosowanie reguł §12.4.8 do operatorów&
i|
tego nie robiłoby. Reguły §12.4.8 już zapewniają semantykę podobną do SQL dla operatora zdźwigniętego^
. uwaga końcowa
12.14 Warunkowe operatory logiczne
12.14.1 Ogólne
Operatory &&
i ||
są nazywane operatorami logicznymi warunkowych. Są one również nazywane "zwarciowymi" operatorami logicznymi.
conditional_and_expression
: inclusive_or_expression
| conditional_and_expression '&&' inclusive_or_expression
;
conditional_or_expression
: conditional_and_expression
| conditional_or_expression '||' conditional_and_expression
;
Operatory &&
i ||
to wersje warunkowe operatorów &
i |
:
- Operacja
x && y
odpowiada operacjix & y
, z tą różnicą, żey
jest obliczana tylko wtedy, gdyx
nie jestfalse
. - Operacja
x || y
odpowiada operacjix | y
, z tą różnicą, żey
jest obliczana tylko wtedy, gdyx
nie jesttrue
.
Uwaga: Powodem, dla którego zwarciowanie używa warunków "nieprawda" oraz "niefałsz", jest umożliwienie operatorom warunkowym definiowanym przez użytkownika określenie, kiedy zastosować zwarciowanie. Typy zdefiniowane przez użytkownika mogą być w stanie, w którym
operator true
zwracafalse
, aoperator false
zwracafalse
. W takich przypadkach ani&&
, ani||
nie spowodowałoby zwarcia. uwaga końcowa
Jeśli operand operatora logicznego warunkowego ma typ czasu kompilacji dynamic
, wyrażenie jest dynamicznie powiązane (§12.3.3). W tym przypadku typ czasu kompilacji wyrażenia jest dynamic
, a rozwiązanie opisane poniżej będzie odbywać się w czasie wykonywania przy użyciu typu czasu wykonywania tych operandów, które mają typ czasu kompilacji dynamic
.
Operacja formularza x && y
lub x || y
jest przetwarzana przez zastosowanie rozpoznawania przeciążenia (§12.4.5), tak jakby operacja została zapisana x & y
lub x | y
. Wtedy
- Jeśli rozpoznawanie przeciążeń nie może znaleźć jednego najlepszego operatora lub jeśli rozpoznawanie przeciążenia wybierze jeden ze wstępnie zdefiniowanych operatorów logicznych liczby całkowitej lub operatorów logicznych dopuszczanych wartości null (§12.13.5), wystąpi błąd czasu powiązania.
- W przeciwnym razie, jeśli wybrany operator jest jednym ze wstępnie zdefiniowanych operatorów logicznych (§12.13.4), operacja jest przetwarzana zgodnie z opisem w §12.14.2.
- W przeciwnym razie wybrany operator jest operatorem zdefiniowanym przez użytkownika, a operacja jest przetwarzana zgodnie z opisem w §12.14.3.
Nie można bezpośrednio przeciążyć operatorów logicznych warunkowych. Jednak ponieważ warunkowe operatory logiczne są oceniane pod względem zwykłych operatorów logicznych, przeciążenia zwykłych operatorów logicznych są, z pewnymi ograniczeniami, również uważane za przeciążenia warunkowych operatorów logicznych. Opisano to w §12.14.3.
12.14.2 Warunkowe operatory logiczne typu Boolean
Gdy operandy &&
lub ||
są typu bool
, lub gdy operandy są typami, które nie definiują odpowiednich operator &
lub operator |
, ale definiują niejawne konwersje na bool
, operacja jest przetwarzana w następujący sposób:
- Operacja
x && y
jest obliczana jakox ? y : false
. Innymi słowy,x
jest najpierw obliczany i konwertowany na typbool
. Następnie, jeślix
jesttrue
,y
jest obliczany i konwertowany na typbool
, a to staje się wynikiem operacji. W przeciwnym razie wynikiem operacji jestfalse
. - Operacja
x || y
jest obliczana jakox ? true : y
. Innymi słowy,x
jest najpierw obliczany i konwertowany na typbool
. Następnie, jeślix
jesttrue
, wynikiem operacji jesttrue
. W przeciwnym raziey
jest obliczana i konwertowana na typbool
, a to staje się wynikiem operacji.
12.14.3 Warunkowe operatory logiczne zdefiniowane przez użytkownika
Gdy operandy &&
lub ||
są typami, które deklarują odpowiednie operator &
zdefiniowane przez użytkownika lub operator |
, oba następujące elementy są prawdziwe, w przypadku gdy T
jest typem, w którym zadeklarowany jest wybrany operator:
- Typ zwracany i typ każdego parametru wybranego operatora musi być
T
. Innymi słowy, operator oblicza logiczne AND lub logiczne OR dwóch operandów typuT
, i zwraca wynik typuT
. -
T
zawiera deklaracjeoperator true
ioperator false
.
Błąd czasu powiązania występuje, jeśli którekolwiek z tych wymagań nie zostanie spełnione. W przeciwnym razie operacja &&
lub ||
jest obliczana przez połączenie zdefiniowanej przez użytkownika operator true
lub operator false
z wybranym operatorem zdefiniowanym przez użytkownika:
- Operacja
x && y
jest oceniana jakoT.false(x) ? x : T.&(x, y)
, gdzieT.false(x)
jest wywołaniemoperator false
zadeklarowanym wT
, aT.&(x, y)
jest wywołaniem wybranegooperator &
. Innymi słowy,x
jest najpierw oceniane, aoperator false
jest wywoływana w wyniku w celu określenia, czyx
jest zdecydowanie fałszywe. Następnie, jeślix
jest zdecydowanie fałsz, wynikiem operacji jest wartość wcześniej obliczona dlax
. W przeciwnym raziey
jest obliczana, a wybranaoperator &
jest wywoływana na wcześniej obliczonej wartości dlax
i wartości obliczonej dlay
w celu wygenerowania wyniku operacji. - Operacja
x || y
jest oceniana jakoT.true(x) ? x : T.|(x, y)
, gdzieT.true(x)
jest wywołaniemoperator true
zadeklarowanym wT
, aT.|(x, y)
jest wywołaniem wybranegooperator |
. Innymi słowy,x
jest najpierw oceniane, aoperator true
jest wywoływana w wyniku, aby określić, czyx
jest zdecydowanie prawdziwe. Następnie, jeślix
jest zdecydowanie prawdziwe, wynikiem operacji jest wartość wcześniej obliczona dlax
. W przeciwnym raziey
jest obliczana, a wybranaoperator |
jest wywoływana na wcześniej obliczonej wartości dlax
i wartości obliczonej dlay
w celu wygenerowania wyniku operacji.
W jednej z tych operacji wyrażenie podane przez x
jest obliczane tylko raz, a wyrażenie podane przez y
nie jest obliczane lub obliczane dokładnie raz.
12.15 Operator łączenia wartości null
Operator ??
jest nazywany operatorem łączenia wartości null.
null_coalescing_expression
: conditional_or_expression
| conditional_or_expression '??' null_coalescing_expression
| throw_expression
;
W wyrażeniu łączenia wartości null w formie a ?? b
, jeśli a
nie jestnull
, wynik to a
; w przeciwnym razie wynik to b
. Operacja oblicza b
tylko wtedy, gdy a
jest null
.
Operator łączenia wartości null jest prawostronnie łączący, co oznacza, że operacje są grupowane od prawej do lewej.
Przykład: wyrażenie w formie
a ?? b ?? c
jest obliczane jakoa ?? (b ?? c)
. Ogólnie rzecz biorąc, wyrażenie w formieE1 ?? E2 ?? ... ?? EN
zwraca pierwszy z operandów, który nie jestnull
, lubnull
, jeśli wszystkie operandy sąnull
. koniec przykładu
Typ wyrażenia a ?? b
zależy od tego, które niejawne konwersje są dostępne dla operandów. W kolejności preferencji typ a ?? b
jest A₀
, A
lub B
, gdzie A
jest typem a
(pod warunkiem, że a
ma typ), B
jest typem b
(pod warunkiem, że b
ma typ), a A₀
jest podstawowym typem A
, jeśli A
jest typem wartości dopuszczalnej lub A
w przeciwnym razie. W szczególności a ?? b
jest przetwarzany w następujący sposób:
- Jeśli
A
istnieje i nie jest typem wartości dopuszczającym wartości null lub typem referencyjnym, wystąpi błąd czasu kompilacji. - W przeciwnym razie, jeśli
A
istnieje ib
jest wyrażeniem dynamicznym, typ wyniku jestdynamic
. W czasie wykonywaniaa
jest najpierw oceniane. Jeślia
nie jestnull
,a
jest konwertowany nadynamic
, a to staje się wynikiem. W przeciwnym razie obliczane jestb
, które staje się wynikiem. - W przeciwnym razie, jeśli
A
istnieje i jest typem wartości akceptującym wartości null, a istnieje niejawna konwersja zb
doA₀
, typ wyniku jestA₀
. W czasie wykonywaniaa
jest najpierw oceniane. Jeślia
nie jestnull
,a
zostanie rozpasany, aby wpisaćA₀
, co stanie się wynikiem. W przeciwnym razieb
jest oceniana i konwertowana na typA₀
, co staje się wynikiem. - W przeciwnym razie, jeśli
A
istnieje i istnieje niejawna konwersja zb
naA
, typ wyniku jestA
. Podczas wykonywania program a jest najpierw oceniany. Jeśli a nie ma wartości null, a staje się wynikiem. W przeciwnym razieb
jest oceniana i konwertowana na typA
, co staje się wynikiem. - W przeciwnym razie, jeśli
A
istnieje i jest typem dopuszczalnej wartości null,b
ma typB
i jeśli istnieje niejawna konwersja zA₀
doB
, wtedy typ wyniku jestB
. W czasie wykonywaniaa
jest najpierw oceniane. Jeślia
nie jestnull
,a
jest rozpakowany do typuA₀
i konwertowany na typB
, a to staje się wynikiem. W przeciwnym razieb
jest obliczane i staje się wynikiem. - W przeciwnym razie, jeśli
b
ma typB
, a niejawna konwersja istnieje za
doB
, typ wyniku jestB
. W czasie wykonywaniaa
jest najpierw oceniane. Jeślia
nie jestnull
,a
jest konwertowany na typB
, a to staje się wynikiem. W przeciwnym razieb
jest obliczane i staje się wynikiem.
W przeciwnym razie a
i b
są niezgodne, a występuje błąd czasu kompilacji.
12.16 Operator wyrażenia throw
throw_expression
: 'throw' null_coalescing_expression
;
throw_expression zgłasza wartość wygenerowaną przez ocenę null_coalescing_expression. Wyrażenie jest niejawnie konwertowane na System.Exception
, a wynik oceny wyrażenia jest konwertowany na System.Exception
przed zgłoszeniem. Zachowanie w czasie wykonywania oceny wyrażenia throw jest takie samo jak określone dla instrukcji throw (§13.10.6).
throw_expression nie ma typu. throw_expression jest konwertowany na każdy typ przez niejawną konwersję rzutu .
Wyrażenie throw może występować tylko w następujących kontekstach składniowych:
- Jako drugi lub trzeci operand ternarnego operatora warunkowego (
?:
). - Jako drugi operand operatora łączenia wartości null (
??
). - Jako ciało wyrażeniowe lambda lub elementu.
12.17 Wyrażenia deklaracji
Wyrażenie deklaracji deklaruje zmienną lokalną.
declaration_expression
: local_variable_type identifier
;
local_variable_type
: type
| 'var'
;
simple_name_
jest również uważany za wyrażenie deklaracji, jeśli wyszukiwanie prostej nazwy nie znalazło skojarzonej deklaracji (§12.8.4). W przypadku użycia jako wyrażenia deklaracji var _
, ale jest dozwolone w więcej miejscach.
Wyrażenie deklaracji odbywa się tylko w następujących kontekstach składniowych:
- Jako
out
argument_value w argument_list. - Jako proste odrzucenie
_
składające się z lewej strony prostego przypisania (§12.21.2). - Jako tuple_element w co najmniej jednej rekursywnie zagnieżdżonej tuple_expression, z których najbardziej zewnętrzna składa się z lewej strony przypisania dekonstrukcyjnego. deconstruction_expression powoduje powstanie wyrażeń deklaracji w tej pozycji, mimo że wyrażenia deklaracji nie są składniowo obecne.
Uwaga: To oznacza, że wyrażenie deklaracji nie może być objęte nawiasem. uwaga końcowa
Jest to błąd, gdy niejawnie typizowana zmienna zadeklarowana za pomocą declaration_expression jest referencjonowana w argument_list, w którym jest zadeklarowana.
Jest to błąd zmiennej zadeklarowanej przy użyciu declaration_expression, do której ma się odwoływać w ramach dekonstrukcji przypisania, w którym występuje.
Wyrażenie deklaracji, które jest prostym odrzuceniem lub gdzie local_variable_type jest identyfikatorem var
, jest klasyfikowane jako zmienna o niejawnie określonym typie . Wyrażenie nie ma typu, a typ zmiennej lokalnej jest wnioskowany na podstawie kontekstu składni w następujący sposób:
- W argument_list wnioskowany typ zmiennej jest zadeklarowanym typem odpowiedniego parametru.
- Jako lewa strona prostego przypisania, wywnioskowany typ zmiennej jest tym samym, co typ prawej strony przypisania.
- W przypadku wyrażenia typu tuple_expression po lewej stronie prostego przypisania wywnioskowany typ zmiennej jest taki sam jak typ odpowiadającego elementu krotki po prawej stronie przypisania (po dekonstrukcji).
W przeciwnym razie wyrażenie deklaracji jest klasyfikowane jako jawnie wpisaną zmienną, a typ wyrażenia oraz zadeklarowanej zmiennej musi być określony przez local_variable_type.
Wyrażenie deklaracji z identyfikatorem _
jest odrzucane (§9.2.9.2) i nie wprowadza nazwy zmiennej. Wyrażenie deklaracji z identyfikatorem innym niż _
wprowadza tę nazwę do najbliższej otaczającej przestrzeni deklaracji zmiennej lokalnej (§7.3).
Przykład:
string M(out int i, string s, out bool b) { ... } var s1 = M(out int i1, "One", out var b1); Console.WriteLine($"{i1}, {b1}, {s1}"); // Error: i2 referenced within declaring argument list var s2 = M(out var i2, M(out i2, "Two", out bool b2), out b2); var s3 = M(out int _, "Three", out var _);
Deklaracja
s1
pokazuje zarówno jawnie, jak i niejawnie wpisane wyrażenia deklaracji. Wnioskowany typb1
jestbool
, ponieważ jest to typ odpowiedniego parametru wyjściowego wM1
. KolejnyWriteLine
może uzyskiwać dostęp doi1
ib1
, które zostały wprowadzone do otaczającego zakresu.Deklaracja
s2
pokazuje próbę użyciai2
w zagnieżdżonym wywołaniu funkcjiM
, co jest niedozwolone, ponieważ referencja występuje na liście argumentów, gdzie zadeklarowanoi2
. Z drugiej strony odwołanie dob2
w ostatnim argumencie jest dozwolone, ponieważ występuje po końcu zagnieżdżonej listy argumentów, gdzieb2
został zadeklarowany.Deklaracja
s3
pokazuje użycie zarówno niejawnie, jak i jawnie wpisanych wyrażeń deklaracji, które są odrzucane. Ponieważ odrzucenia nie deklarują nazwanej zmiennej, dozwolone są wiele wystąpień identyfikatora_
.(int i1, int _, (var i2, var _), _) = (1, 2, (3, 4), 5);
W tym przykładzie pokazano użycie niejawnie i jawnie typizowanych wyrażeń deklaracji zarówno dla zmiennych, jak i odrzucanych w przypisaniu dekonstrukcyjnym. simple_name
_
jest odpowiednikiemvar _
, gdy nie znaleziono deklaracji_
.void M1(out int i) { ... } void M2(string _) { M1(out _); // Error: `_` is a string M1(out var _); }
W tych przykładach pokazano użycie
var _
w celu zapewnienia niejawnie typizowanego odrzucenia, gdy_
jest niedostępna, ponieważ wyznacza zmienną w otaczającym zakresie.koniec przykładu
12.18 Operator warunkowy
Operator ?:
jest nazywany operatorem warunkowym. Czasami nazywa się go również operatorem ternarnym.
conditional_expression
: null_coalescing_expression
| null_coalescing_expression '?' expression ':' expression
| null_coalescing_expression '?' 'ref' variable_reference ':'
'ref' variable_reference
;
Wyrażenie throw (§12.16) nie jest dozwolone w operatorze warunkowym, jeśli ref
jest obecne.
Wyrażenie warunkowe formularza b ? x : y
najpierw oblicza warunek b
. Następnie, jeśli b
jest true
, x
jest oceniany i staje się wynikiem operacji. W przeciwnym razie y
jest obliczane i stanowi wynik operacji. Wyrażenie warunkowe nigdy nie ocenia zarówno x
, jak i y
.
Operator warunkowy jest asocjacyjny od prawej do lewej, co oznacza, że operacje są grupowane od prawej do lewej.
Przykład: wyrażenie w formie
a ? b : c ? d : e
jest obliczane jakoa ? b : (c ? d : e)
. koniec przykładu
Pierwszy operand operatora ?:
jest wyrażeniem, które można niejawnie przekonwertować na bool
lub wyrażenie typu implementujące operator true
. Jeśli żadne z tych wymagań nie zostanie spełnione, wystąpi błąd czasu kompilacji.
Jeśli ref
jest obecny:
- Powinna istnieć konwersja tożsamości między typami dwóch variable_reference, a typ wyniku może być dowolnym z tych typów. Jeśli dowolny typ jest
dynamic
, wnioskowanie typu preferujedynamic
(§8.7). Jeśli dowolny z typów jest typem krotki (§8.3.11), wnioskowanie typu uwzględnia nazwy elementów, gdy nazwy te są zgodne w tej samej pozycji porządkowej w obu krotkach. - Wynik jest odwołaniem do zmiennej, która jest zapisywalna, jeśli oba variable_references są możliwe do zapisu.
Uwaga: gdy
ref
jest obecny, wyrażenie warunkowe zwraca odwołanie do zmiennej, które można przypisać do zmiennej referencyjnej przy użyciu operatora= ref
lub przekazane jako parametr referencyjny/wejściowy/wyjściowy. uwaga końcowa
Jeśli ref
nie istnieje, drugi i trzeci operandy, x
i y
operatora ?:
kontrolują typ wyrażenia warunkowego:
- Jeśli
x
ma typX
, ay
ma typY
,- Jeśli istnieje konwersja identycznościowa między
X
aY
, wynikiem jest najlepszy wspólny typ zbioru wyrażeń (§12.6.3.15). Jeśli dowolny typ jestdynamic
, wnioskowanie typu preferujedynamic
(§8.7). Jeśli dowolny z typów jest typem krotki (§8.3.11), wnioskowanie typu uwzględnia nazwy elementów, gdy nazwy te są zgodne w tej samej pozycji porządkowej w obu krotkach. - W przeciwnym razie, jeśli konwersja niejawna (§10.2) istnieje z
X
doY
, ale nie zY
doX
,Y
jest typem wyrażenia warunkowego. - W przeciwnym razie, jeśli istnieje niejawna konwersja wyliczenia (§10.2.4) z
X
doY
,Y
jest typem wyrażenia warunkowego. - W przeciwnym razie, jeśli istnieje niejawna konwersja wyliczenia (§10.2.4) z
Y
doX
,X
jest typem wyrażenia warunkowego. - W przeciwnym razie, jeśli konwersja niejawna (§10.2) istnieje z
Y
doX
, ale nie zX
doY
,X
jest typem wyrażenia warunkowego. - W przeciwnym razie nie można określić typu wyrażenia i występuje błąd czasu kompilacji.
- Jeśli istnieje konwersja identycznościowa między
- Jeśli tylko jeden z
x
iy
ma typ, a zarównox
, jak iy
są niejawnie konwertowane na ten typ, jest to typ wyrażenia warunkowego. - W przeciwnym razie nie można określić typu wyrażenia i występuje błąd czasu kompilacji.
Przetwarzanie w czasie wykonywania wyrażenia warunkowego ref w postaci b ? ref x : ref y
składa się z następujących kroków:
- Najpierw
b
jest obliczana, a wartośćbool
dlab
jest określana:- Jeśli istnieje niejawna konwersja typu
b
nabool
, ta niejawna konwersja jest wykonywana w celu wygenerowania wartościbool
. - W przeciwnym razie
operator true
, którą definiuje typb
, zostaje wywołana w celu utworzenia wartościbool
.
- Jeśli istnieje niejawna konwersja typu
- Jeśli wartość
bool
wygenerowana przez powyższy krok jesttrue
, zostanie obliczonax
, a wynikowe odwołanie do zmiennej stanie się wynikiem wyrażenia warunkowego. - W przeciwnym razie
y
jest oceniane, a odniesienie do zmiennej wynikowej staje się wynikiem wyrażenia warunkowego.
Przetwarzanie w trakcie działania wyrażenia warunkowego w postaci b ? x : y
składa się z następujących kroków:
- Najpierw
b
jest obliczana, a wartośćbool
dlab
jest określana:- Jeśli istnieje niejawna konwersja typu
b
nabool
, ta niejawna konwersja jest wykonywana w celu wygenerowania wartościbool
. - W przeciwnym razie
operator true
, którą definiuje typb
, zostaje wywołana w celu utworzenia wartościbool
.
- Jeśli istnieje niejawna konwersja typu
- Jeśli wartość
bool
wygenerowana przez powyższy krok jesttrue
,x
jest obliczana i konwertowana na typ wyrażenia warunkowego, a to staje się wynikiem wyrażenia warunkowego. - W przeciwnym razie
y
jest obliczana i konwertowana na typ wyrażenia warunkowego, a to staje się wynikiem wyrażenia warunkowego.
12.19 Anonimowe wyrażenia funkcji
12.19.1 Ogólne
Funkcja anonimowa jest wyrażeniem reprezentującym definicję metody "w wierszu". Funkcja anonimowa nie ma wartości ani typu w sobie, ale jest konwertowana na zgodny typ delegata lub drzewa wyrażeń. Ocena konwersji funkcji anonimowej zależy od typu docelowego konwersji: jeśli jest to typ delegata, konwersja jest obliczana na wartość delegata odwołującą się do metody zdefiniowanej przez funkcję anonimową. Jeśli jest to typ drzewa wyrażeń, konwersja jest obliczana na drzewo wyrażeń, które reprezentuje strukturę metody jako strukturę obiektu.
Uwaga: Ze względów historycznych istnieją dwa składniowe warianty funkcji anonimowych, a mianowicie wyrażenia lambdai wyrażenia metody anonimowej. Dla prawie wszystkich celów lambda_expressionsą bardziej zwięzłe i wyraziste niż anonymous_method_expression, które pozostają w języku ze względu na zgodność wsteczną. uwaga końcowa
lambda_expression
: 'async'? anonymous_function_signature '=>' anonymous_function_body
;
anonymous_method_expression
: 'async'? 'delegate' explicit_anonymous_function_signature? block
;
anonymous_function_signature
: explicit_anonymous_function_signature
| implicit_anonymous_function_signature
;
explicit_anonymous_function_signature
: '(' explicit_anonymous_function_parameter_list? ')'
;
explicit_anonymous_function_parameter_list
: explicit_anonymous_function_parameter
(',' explicit_anonymous_function_parameter)*
;
explicit_anonymous_function_parameter
: anonymous_function_parameter_modifier? type identifier
;
anonymous_function_parameter_modifier
: 'ref'
| 'out'
| 'in'
;
implicit_anonymous_function_signature
: '(' implicit_anonymous_function_parameter_list? ')'
| implicit_anonymous_function_parameter
;
implicit_anonymous_function_parameter_list
: implicit_anonymous_function_parameter
(',' implicit_anonymous_function_parameter)*
;
implicit_anonymous_function_parameter
: identifier
;
anonymous_function_body
: null_conditional_invocation_expression
| expression
| 'ref' variable_reference
| block
;
Przy rozpoznawaniu anonymous_function_body, jeśli stosowane są zarówno null_conditional_invocation_expression, jak i wyrażenie alternatywy, należy wybrać pierwszy.
Uwaga: Nakładanie się oraz priorytet pomiędzy alternatywami są wyłącznie dla wygody opisowej; reguły gramatyczne można opracować, aby usunąć nakładania się. ANTLR i inne systemy gramatyczne przyjmują tę samą wygodę i dlatego anonymous_function_body ma określone semantyki automatycznie. uwaga końcowa
Uwaga: w przypadku traktowania jako wyrażenia formuła składniowa, taka jak
x?.M()
, byłaby błędem, jeśli typ wynikuM
jestvoid
(§12.8.13). Jednak w przypadku traktowania jako null_conditional_invocation_expressiontyp wyniku może byćvoid
. uwaga końcowa
Przykład: typ wyniku
List<T>.Reverse
tovoid
. W poniższym kodzie treść wyrażenia anonimowego jest null_conditional_invocation_expression, więc nie jest to błąd.Action<List<int>> a = x => x?.Reverse();
koniec przykładu
Operator =>
ma takie samo pierwszeństwo jak przypisanie (=
) i jest prawostronnie łączny.
Funkcja anonimowa z modyfikatorem async
jest funkcją asynchronikową i jest zgodna z regułami opisanymi w §15.15.
Parametry funkcji anonimowej w postaci lambda_expression mogą być jawnie lub niejawnie wpisywane. Na liście jawnie wpisanych parametrów typ każdego parametru jest jawnie określony. W niejawnie typizowanej liście parametrów typy parametrów są domyślnie określane na podstawie kontekstu, w którym występuje funkcja anonimowa — w szczególności, gdy funkcja anonimowa jest zamieniana na odpowiedni typ delegata lub typ drzewa wyrażeń, ten typ określa typy parametrów (§10.7).
W lambda_expression z jednym, niejawnie wpisanym parametrem nawiasy mogą zostać pominięte z listy parametrów. Innymi słowy, anonimowa funkcja formularza
( «param» ) => «expr»
można skrócić do
«param» => «expr»
Lista parametrów funkcji anonimowej w postaci anonymous_method_expression jest opcjonalna. W przypadku podania parametrów należy jawnie wpisać. Jeśli nie, funkcja anonimowa może zostać przekształcona w delegata z dowolną listą parametrów pod warunkiem, że nie zawiera ona parametrów wyjściowych.
Ciało bloku funkcji anonimowej jest zawsze osiągalne (§13.2).
Przykład: poniżej przedstawiono kilka przykładów funkcji anonimowych:
x => x + 1 // Implicitly typed, expression body x => { return x + 1; } // Implicitly typed, block body (int x) => x + 1 // Explicitly typed, expression body (int x) => { return x + 1; } // Explicitly typed, block body (x, y) => x * y // Multiple parameters () => Console.WriteLine() // No parameters async (t1,t2) => await t1 + await t2 // Async delegate (int x) { return x + 1; } // Anonymous method expression delegate { return 1 + 1; } // Parameter list omitted
koniec przykładu
Zachowanie lambda_expressions i anonymous_method_expressions jest takie samo, z wyjątkiem następujących kwestii:
- anonymous_method_expressionumożliwia całkowite pominięcie listy parametrów, co pozwala na konwersję do typów delegatów z dowolną listą parametrów wartości.
- lambda_expressionpozwala na pomijanie i wnioskowanie typów parametrów, natomiast anonymous_method_expressionwymaga ich jawnego określenia.
- Treść lambda_expression może być wyrażeniem lub blokiem, natomiast treść anonymous_method_expression jest blokiem.
- Tylko lambda_expressionmają konwersje na zgodne typy drzewa wyrażeń (§8.6).
12.19.2 Sygnatury funkcji anonimowych
anonymous_function_signature określa nazwy i opcjonalnie typy parametrów dla funkcji anonimowej. Zakres parametrów funkcji anonimowej to anonymous_function_body (§7.7). Razem z listą parametrów (jeśli jest podana) ciało metody anonimowej stanowi przestrzeń deklaracji (§7.3). W związku z tym nazwanie parametru funkcji anonimowej tak samo, jak zmiennej lokalnej, stałej lokalnej lub innego parametru, którego zakres obejmuje anonymous_method_expression lub lambda_expression, stanowi błąd czasu kompilacji.
Jeśli funkcja anonimowa ma explicit_anonymous_function_signature, zestaw zgodnych typów delegatów i typów drzewa wyrażeń jest ograniczony do tych, które mają te same typy parametrów i modyfikatory w tej samej kolejności (§10.7). W przeciwieństwie do konwersji grup metod (§10.8), kontrawariancja anonimowych typów parametrów funkcji nie jest obsługiwana. Jeśli funkcja anonimowa nie ma anonymous_function_signature, wtedy zestaw zgodnych typów delegatów i drzew wyrażeń jest ograniczony do tych, które nie mają parametrów wyjściowych.
Należy pamiętać, że anonymous_function_signature nie może zawierać atrybutów ani tablicy parametrów. Niemniej jednak anonymous_function_signature może być zgodna z typem delegata, którego lista argumentów zawiera tablicę parametrów.
Należy również pamiętać, że konwersja na typ drzewa wyrażeń, nawet jeśli jest zgodna, może nadal zakończyć się niepowodzeniem w czasie kompilacji (§8.6).
12.19.3 Anonimowe jednostki funkcji
Treść (wyrażenie lub blok ) funkcji anonimowej podlega następującym regułom:
- Jeśli funkcja anonimowa zawiera podpis, parametry określone w podpisie są dostępne w treści. Jeśli funkcja anonimowa nie ma podpisu, można ją przekonwertować na typ delegata lub typ wyrażenia o parametrach (§10.7), ale nie można uzyskać dostępu do parametrów w treści.
- Z wyjątkiem parametrów by-reference określonych w podpisie (jeśli istnieje) najbliższej otaczającej funkcji anonimowej, jest to błąd czasu kompilacji dla treści dostępu do parametru by-reference.
- Z wyjątkiem parametrów określonych w sygnaturze (jeśli istnieje) najbliższej otaczającej funkcji anonimowej, w ciele występuje błąd kompilacji, gdy próbuje się uzyskać dostęp do parametru typu
ref struct
. - Gdy typ
this
jest typem struktury, próba uzyskania dostępu dothis
stanowi błąd czasu kompilacji. To prawda, niezależnie od tego, czy dostęp jest wyraźny (jak wthis.x
), czy niejawny (jak wx
, gdziex
jest elementem instancji struktury). Ta reguła po prostu zabrania takiego dostępu i nie ma wpływu na to, czy wyszukiwanie zwraca członka struktury. - Ciało ma dostęp do zmiennych zewnętrznych (§12.19.6) funkcji anonimowej. Dostęp do zmiennej zewnętrznej odniesie się do egzemplarza zmiennej, który jest aktywny w momencie oceny wyrażenia lambda_expression lub anonymous_method_expression (§12.19.7).
- Jest to błąd czasu kompilacji dla treści zawierającej instrukcję
goto
, instrukcjębreak
lub instrukcjęcontinue
, której element docelowy znajduje się poza treścią lub w treści zawartej funkcji anonimowej. - Instrukcja
return
w treści zwraca kontrolę z wywołania najbliższej otaczającej funkcji anonimowej, a nie z otaczającej funkcji członkowskiej.
Jest jawnie nieokreślone, czy istnieje jakikolwiek sposób wykonania bloku funkcji anonimowej inny niż poprzez ewaluację i wywołanie lambda_expression lub anonymous_method_expression. W szczególności kompilator może zdecydować się na zaimplementowanie funkcji anonimowej przez synchronizowanie co najmniej jednej nazwanej metody lub typów. Nazwy wszelkich takich syntetyzowanych elementów mają postać zarezerwowaną do użytku kompilatora (§6.4.3).
12.19.4 Rozpoznawanie przeciążenia
Funkcje anonimowe w liście argumentów uczestniczą w wywnioskowaniu typów i rozwiązywaniu przeciążenia. Zapoznaj się z §12.6.3 i §12.6.4, aby uzyskać dokładne zasady.
Przykład: Poniższy przykład ilustruje wpływ funkcji anonimowych na rozpoznawanie przeciążeń.
class ItemList<T> : List<T> { public int Sum(Func<T, int> selector) { int sum = 0; foreach (T item in this) { sum += selector(item); } return sum; } public double Sum(Func<T, double> selector) { double sum = 0; foreach (T item in this) { sum += selector(item); } return sum; } }
Klasa
ItemList<T>
ma dwie metodySum
. Każdy przyjmuje argumentselector
, który wyodrębnia wartość do zsumowania z elementu listy. Wyodrębniona wartość może być alboint
, albodouble
, a wynikowa suma jest odpowiednio alboint
, albodouble
.Metody
Sum
mogą na przykład służyć do obliczania sum z listy wierszy szczegółowych w zamówieniu.class Detail { public int UnitCount; public double UnitPrice; ... } class A { void ComputeSums() { ItemList<Detail> orderDetails = GetOrderDetails( ... ); int totalUnits = orderDetails.Sum(d => d.UnitCount); double orderTotal = orderDetails.Sum(d => d.UnitPrice * d.UnitCount); ... } ItemList<Detail> GetOrderDetails( ... ) { ... } }
W pierwszym wywołaniu
orderDetails.Sum
mają zastosowanie obie metodySum
, ponieważ funkcja anonimowad => d.UnitCount
jest zgodna zarówno zFunc<Detail,int>
, jak iFunc<Detail,double>
. Mechanizm rozwiązywania przeciążeń wybiera pierwszą metodęSum
, ponieważ konwersja naFunc<Detail,int>
jest lepsza niż konwersja naFunc<Detail,double>
.W drugim wywołaniu
orderDetails.Sum
ma zastosowanie tylko druga metodaSum
, ponieważ funkcja anonimowad => d.UnitPrice * d.UnitCount
generuje wartość typudouble
. W związku z tym rozpoznawanie przeciążeń wybiera drugą metodęSum
dla tego wywołania.koniec przykładu
12.19.5 Funkcje anonimowe i powiązanie dynamiczne
Funkcja anonimowa nie może być odbiornikiem, argumentem ani operandem operacji dynamicznie powiązanej.
12.19.6 Zmienne zewnętrzne
12.19.6.1 Ogólne
Każda lokalna zmienna, parametr wartości lub tablica parametrów, której zakres zawiera lambda_expression lub anonymous_method_expression, jest nazywany zmienną zewnętrzną funkcji anonimowej. W składowej funkcji wystąpienia klasy ta wartość jest traktowana jako parametr wartości i jest zewnętrzną zmienną dowolnej funkcji anonimowej zawartej w składowej funkcji.
12.19.6.2 Przechwycone zmienne zewnętrzne
Gdy zmienna zewnętrzna jest przywoływana przez funkcję anonimową, mówi się, że zmienna zewnętrzna została przechwycona przez funkcję anonimową. Zazwyczaj okres istnienia zmiennej lokalnej jest ograniczony do wykonywania bloku lub instrukcji, z którą jest skojarzona (§9.2.9.1). Jednak okres istnienia przechwyconej zmiennej zewnętrznej jest dłuższy co najmniej do momentu, aż delegat lub drzewo wyrażeń utworzone na podstawie funkcji anonimowej będzie kwalifikować się do zbierania śmieci.
Przykład: w przykładzie
delegate int D(); class Test { static D F() { int x = 0; D result = () => ++x; return result; } static void Main() { D d = F(); Console.WriteLine(d()); Console.WriteLine(d()); Console.WriteLine(d()); } }
lokalna zmienna
x
jest przechwytywana przez funkcję anonimową, a okres istnieniax
zostanie przedłużony przynajmniej do czasu, aż delegat zwrócony zF
będzie kwalifikował się do zbierania śmieci. Ponieważ każde wywołanie funkcji anonimowej operuje na tym samym wystąpieniux
, wynik działania przykładu to:1 2 3
koniec przykładu
Gdy zmienna lokalna lub parametr wartości jest przechwytywany przez funkcję anonimową, zmienna lokalna lub parametr nie jest już uznawana za zmienną stałą (§23.4), ale jest zamiast tego uważana za zmienną zmienną do przenoszenia. Jednak przechwycone zmienne zewnętrzne nie mogą być używane w instrukcji fixed
(§23.7), więc nie można użyć adresu przechwyconej zmiennej zewnętrznej.
Uwaga: w przeciwieństwie do nieuchwyconej zmiennej, przechwycona zmienna lokalna może być jednocześnie widoczna dla wielu wątków wykonywania. uwaga końcowa
12.19.6.3 Utworzenie wystąpienia zmiennych lokalnych
Zmienna lokalna jest uważana za zainicjalizowana, gdy wykonanie wchodzi w zakres zmiennej.
Przykład: na przykład, gdy wywoływana jest następująca metoda, zmienna lokalna
x
jest tworzona i inicjowana trzy razy — raz dla każdej iteracji pętli.static void F() { for (int i = 0; i < 3; i++) { int x = i * 2 + 1; ... } }
Jednak przeniesienie deklaracji
x
poza pętlę powoduje utworzenie pojedynczego egzemplarzax
:static void F() { int x; for (int i = 0; i < 3; i++) { x = i * 2 + 1; ... } }
koniec przykładu
W przypadku braku przechwycenia nie ma możliwości dokładnego obserwowania, jak często jest tworzone wystąpienie zmiennej lokalnej — ponieważ okresy istnienia wystąpień wystąpień są rozłączne, istnieje możliwość, aby każde wystąpienie po prostu używało tej samej lokalizacji przechowywania. Jednak gdy funkcja anonimowa przechwytuje zmienną lokalną, efekty instancjonowania stają się widoczne.
Przykład: przykład
delegate void D(); class Test { static D[] F() { D[] result = new D[3]; for (int i = 0; i < 3; i++) { int x = i * 2 + 1; result[i] = () => Console.WriteLine(x); } return result; } static void Main() { foreach (D d in F()) { d(); } } }
generuje dane wyjściowe:
1 3 5
Jednak po przeniesieniu deklaracji
x
poza pętlą:delegate void D(); class Test { static D[] F() { D[] result = new D[3]; int x; for (int i = 0; i < 3; i++) { x = i * 2 + 1; result[i] = () => Console.WriteLine(x); } return result; } static void Main() { foreach (D d in F()) { d(); } } }
dane wyjściowe to:
5 5 5
Należy pamiętać, że kompilatorowi wolno (ale nie jest wymagane) dokonać optymalizacji trzech wystąpień w jedną instancję delegata (§10.7.2).
koniec przykładu
Jeśli pętla for deklaruje zmienną iteracji, ta zmienna jest uważana za zadeklarowaną poza pętlą.
Przykład: Zatem, jeśli przykład zostanie zmieniony w celu uchwycenia zmiennej iteracyjnej:
delegate void D(); class Test { static D[] F() { D[] result = new D[3]; for (int i = 0; i < 3; i++) { result[i] = () => Console.WriteLine(i); } return result; } static void Main() { foreach (D d in F()) { d(); } } }
przechwytywane jest tylko jedno wystąpienie zmiennej iteracji, co powoduje wygenerowanie danych wyjściowych:
3 3 3
koniec przykładu
Możliwe jest, że delegaty funkcji anonimowych będą współdzielić niektóre przechwycone zmienne, ale mieć oddzielne wystąpienia innych.
Przykład: na przykład jeśli
F
zostanie zmieniona nastatic D[] F() { D[] result = new D[3]; int x = 0; for (int i = 0; i < 3; i++) { int y = 0; result[i] = () => Console.WriteLine($"{++x} {++y}"); } return result; }
trzej delegaci przechwytują to samo wystąpienie
x
, ale oddzielne wystąpieniay
, a dane wyjściowe to:1 1 2 1 3 1
koniec przykładu
Oddzielne funkcje anonimowe mogą przechwytywać to samo wystąpienie zmiennej zewnętrznej.
Przykład: w przykładzie:
delegate void Setter(int value); delegate int Getter(); class Test { static void Main() { int x = 0; Setter s = (int value) => x = value; Getter g = () => x; s(5); Console.WriteLine(g()); s(10); Console.WriteLine(g()); } }
dwie funkcje anonimowe przechwytują to samo wystąpienie zmiennej lokalnej
x
, a tym samym mogą "komunikować się" za pośrednictwem tej zmiennej. Dane wyjściowe przykładu to:5 10
koniec przykładu
12.19.7 Ocena anonimowych wyrażeń funkcji
Funkcja anonimowa F
zawsze jest konwertowana na typ delegata D
lub typ drzewa wyrażeń E
, bezpośrednio lub przez wykonanie wyrażenia tworzenia delegata new D(F)
. Ta konwersja określa wynik funkcji anonimowej zgodnie z opisem w §10.7.
Przykład implementacji 12.19.8
Ten podpunkt jest informacyjny.
W tym podpunkcie opisano możliwą implementację przekształceń funkcji anonimowych w kontekście innych konstrukcji C#. Implementacja opisana w tym miejscu opiera się na tych samych zasadach używanych przez kompilator komercyjnego języka C#, ale nie jest to implementacja upoważniona ani jedyną możliwą. Tylko krótko wspomina konwersje na drzewa wyrażeń, ponieważ ich dokładna semantyka wykracza poza zakres tej specyfikacji.
Pozostała część tej podklauzuli zawiera kilka przykładów kodu, które zawierają funkcje anonimowe o różnorodnych cechach. Dla każdego przykładu jest udostępniane odpowiednie tłumaczenie kodu, które używa tylko innych konstrukcji języka C#. W przykładach identyfikator D
jest używany do reprezentacji następującego typu delegata:
public delegate void D();
Najprostszą formą funkcji anonimowej jest taka, która nie przechwytuje żadnych zmiennych zewnętrznych:
delegate void D();
class Test
{
static void F()
{
D d = () => Console.WriteLine("test");
}
}
Można to przetłumaczyć na wystąpienie delegata, które odwołuje się do metody statycznej wygenerowanej przez kompilator, w której znajduje się kod funkcji anonimowej:
delegate void D();
class Test
{
static void F()
{
D d = new D(__Method1);
}
static void __Method1()
{
Console.WriteLine("test");
}
}
W poniższym przykładzie funkcja anonimowa odnosi się do członków instancji this
:
delegate void D();
class Test
{
int x;
void F()
{
D d = () => Console.WriteLine(x);
}
}
Można to przetłumaczyć na metodę wystąpienia wygenerowanego przez kompilator zawierający kod funkcji anonimowej:
delegate void D();
class Test
{
int x;
void F()
{
D d = new D(__Method1);
}
void __Method1()
{
Console.WriteLine(x);
}
}
W tym przykładzie funkcja anonimowa przechwytuje zmienną lokalną:
delegate void D();
class Test
{
void F()
{
int y = 123;
D d = () => Console.WriteLine(y);
}
}
Okres istnienia zmiennej lokalnej musi być teraz rozszerzony do co najmniej okresu istnienia delegata funkcji anonimowej. Można to osiągnąć przez "wciągnąć" zmienną lokalną do pola klasy wygenerowanej przez kompilator. Utworzenie wystąpienia zmiennej lokalnej (§12.19.6.3), a następnie odpowiada utworzeniu wystąpienia klasy wygenerowanej przez kompilator, a uzyskiwanie dostępu do zmiennej lokalnej odpowiada uzyskiwaniu dostępu do pola w wystąpieniu klasy wygenerowanej przez kompilator. Ponadto funkcja anonimowa staje się metodą wystąpienia klasy generowanej przez kompilator:
delegate void D();
class Test
{
void F()
{
__Locals1 __locals1 = new __Locals1();
__locals1.y = 123;
D d = new D(__locals1.__Method1);
}
class __Locals1
{
public int y;
public void __Method1()
{
Console.WriteLine(y);
}
}
}
Na koniec następująca funkcja anonimowa przechwytuje this
, a także dwie zmienne lokalne z różnymi okresami istnienia:
delegate void D();
class Test
{
int x;
void F()
{
int y = 123;
for (int i = 0; i < 10; i++)
{
int z = i * 2;
D d = () => Console.WriteLine(x + y + z);
}
}
}
W tym miejscu jest tworzona klasa wygenerowana przez kompilator dla każdego bloku, w którym przechwytywane są zmienne lokalne, tak aby zmienne lokalne w różnych blokach mogły mieć niezależne czasy życia. Wystąpienie __Locals2
, klasy wygenerowanej przez kompilator dla bloku wewnętrznego, zawiera zmienną lokalną z
oraz pole odnoszące się do wystąpienia __Locals1
. Wystąpienie __Locals1
, klasy wygenerowanej przez kompilator dla bloku zewnętrznego, zawiera zmienną lokalną y
i pole, które odwołuje się do this
otaczającego członka funkcji. Dzięki tym strukturom danych można uzyskać dostęp do wszystkich przechwyconych zmiennych zewnętrznych za pośrednictwem wystąpienia __Local2
, a kod funkcji anonimowej może zostać zaimplementowany jako metoda wystąpienia tej klasy.
delegate void D();
class Test
{
int x;
void F()
{
__Locals1 __locals1 = new __Locals1();
__locals1.__this = this;
__locals1.y = 123;
for (int i = 0; i < 10; i++)
{
__Locals2 __locals2 = new __Locals2();
__locals2.__locals1 = __locals1;
__locals2.z = i * 2;
D d = new D(__locals2.__Method1);
}
}
class __Locals1
{
public Test __this;
public int y;
}
class __Locals2
{
public __Locals1 __locals1;
public int z;
public void __Method1()
{
Console.WriteLine(__locals1.__this.x + __locals1.y + z);
}
}
}
Ta sama technika zastosowana tutaj do przechwytywania zmiennych lokalnych może być również używana podczas konwertowania funkcji anonimowych na drzewa wyrażeń: odwołania do obiektów generowanych przez kompilatora mogą być przechowywane w drzewie wyrażeń, a dostęp do zmiennych lokalnych może być reprezentowany jako dostęp do pól na tych obiektach. Zaletą tego podejścia jest to, że umożliwia współużytkowanie lokalnych zmiennych "wyprowadzonych" między delegatami a drzewami wyrażeń.
Koniec tekstu informacyjnego.
12.20 Wyrażenia zapytań
12.20.1 Ogólne
Wyrażenia zapytań zapewniają składnię zintegrowaną ze językiem dla zapytań podobnych do języków zapytań relacyjnych i hierarchicznych, takich jak SQL i XQuery.
query_expression
: from_clause query_body
;
from_clause
: 'from' type? identifier 'in' expression
;
query_body
: query_body_clauses? select_or_group_clause query_continuation?
;
query_body_clauses
: query_body_clause
| query_body_clauses query_body_clause
;
query_body_clause
: from_clause
| let_clause
| where_clause
| join_clause
| join_into_clause
| orderby_clause
;
let_clause
: 'let' identifier '=' expression
;
where_clause
: 'where' boolean_expression
;
join_clause
: 'join' type? identifier 'in' expression 'on' expression
'equals' expression
;
join_into_clause
: 'join' type? identifier 'in' expression 'on' expression
'equals' expression 'into' identifier
;
orderby_clause
: 'orderby' orderings
;
orderings
: ordering (',' ordering)*
;
ordering
: expression ordering_direction?
;
ordering_direction
: 'ascending'
| 'descending'
;
select_or_group_clause
: select_clause
| group_clause
;
select_clause
: 'select' expression
;
group_clause
: 'group' expression 'by' expression
;
query_continuation
: 'into' identifier query_body
;
Wyrażenie zapytania rozpoczyna się od klauzuli from
i kończy się klauzulą select
lub group
. Po początkowej klauzuli from
może występować zero lub więcej klauzul from
, let
, where
, join
lub orderby
. Każda klauzula from
jest generatorem wprowadzającym zmienną zakresu , która przechodzi przez elementy sekwencji . Każda klauzula let
wprowadza zmienną zakresu reprezentującą wartość obliczoną za pomocą poprzednich zmiennych zakresu. Każda klauzula where
to filtr wykluczający elementy z wyniku. Każda klauzula join
porównuje określone klucze sekwencji źródłowej z kluczami innej sekwencji, co daje pasujące pary. Każda klauzula orderby
zmienia kolejność elementów zgodnie z określonymi kryteriami. Końcowa klauzula select
lub group
określa kształt wyniku pod względem zmiennych zakresu. Na koniec można użyć klauzuli into
do "łączenia" zapytań, traktując wyniki jednego zapytania jako generator w kolejnym zapytaniu.
12.20.2 Niejednoznaczności w wyrażeniach zapytań
Wyrażenia zapytań używają kilku słów kluczowych kontekstowych (§6.4.4): ascending
, by
, descending
, equals
, from
, group
, into
, join
, let
, on
, orderby
, select
i where
.
Aby uniknąć niejednoznaczności, które mogą wynikać z użycia tych identyfikatorów zarówno jako słów kluczowych, jak i prostych nazw tych identyfikatorów są traktowane jako słowa kluczowe w dowolnym miejscu w wyrażeniu zapytania, chyba że są one poprzedzone prefiksem "@
" (§6.4.4), w którym przypadku są traktowane jako identyfikatory. W tym celu wyrażenie zapytania to dowolne wyrażenie rozpoczynające się od "from
identyfikatora", a następnie dowolny token z wyjątkiem ";
", "=
" lub ",
".
12.20.3 Tłumaczenie wyrażenia zapytania
12.20.3.1 Ogólne
Język C# nie określa semantyki wykonywania wyrażeń zapytań. Wyrażenia zapytania są tłumaczone na wywołania metod, które są zgodne ze wzorcem wyrażenia zapytania (§12.20.4). W szczególności wyrażenia zapytań są tłumaczone na wywołania metod o nazwie Where
, Select
, SelectMany
, Join
, GroupJoin
, OrderBy
, OrderByDescending
, ThenBy
, ThenByDescending
, GroupBy
i Cast
. Te metody powinny mieć określone podpisy i typy zwracane, zgodnie z opisem w §12.20.4. Metody te mogą być metodami instancji obiektu, do którego odwołuje się zapytanie, lub metodami rozszerzenia, które są definiowane poza obiektem. Te metody implementują rzeczywiste wykonanie zapytania.
Tłumaczenie z wyrażeń zapytania na wywołania metody jest mapowaniem składniowym, które występuje przed wykonaniem jakiegokolwiek powiązania typu lub rozpoznawania przeciążenia. Po tłumaczeniu wyrażeń zapytań wywołania metody wynikowej są przetwarzane jako zwykłe wywołania metod, co może z kolei spowodować wykrycie błędów czasu kompilacji. Te warunki błędu obejmują metody, które nie istnieją, argumenty nieprawidłowych typów i metody ogólne, w których wnioskowanie typu kończy się niepowodzeniem.
Wyrażenie zapytania jest przetwarzane wielokrotnie przez zastosowanie następujących tłumaczeń aż do momentu, gdy dalsze redukcje nie są już możliwe. Tłumaczenia są wymienione w kolejności aplikacji: każda sekcja zakłada, że tłumaczenia w poprzednich sekcjach zostały wykonane wyczerpująco, a po wyczerpaniu sekcja nie zostanie później ponownie podjęta w przetwarzaniu tego samego wyrażenia zapytania.
Błędem czasu kompilacji w wyrażeniu zapytania jest uwzględnienie przypisania do zmiennej zakresu lub użycie zmiennej zakresu jako argumentu dla parametru referencyjnego lub wyjściowego.
Niektóre tłumaczenia wprowadzają zmienne zakresu z przezroczystymi identyfikatorami oznaczone przez *. Zostały one opisane w §12.20.3.8.
12.20.3.2 Wyrażenia zapytań z kontynuacjami
Wyrażenie zapytania z kontynuacją po treści zapytania
from «x1» in «e1» «b1» into «x2» «b2»
jest tłumaczony na
from «x2» in ( from «x1» in «e1» «b1» ) «b2»
Tłumaczenia w poniższych sekcjach zakładają, że zapytania nie mają kontynuacji.
Przykład: Przykład:
from c in customers group c by c.Country into g select new { Country = g.Key, CustCount = g.Count() }
jest tłumaczony na:
from g in (from c in customers group c by c.Country) select new { Country = g.Key, CustCount = g.Count() }
ostateczne tłumaczenie, o którym mowa:
customers. GroupBy(c => c.Country). Select(g => new { Country = g.Key, CustCount = g.Count() })
koniec przykładu
12.20.3.3 Jawne typy zmiennych zakresu
Klauzula from
, która jawnie określa typ zmiennej zakresu
from «T» «x» in «e»
jest tłumaczony na
from «x» in ( «e» ) . Cast < «T» > ( )
Klauzula join
, która jawnie określa typ zmiennej zakresu
join «T» «x» in «e» on «k1» equals «k2»
jest tłumaczony na
join «x» in ( «e» ) . Cast < «T» > ( ) on «k1» equals «k2»
Tłumaczenia w poniższych sekcjach zakładają, że zapytania nie mają jawnych typów zmiennych zakresu.
Przykład: przykład
from Customer c in customers where c.City == "London" select c
jest tłumaczony na
from c in (customers).Cast<Customer>() where c.City == "London" select c
końcowe tłumaczenie, które jest
customers. Cast<Customer>(). Where(c => c.City == "London")
koniec przykładu
Uwaga: jawne typy zmiennych zakresu są przydatne do wykonywania zapytań dotyczących kolekcji, które implementują interfejs
IEnumerable
niegeneryczny, ale nie ogólny interfejsIEnumerable<T>
. W powyższym przykładzie byłoby tak, gdyby klienci byli typuArrayList
. uwaga końcowa
12.20.3.4 Zdegenerowane wyrażenia zapytań
Wyrażenie zapytania formularza
from «x» in «e» select «x»
jest tłumaczony na
( «e» ) . Select ( «x» => «x» )
Przykład: przykład
from c in customers select c
jest tłumaczony na
(customers).Select(c => c)
koniec przykładu
Zdegenerowane wyrażenie zapytania to takie, które w sposób trywialny wybiera elementy źródła.
Uwaga: późniejsze fazy tłumaczenia (§12.20.3.6 i §12.20.3.7) usuwają zgenerowane zapytania wprowadzone przez inne kroki tłumaczenia, zastępując je źródłem. Należy jednak upewnić się, że wynik wyrażenia zapytania nigdy nie jest obiektem źródłowym. W przeciwnym razie zwrócenie wyniku takiego zapytania może przypadkowo uwidocznić dane prywatne (np. tablicę elementów) do obiektu wywołującego. W związku z tym ten krok chroni degenerowane zapytania napisane bezpośrednio w kodzie źródłowym przez jawne wywołanie
Select
w źródle. Następnie do implementatorówSelect
i innych operatorów zapytań, aby upewnić się, że te metody nigdy nie zwracają samego obiektu źródłowego. uwaga końcowa
12.20.3.5 Od, let, where, join and orderby klauzule
Wyrażenie zapytania z drugą klauzulą from
, po której następuje klauzula select
from «x1» in «e1»
from «x2» in «e2»
select «v»
jest tłumaczony na
( «e1» ) . SelectMany( «x1» => «e2» , ( «x1» , «x2» ) => «v» )
Przykład: przykład
from c in customers from o in c.Orders select new { c.Name, o.OrderID, o.Total }
jest tłumaczony na
(customers). SelectMany(c => c.Orders, (c,o) => new { c.Name, o.OrderID, o.Total } )
koniec przykładu
Wyrażenie zapytania z drugą klauzulą from
, po której następuje treść zapytania Q
zawierające niepusty zestaw klauzul treści zapytania:
from «x1» in «e1»
from «x2» in «e2»
Q
jest tłumaczony na
from * in («e1») . SelectMany( «x1» => «e2» ,
( «x1» , «x2» ) => new { «x1» , «x2» } )
Q
Przykład: przykład
from c in customers from o in c.Orders orderby o.Total descending select new { c.Name, o.OrderID, o.Total }
jest tłumaczony na
from * in (customers). SelectMany(c => c.Orders, (c,o) => new { c, o }) orderby o.Total descending select new { c.Name, o.OrderID, o.Total }
końcowe tłumaczenie, które jest
customers. SelectMany(c => c.Orders, (c,o) => new { c, o }). OrderByDescending(x => x.o.Total). Select(x => new { x.c.Name, x.o.OrderID, x.o.Total })
gdzie
x
jest identyfikatorem wygenerowanym przez kompilator, który jest w przeciwnym razie niewidoczny i niedostępny.koniec przykładu
Wyrażenie let
wraz z poprzednią klauzulą from
:
from «x» in «e»
let «y» = «f»
...
jest tłumaczony na
from * in ( «e» ) . Select ( «x» => new { «x» , «y» = «f» } )
...
Przykład: przykład
from o in orders let t = o.Details.Sum(d => d.UnitPrice * d.Quantity) where t >= 1000 select new { o.OrderID, Total = t }
jest tłumaczony na
from * in (orders).Select( o => new { o, t = o.Details.Sum(d => d.UnitPrice * d.Quantity) }) where t >= 1000 select new { o.OrderID, Total = t }
końcowe tłumaczenie, które jest
orders .Select(o => new { o, t = o.Details.Sum(d => d.UnitPrice * d.Quantity) }) .Where(x => x.t >= 1000) .Select(x => new { x.o.OrderID, Total = x.t })
gdzie
x
jest identyfikatorem wygenerowanym przez kompilator, który jest w przeciwnym razie niewidoczny i niedostępny.koniec przykładu
Wyrażenie where
wraz z poprzednią klauzulą from
:
from «x» in «e»
where «f»
...
jest tłumaczony na
from «x» in ( «e» ) . Where ( «x» => «f» )
...
Klauzula join
, po której natychmiast następuje klauzula select
from «x1» in «e1»
join «x2» in «e2» on «k1» equals «k2»
select «v»
jest tłumaczony na
( «e1» ) . Join( «e2» , «x1» => «k1» , «x2» => «k2» , ( «x1» , «x2» ) => «v» )
Przykład: przykład
from c in customersh join o in orders on c.CustomerID equals o.CustomerID select new { c.Name, o.OrderDate, o.Total }
jest tłumaczony na
(customers).Join( orders, c => c.CustomerID, o => o.CustomerID, (c, o) => new { c.Name, o.OrderDate, o.Total })
koniec przykładu
Klauzula join
, po której następuje klauzula treści zapytania:
from «x1» in «e1»
join «x2» in «e2» on «k1» equals «k2»
...
jest tłumaczony na
from * in ( «e1» ) . Join(
«e2» , «x1» => «k1» , «x2» => «k2» ,
( «x1» , «x2» ) => new { «x1» , «x2» })
...
Klauzula join
-into
, po której natychmiast następuje klauzula select
from «x1» in «e1»
join «x2» in «e2» on «k1» equals «k2» into «g»
select «v»
jest tłumaczony na
( «e1» ) . GroupJoin( «e2» , «x1» => «k1» , «x2» => «k2» ,
( «x1» , «g» ) => «v» )
Klauzula join into
, po której następuje klauzula treści zapytania
from «x1» in «e1»
join «x2» in «e2» on «k1» equals «k2» into *g»
...
jest tłumaczony na
from * in ( «e1» ) . GroupJoin(
«e2» , «x1» => «k1» , «x2» => «k2» , ( «x1» , «g» ) => new { «x1» , «g» })
...
Przykład: przykład
from c in customers join o in orders on c.CustomerID equals o.CustomerID into co let n = co.Count() where n >= 10 select new { c.Name, OrderCount = n }
jest tłumaczony na
from * in (customers).GroupJoin( orders, c => c.CustomerID, o => o.CustomerID, (c, co) => new { c, co }) let n = co.Count() where n >= 10 select new { c.Name, OrderCount = n }
końcowe tłumaczenie, które jest
customers .GroupJoin( orders, c => c.CustomerID, o => o.CustomerID, (c, co) => new { c, co }) .Select(x => new { x, n = x.co.Count() }) .Where(y => y.n >= 10) .Select(y => new { y.x.c.Name, OrderCount = y.n })
gdzie
x
iy
to identyfikatory generowane przez kompilator, które w przeciwnym razie są niewidoczne i niedostępne.koniec przykładu
Klauzula orderby
i jej poprzednia klauzula from
:
from «x» in «e»
orderby «k1» , «k2» , ... , «kn»
...
jest tłumaczony na
from «x» in ( «e» ) .
OrderBy ( «x» => «k1» ) .
ThenBy ( «x» => «k2» ) .
... .
ThenBy ( «x» => «kn» )
...
Jeśli klauzula ordering
określa malejący wskaźnik kierunku, zamiast tego jest generowane wywołanie OrderByDescending
lub ThenByDescending
.
Przykład: przykład
from o in orders orderby o.Customer.Name, o.Total descending select o
ma ostateczne tłumaczenie
(orders) .OrderBy(o => o.Customer.Name) .ThenByDescending(o => o.Total)
koniec przykładu
W poniższych tłumaczeniach przyjęto założenie, że w każdym wyrażeniu zapytania nie ma klauzul let
, where
, join
ani orderby
ani więcej niż jedna klauzula początkowa from
.
12.20.3.6 Klauzule wyboru
Wyrażenie zapytania formularza
from «x» in «e» select «v»
jest tłumaczony na
( «e» ) . Select ( «x» => «v» )
z wyjątkiem sytuacji, gdy «v»
jest identyfikatorem «x»
, tłumaczenie jest po prostu
( «e» )
Przykład: przykład
from c in customers.Where(c => c.City == "London") select c
jest po prostu przekładany na
(customers).Where(c => c.City == "London")
koniec przykładu
12.20.3.7 Klauzule grupy
Klauzula group
from «x» in «e» group «v» by «k»
jest tłumaczony na
( «e» ) . GroupBy ( «x» => «k» , «x» => «v» )
z wyjątkiem, gdy «v»
jest identyfikatorem «x»
, tłumaczenie jest
( «e» ) . GroupBy ( «x» => «k» )
Przykład: przykład
from c in customers group c.Name by c.Country
jest tłumaczony na
(customers).GroupBy(c => c.Country, c => c.Name)
koniec przykładu
12.20.3.8 Identyfikatory przezroczyste
Niektóre tłumaczenia wprowadzają zmienne zakresu z identyfikatorami przezroczystymi oznaczone przez *
. Identyfikatory przezroczyste istnieją tylko jako krok pośredni w procesie tłumaczenia wyrażeń zapytania.
Gdy tłumaczenie zapytania wprowadza przezroczysty identyfikator, dalsze kroki tłumaczenia propagują przezroczysty identyfikator do funkcji anonimowych i inicjatorów obiektów anonimowych. W tych kontekstach identyfikatory przezroczyste mają następujące zachowanie:
- Gdy przezroczysty identyfikator występuje jako parametr w funkcji anonimowej, elementy członkowskie skojarzonego typu anonimowego są automatycznie dostępne w treści funkcji anonimowej.
- Gdy element członkowski z przezroczystym identyfikatorem jest w zasięgu, jego członkowie również są w zasięgu.
- Gdy identyfikator przezroczysty występuje jako deklarator członka w inicjatorze obiektu anonimowego, dodaje członka z przezroczystym identyfikatorem.
W opisanych powyżej krokach tłumaczenia identyfikatory przezroczyste są zawsze wprowadzane razem z typami anonimowymi, z zamiarem przechwytywania wielu zmiennych zakresu jako elementów członkowskich pojedynczego obiektu. Implementacja języka C# może używać innego mechanizmu niż typy anonimowe do grupowania wielu zmiennych zakresu. W poniższych przykładach tłumaczenia przyjęto założenie, że są używane typy anonimowe i przedstawiono jedno możliwe tłumaczenie identyfikatorów przezroczystych.
Przykład: przykład
from c in customers from o in c.Orders orderby o.Total descending select new { c.Name, o.Total }
jest tłumaczony na
from * in (customers).SelectMany(c => c.Orders, (c,o) => new { c, o }) orderby o.Total descending select new { c.Name, o.Total }
co jest dalej tłumaczone na
customers .SelectMany(c => c.Orders, (c,o) => new { c, o }) .OrderByDescending(* => o.Total) .Select(\* => new { c.Name, o.Total })
które, gdy identyfikatory przezroczyste są wymazane, jest równoważne
customers .SelectMany(c => c.Orders, (c,o) => new { c, o }) .OrderByDescending(x => x.o.Total) .Select(x => new { x.c.Name, x.o.Total })
gdzie
x
jest identyfikatorem wygenerowanym przez kompilator, który jest w przeciwnym razie niewidoczny i niedostępny.Przykład
from c in customers join o in orders on c.CustomerID equals o.CustomerID join d in details on o.OrderID equals d.OrderID join p in products on d.ProductID equals p.ProductID select new { c.Name, o.OrderDate, p.ProductName }
jest tłumaczony na
from * in (customers).Join( orders, c => c.CustomerID, o => o.CustomerID, (c, o) => new { c, o }) join d in details on o.OrderID equals d.OrderID join p in products on d.ProductID equals p.ProductID select new { c.Name, o.OrderDate, p.ProductName }
które jest dodatkowo zredukowane do
customers .Join(orders, c => c.CustomerID, o => o.CustomerID, (c, o) => new { c, o }) .Join(details, * => o.OrderID, d => d.OrderID, (*, d) => new { *, d }) .Join(products, * => d.ProductID, p => p.ProductID, (*, p) => new { c.Name, o.OrderDate, p.ProductName })
końcowe tłumaczenie, które jest
customers .Join(orders, c => c.CustomerID, o => o.CustomerID, (c, o) => new { c, o }) .Join(details, x => x.o.OrderID, d => d.OrderID, (x, d) => new { x, d }) .Join(products, y => y.d.ProductID, p => p.ProductID, (y, p) => new { y.x.c.Name, y.x.o.OrderDate, p.ProductName })
gdzie
x
iy
są identyfikatorami generowanymi przez kompilator, które w przeciwnym razie są niewidoczne i niedostępne. koniec przykładu
12.20.4 Wzorzec wyrażenia zapytania
Wzorzec wyrażeń-kwerend ustanawia wzorzec metod, które typy mogą implementować w celu obsługi wyrażeń kwerendy.
Typ ogólny C<T>
obsługuje wzorzec wyrażenia zapytania, jeśli jego publiczne metody składowe i metody rozszerzenia dostępne publicznie mogą zostać zastąpione przez następującą definicję klasy. Elementy członkowskie i dostępne metody rozszerzeń są określane jako "kształt" typu ogólnego C<T>
. Typ ogólny jest używany w celu zilustrowania odpowiednich relacji między typami parametrów i zwracanych, ale istnieje możliwość zaimplementowania wzorca dla typów innych niż ogólne.
delegate R Func<T1,R>(T1 arg1);
delegate R Func<T1,T2,R>(T1 arg1, T2 arg2);
class C
{
public C<T> Cast<T>() { ... }
}
class C<T> : C
{
public C<T> Where(Func<T,bool> predicate) { ... }
public C<U> Select<U>(Func<T,U> selector) { ... }
public C<V> SelectMany<U,V>(Func<T,C<U>> selector,
Func<T,U,V> resultSelector) { ... }
public C<V> Join<U,K,V>(C<U> inner, Func<T,K> outerKeySelector,
Func<U,K> innerKeySelector, Func<T,U,V> resultSelector) { ... }
public C<V> GroupJoin<U,K,V>(C<U> inner, Func<T,K> outerKeySelector,
Func<U,K> innerKeySelector, Func<T,C<U>,V> resultSelector) { ... }
public O<T> OrderBy<K>(Func<T,K> keySelector) { ... }
public O<T> OrderByDescending<K>(Func<T,K> keySelector) { ... }
public C<G<K,T>> GroupBy<K>(Func<T,K> keySelector) { ... }
public C<G<K,E>> GroupBy<K,E>(Func<T,K> keySelector,
Func<T,E> elementSelector) { ... }
}
class O<T> : C<T>
{
public O<T> ThenBy<K>(Func<T,K> keySelector) { ... }
public O<T> ThenByDescending<K>(Func<T,K> keySelector) { ... }
}
class G<K,T> : C<T>
{
public K Key { get; }
}
Powyższe metody używają typów delegatów ogólnych Func<T1, R>
i Func<T1, T2, R>
, ale mogą one równie dobrze używać innych typów delegatów lub drzewa wyrażeń z tymi samymi relacjami pomiędzy typami parametrów i wartości zwracanych.
Uwaga: Zalecana relacja między
C<T>
iO<T>
gwarantuje, że metodyThenBy
iThenByDescending
są dostępne tylko w wynikuOrderBy
lubOrderByDescending
. uwaga końcowa
Uwaga: Zalecany kształt wyniku
GroupBy
— sekwencja sekwencji, w której każda sekwencja wewnętrzna ma dodatkową właściwośćKey
. uwaga końcowa
Uwaga: ponieważ wyrażenia zapytania są tłumaczone na wywołania metod za pomocą mapowania składniowego, typy mają znaczną elastyczność w sposobie implementowania dowolnego lub całego wzorca wyrażenia zapytania. Na przykład metody wzorca można zaimplementować jako metody instancyjne lub metody rozszerzeń, ponieważ obie mają tę samą składnię wywołania. Metody te mogą przyjmować delegaty lub drzewa wyrażeń, ponieważ funkcje anonimowe mogą być przekształcane w oba. Typy implementujące tylko niektóre wzorce wyrażeń zapytania obsługują tylko tłumaczenia wyrażeń zapytań mapujące na obsługiwane przez ten typ metody. uwaga końcowa
Uwaga: Przestrzeń nazw
System.Linq
zapewnia implementację wzorca wyrażenia zapytania dla dowolnego typu, który implementuje interfejsSystem.Collections.Generic.IEnumerable<T>
. uwaga końcowa
12.21 Operatory przypisania
12.21.1 Ogólne
Wszystkie, ale jeden z operatorów przypisania przypisuje nową wartość do zmiennej, właściwości, zdarzenia lub elementu indeksatora. Wyjątek, = ref
, przypisuje referencję zmiennej (§9.5) zmiennej odniesienia (§9.7).
assignment
: unary_expression assignment_operator expression
;
assignment_operator
: '=' 'ref'? | '+=' | '-=' | '*=' | '/=' | '%=' | '&=' | '|=' | '^=' | '<<='
| right_shift_assignment
;
Lewy operand przypisania powinno być wyrażeniem typu zmiennej lub, z wyjątkiem = ref
, dostępem do właściwości, indeksatora, zdarzenia lub krotki. Wyrażenie deklaracji nie jest bezpośrednio dozwolone jako lewy operand, ale może wystąpić jako krok w procesie oceny przypisania dekonstrukcyjnego.
Operator =
jest nazywany operatorem prostego przypisania . Przypisuje wartość lub wartości prawego operandu do zmiennej, właściwości, elementu indeksatora lub elementów krotki nadanych przez lewy operand. Lewy operand operatora przypisania prostego nie może być dostępem do zdarzenia (z wyjątkiem opisanym w §15.8.2). Prosty operator przypisania jest opisany w §12.21.2.
Operator = ref
jest nazywany operatorem przypisania ref . Sprawia, że prawy operand, który powinien być variable_reference (§9.5), staje się deskryptorem zmiennej referencyjnej wskazanej przez lewy operand. Operator przypisania ref jest opisany w §12.21.3.
Operatory przypisania inne niż operatory =
i = ref
są nazywane operatorami przypisania złożonego . Te operatory wykonują wskazaną operację na dwóch operandach, a następnie przypisują wynikową wartość do zmiennej, właściwości lub elementu indeksatora podanego przez lewy operand. Operatory przypisania złożonego są opisane w §12.21.4.
Operatory +=
i -=
z wyrażeniem dostępu do zdarzenia jako lewym operandem są nazywane operatorami przypisania zdarzenia . Żaden inny operator przypisania nie jest prawidłowy, gdy dostęp do zdarzenia jest lewym operatorem. Operatory przypisania zdarzeń są opisane w §12.21.5.
Operatory przypisania są skojarzone od prawej do lewej, co oznacza, że operacje są grupowane od prawej do lewej.
Przykład: wyrażenie w formie
a = b = c
jest obliczane jakoa = (b = c)
. koniec przykładu
12.21.2 Proste przypisanie
Operator =
jest nazywany operatorem prostego przypisania.
Jeśli lewy operand prostego przypisania ma postać E.P
lub E[Ei]
gdzie E
ma typ czasu kompilacji dynamic
, przypisanie jest dynamicznie powiązane (§12.3.3). W tym przypadku typ kompilacyjny wyrażenia przypisania to dynamic
, a rozwiązanie opisane poniżej będzie miało miejsce podczas wykonywania programu w oparciu o typ wykonawczy E
. Jeśli lewy operand ma postać E[Ei]
, w której co najmniej jeden element Ei
ma typ czasu kompilacji dynamic
, a typ czasu kompilacji E
nie jest tablicą, wynikowy dostęp indeksatora jest dynamicznie powiązany, ale z ograniczonym sprawdzaniem czasu kompilacji (§12.6.5).
Proste przypisanie, w którym lewy operand jest klasyfikowany jako krotka, jest również nazywane przypisaniem dekonstrukcyjnym. Jeśli którykolwiek z elementów krotki lewego operandu ma nazwę elementu, wystąpi błąd czasu kompilacji. Jeśli którykolwiek z elementów krotki lewego operandu jest declaration_expression, a którykolwiek inny element nie jest declaration_expression lub prostym pominięciem, wystąpi błąd czasu kompilacji.
Typ zwykłego przypisania x = y
jest typem przypisania x
do y
, który jest określany rekursywnie w następujący sposób:
- Jeśli
x
jest wyrażeniem krotki(x1, ..., xn)
, ay
można zdekonstruować do wyrażenia krotki(y1, ..., yn)
zn
elementami (§12.7), i każde przypisanie doxi
yi
ma typTi
, to przypisanie ma typ(T1, ..., Tn)
. - W przeciwnym razie jeśli
x
jest klasyfikowana jako zmienna, zmienna nie jestreadonly
,x
ma typT
, ay
ma niejawną konwersję naT
, przypisanie ma typT
. - W przeciwnym razie, jeśli
x
jest klasyfikowaną jako niejawnie typizowaną zmienną (tj. niejawnie typizowanego wyrażenia deklaracji), ay
ma typT
, to wnioskowany typ zmiennej toT
, a przypisanie ma typT
. - W przeciwnym razie, jeśli
x
jest klasyfikowane jako dostęp do właściwości lub indeksatora, właściwość lub indeksator ma akcesor ustawiający, do którego można uzyskać dostęp,x
ma typT
, ay
ma niejawną konwersję naT
, to przypisanie ma typT
. - W przeciwnym razie przypisanie jest nieprawidłowe i występuje błąd związany z czasem wiązania.
Przetwarzanie w czasie wykonywania prostego przypisania w postaci x = y
z typem T
odbywa się przez przypisanie do x
, y
z typem T
, które składa się z następujących kroków rekurencyjnych:
-
x
jest oceniana, jeśli jeszcze nie została oceniona. - Jeśli
x
jest klasyfikowana jako zmienna,y
jest obliczana i w razie potrzeby przekonwertowana naT
za pomocą niejawnej konwersji (§10.2).- Jeśli zmienna oznaczona przez
x
jest elementem tablicy typu reference_type, wykonywane jest sprawdzenie w czasie wykonywania, aby upewnić się, że wartość obliczona dlay
jest zgodna z instancją tablicy, którejx
jest elementem. Sprawdzanie powiedzie się, jeśliy
jestnull
, lub jeśli istnieje niejawna konwersja odwołania (§10.2.8) z typu wystąpienia, na które wskazujey
, do rzeczywistego typu elementu tablicy, zawierającejx
. W przeciwnym razie zostanie zgłoszonySystem.ArrayTypeMismatchException
. - Wartość wynikająca z ewaluacji i konwersji
y
jest przechowywana w lokalizacji wynikającej z ewaluacjix
i jest zwracana jako wynik przypisania.
- Jeśli zmienna oznaczona przez
- Jeśli
x
jest klasyfikowana jako właściwość lub dostęp indeksatora:-
y
jest obliczana i w razie potrzeby przekonwertowana naT
za pomocą niejawnej konwersji (§10.2). - Akcesor ustawiający
x
jest wywoływany z wartością, którą uzyskuje się w wyniku oceny i konwersjiy
jako argument wartości. - Wartość wynikająca z oceny i konwersji
y
jest wynikiem przypisania.
-
- Jeśli
x
jest klasyfikowany jako krotka(x1, ..., xn)
o arnościn
:-
y
jest dekonstruktorowany elementamin
do wyrażenia krotkie
. - Krotka wynikowa
t
zostaje utworzona poprzez konwersjęe
naT
przy użyciu niejawnej konwersji krotki. - dla każdego
xi
w kolejności od lewej do prawej wykonuje się przypisaniexi
dot.Itemi
, z wyjątkiem sytuacji, w którejxi
nie są ponownie oceniane. -
t
jest zwracany w wyniku przypisania.
-
Uwaga: jeśli typ czasu kompilacji
x
jestdynamic
i istnieje niejawna konwersja typu czasu kompilacjiy
dodynamic
, nie jest wymagane rozwiązanie środowiska uruchomieniowego. uwaga końcowa
Uwaga: Reguły współrzędności tablic (§17.6) pozwalają, aby wartość typu tablicowego
A[]
była odwołaniem do instancji typu tablicowegoB[]
, pod warunkiem że istnieje niejawna konwersja odwołania zB
doA
. Ze względu na te reguły, przypisanie do elementu tablicy reference_type wymaga sprawdzenia w czasie wykonywania, aby upewnić się, że wartość, która jest przypisywana, jest zgodna z wystąpieniem tablicy. W przykładziestring[] sa = new string[10]; object[] oa = sa; oa[0] = null; // OK oa[1] = "Hello"; // OK oa[2] = new ArrayList(); // ArrayTypeMismatchException
Ostatnie przypisanie powoduje rzucenie
System.ArrayTypeMismatchException
, ponieważ odwołanie doArrayList
nie może być przechowywane w elemenciestring[]
.uwaga końcowa
Gdy właściwość lub indeksator zadeklarowany w struct_type jest celem przypisania, wyrażenie wystąpienia skojarzone z właściwością lub dostępem indeksatora jest klasyfikowane jako zmienna. Jeśli wyrażenie wystąpienia jest uznawane za wartość, wystąpi błąd czasu wiązania.
Uwaga: z powodu §12.8.7ta sama reguła dotyczy również pól. uwaga końcowa
Przykładowy: biorąc pod uwagę deklaracje:
struct Point { int x, y; public Point(int x, int y) { this.x = x; this.y = y; } public int X { get { return x; } set { x = value; } } public int Y { get { return y; } set { y = value; } } } struct Rectangle { Point a, b; public Rectangle(Point a, Point b) { this.a = a; this.b = b; } public Point A { get { return a; } set { a = value; } } public Point B { get { return b; } set { b = value; } } }
w przykładzie
Point p = new Point(); p.X = 100; p.Y = 100; Rectangle r = new Rectangle(); r.A = new Point(10, 10); r.B = p;
przypisania do
p.X
,p.Y
,r.A
ir.B
są dozwolone, ponieważp
ir
są zmiennymi. Jednak w przykładzieRectangle r = new Rectangle(); r.A.X = 10; r.A.Y = 10; r.B.X = 100; r.B.Y = 100;
wszystkie przypisania są nieprawidłowe, ponieważ
r.A
ir.B
nie są zmiennymi.koniec przykładu
12.21.3 Przydział referencji
Operator = ref
jest znany jako operator przypisania ref.
Lewy operand jest wyrażeniem, które wiąże się ze zmienną referencyjną (§9.7), parametrem referencyjnym (innym niż this
), parametrem wyjściowym lub parametrem wejściowym. Prawy operand powinien być wyrażeniem, które daje variable_reference określające wartość tego samego typu co lewy operand.
Jest to błąd czasu kompilacji, jeśli kontekst ref-safe-context (§9.7.2) lewego operanda jest szerszy niż kontekst ref-safe-context prawego operanda.
Prawy operand powinien być jednoznacznie przypisany w momencie przypisania przez odwołanie.
Gdy lewy operand wiąże się z parametrem wyjściowym, jest to błąd, jeśli ten parametr wyjściowy nie został zdecydowanie przypisany na początku operatora przypisania ref.
Jeśli lewy operand jest zapisywalnym odwołaniem (tj. wyznacza coś innego niż lokalny lub wejściowy parametr ref readonly
), to prawy operand musi być zapisywalnym odwołaniem_do_zmiennej . Jeśli prawa zmienna operandu jest zapisywalna, lewy operand może być zapisywalny lub tylko do odczytu ref.
Operacja sprawia, że lewy operand staje się aliasem dla zmiennej określonej przez prawy operand. Alias może być tylko do odczytu, nawet jeśli właściwa zmienna operandu jest zapisywalna.
Operator przypisania ref zwraca odwołanie do zmiennej przypisanego typu. Jest zapisywalny, jeśli lewy operand jest zapisywalny.
Operator przypisania ref nie powinien odczytywać lokalizacji przechowywania, na które wskazuje prawy operand.
Przykład: oto kilka przykładów użycia
= ref
:public static int M1() { ... } public static ref int M2() { ... } public static ref uint M2u() { ... } public static ref readonly int M3() { ... } public static void Test() { int v = 42; ref int r1 = ref v; // OK, r1 refers to v, which has value 42 r1 = ref M1(); // Error; M1 returns a value, not a reference r1 = ref M2(); // OK; makes an alias r1 = ref M2u(); // Error; lhs and rhs have different types r1 = ref M3(); // error; M3 returns a ref readonly, which r1 cannot honor ref readonly int r2 = ref v; // OK; make readonly alias to ref r2 = ref M2(); // OK; makes an alias, adding read-only protection r2 = ref M3(); // OK; makes an alias and honors the read-only r2 = ref (r1 = ref M2()); // OK; r1 is an alias to a writable variable, // r2 is an alias (with read-only access) to the same variable }
koniec przykładu
Uwaga: podczas odczytywania kodu przy użyciu operatora
= ref
może być kuszące odczytanie częściref
jako części operandu. Jest to szczególnie mylące, gdy operand jest wyrażeniem warunkowym?:
. Na przykład podczas odczytywaniaref int a = ref b ? ref x : ref y;
ważne jest, aby odczytywać to jako operator= ref
, ab ? ref x : ref y
jest właściwym operandem:ref int a = ref (b ? ref x : ref y);
. Co ważne, wyrażenieref b
nie jest częścią tej instrukcji, mimo że może wydawać się tak na pierwszy rzut oka. uwaga końcowa
12.21.4 Przypisanie złożone
Jeśli lewy operand przypisania złożonego ma postać E.P
lub E[Ei]
gdzie E
ma typ czasu kompilacji dynamic
, przypisanie jest dynamicznie powiązane (§12.3.3). W tym przypadku typ kompilacyjny wyrażenia przypisania to dynamic
, a rozwiązanie opisane poniżej będzie miało miejsce podczas wykonywania programu w oparciu o typ wykonawczy E
. Jeśli lewy operand ma postać E[Ei]
, w której co najmniej jeden element Ei
ma typ czasu kompilacji dynamic
, a typ czasu kompilacji E
nie jest tablicą, wynikowy dostęp indeksatora jest dynamicznie powiązany, ale z ograniczonym sprawdzaniem czasu kompilacji (§12.6.5).
Operacja w formie x «op»= y
jest przetwarzana poprzez zastosowanie rozpoznawania przeciążenia operatora binarnego (§12.4.5), tak jakby operacja była zapisana jako x «op» y
. Wtedy
- Jeśli typ zwracany wybranego operatora jest niejawnie konwertowany na typ
x
, operacja jest obliczana jakox = x «op» y
, z tą różnicą, żex
jest obliczana tylko raz. - W przeciwnym razie, jeśli wybrany operator jest wstępnie zdefiniowanym operatorem, jeśli zwracany typ wybranego operatora jest jawnie konwertowalny na typ
x
, a jeśliy
jest niejawnie konwertowalny na typx
lub operator jest operatorem przesunięcia, wtedy operacja jest oceniana jakox = (T)(x «op» y)
, gdzieT
jest typemx
, z wyjątkiem tego, żex
jest obliczany tylko raz. - W przeciwnym razie przypisanie złożone jest nieprawidłowe i występuje błąd związany z czasem powiązania.
Termin "oceniane tylko raz" oznacza, że w ocenie x «op» y
wyniki jakichkolwiek wyrażeń składowych x
są tymczasowo zapisywane, a następnie ponownie używane podczas wykonywania przypisania do x
.
Przykład: w przypisaniu
A()[B()] += C()
, gdzieA
jest metodą zwracającąint[]
, aB
iC
są metodami zwracającymiint
, metody są wywoływane tylko raz, w kolejnościA
,B
,C
. koniec przykładu
Gdy lewy operand przypisania złożonego jest dostępem do właściwości lub indeksatorem, właściwość lub indeksator musi mieć zarówno metodę dostępu get, jak i metodę dostępu set. Jeśli tak nie jest, wystąpi błąd powiązania czasowego.
Druga reguła powyżej pozwala na ocenę x «op»= y
jako x = (T)(x «op» y)
w niektórych kontekstach. Reguła istnieje tak, że wstępnie zdefiniowane operatory mogą być używane jako operatory złożone, gdy lewy operand jest typu sbyte
, byte
, short
, ushort
lub char
. Nawet jeśli oba argumenty są jednym z tych typów, wstępnie zdefiniowane operatory generują wynik typu int
, zgodnie z opisem w §12.4.7.3. W związku z tym bez rzutowania nie byłoby możliwe przypisanie wyniku do lewego operandu.
Intuicyjnym efektem reguły dla wstępnie zdefiniowanych operatorów jest po prostu to, że x «op»= y
jest dozwolona, jeśli dozwolone są obie x «op» y
i x = y
.
Przykład: w poniższym kodzie
byte b = 0; char ch = '\0'; int i = 0; b += 1; // OK b += 1000; // Error, b = 1000 not permitted b += i; // Error, b = i not permitted b += (byte)i; // OK ch += 1; // Error, ch = 1 not permitted ch += (char)1; // OK
Intuicyjnym powodem każdego błędu jest to, że podobne proste przypisanie byłoby również błędne.
koniec przykładu
Uwaga: To również oznacza, że operacje złożonego przypisania obsługują podniesione operatory. Ponieważ przypisanie złożone
x «op»= y
jest oceniane albo jakox = x «op» y
, albox = (T)(x «op» y)
, reguły oceny niejawnie obejmują podniesione operatory. uwaga końcowa
12.21.5 Przypisanie zdarzenia
Jeśli lewy operand operatora a += or -=
jest klasyfikowany jako dostęp do zdarzeń, wyrażenie jest oceniane w następujący sposób:
- Wyrażenie wystąpienia, jeśli istnieje, jest oceniane w dostępie do zdarzenia.
- Prawy operand operatora
+=
lub-=
jest obliczany, a w razie potrzeby przekonwertowany na typ lewego operandu za pomocą konwersji niejawnej (§10.2). - Wywołano akcesor zdarzenia, przekazując listę argumentów składającą się z wartości obliczonej w poprzednim kroku. Jeśli operator został
+=
, wywoływana jest funkcja dodawania metody dostępu; jeśli operator został-=
, wywoływana jest funkcja usuwania metody dostępu.
Wyrażenie przypisania zdarzenia nie zwraca wartości. W związku z tym wyrażenie przypisania zdarzeń jest prawidłowe tylko w kontekście statement_expression (§13.7).
Wyrażenie 12.22
Wyrażenie to wyrażenie bez przypisania lub przypisanie .
expression
: non_assignment_expression
| assignment
;
non_assignment_expression
: declaration_expression
| conditional_expression
| lambda_expression
| query_expression
;
12.23 Wyrażenia stałe
Wyrażenie stałe jest wyrażeniem, które jest w pełni oceniane w czasie kompilacji.
constant_expression
: expression
;
Wyrażenie stałe powinno mieć wartość null
lub jeden z następujących typów:
-
sbyte
,byte
,short
,ushort
,int
,uint
,long
,ulong
,char
,float
,double
,decimal
,bool
,string
; - typ wyliczenia; lub
- wyrażenie wartości domyślnej (§12.8.21) dla typu odwołania.
Tylko następujące konstrukcje są dozwolone w wyrażeniach stałych:
- Literały (w tym literał
null
). - Odwołania do
const
elementów typów danych klas i struktur. - Odwołania do elementów typów wyliczenia.
- Odwołania do lokalnych stałych.
- Wyrażenia w nawiasach, które są wyrażeniami stałymi.
- Wyrażenia rzutowe.
- wyrażenia
checked
iunchecked
-
nameof
wyrażenia. - Wstępnie zdefiniowane
+
,-
,!
(negacja logiczna) i operatory jednoargumentowe~
. - Wstępnie zdefiniowane
+
,-
,*
,/
,%
,<<
,>>
,&
,|
,^
,&&
,||
,==
,!=
,<
,>
,<=
i>=
operatory binarne. - Operator warunkowy
?:
. - Operator
!
forgiving o wartości null (§12.8.9). -
sizeof
wyrażenia, pod warunkiem, że niezarządzany typ jest jednym z typów określonych w §23.6.9, dla którychsizeof
zwraca stałą wartość. - Wyrażenia wartości domyślnej, pod warunkiem, że typ jest jednym z typów wymienionych powyżej.
Następujące konwersje są dozwolone w wyrażeniach stałych:
- Konwersje tożsamości
- Konwersje liczbowe
- Konwersje wyliczenia
- Konwersje wyrażeń stałych
- Niejawne i jawne konwersje odwołań, pod warunkiem, że źródłem konwersji jest wyrażenie stałe, które oblicza wartość
null
.
Uwaga: Inne konwersje, w tym boxing, unboxing i niejawne konwersje odwołań wartości innych niż
null
są niedozwolone w wyrażeniach stałych. uwaga końcowa
Przykład: w poniższym kodzie
class C { const object i = 5; // error: boxing conversion not permitted const object str = "hello"; // error: implicit reference conversion }
inicjowanie
i
jest błędem, ponieważ wymagana jest konwersja boksu. Inicjowaniestr
jest błędem, ponieważ wymagana jest niejawna konwersja odwołania z wartości innej niżnull
.koniec przykładu
Zawsze, gdy wyrażenie spełnia wymagania wymienione powyżej, wyrażenie jest obliczane w czasie kompilacji. Jest to prawda, nawet jeśli wyrażenie jest podwyrażeniem większego procesu, który zawiera zmienne konstrukcje.
Obliczanie w czasie kompilacji wyrażeń stałych używa tych samych reguł co obliczanie w czasie wykonywania wyrażeń niestałych, z tą różnicą, że w przypadku, gdy ocena czasu wykonywania zgłosiła wyjątek, ocena czasu kompilacji powoduje wystąpienie błędu czasu kompilacji.
Jeśli wyrażenie stałe nie zostanie jawnie umieszczone w kontekście unchecked
, przepełnienia występujące w operacjach arytmetycznych typu całkowitego i konwersji podczas obliczania wyrażeń w czasie kompilacji zawsze powodują błędy czasu kompilacji (§12.8.20).
Wyrażenia stałe są wymagane w kontekstach wymienionych poniżej i jest to wskazane w gramatyce przy użyciu constant_expression. W tych kontekstach występuje błąd czasu kompilacji, jeśli wyrażenie nie może być w pełni ocenione w czasie kompilacji.
- Deklaracje stałe (§15.4)
- Deklaracje elementów wyliczenia (§19.4)
- Domyślne argumenty list parametrów (§15.6.2)
-
case
etykiety instrukcjiswitch
(§13.8.3). - oświadczenia
goto case
(§13.10.4) - Długości wymiarów w wyrażeniu tworzenia tablicy (§12.8.17.5), które zawiera inicjalizator.
- Atrybuty (§22)
- W constant_pattern (§11.2.3)
Niejawna konwersja wyrażenia stałego (§10.2.11) zezwala na konwersję wyrażenia stałego typu int
na sbyte
, byte
, short
, ushort
, uint
lub ulong
, pod warunkiem, że wartość wyrażenia stałego mieści się w zakresie typu docelowego.
12.24 Wyrażenia logiczne
boolean_expression to wyrażenie, które daje wynik typu bool
; bezpośrednio lub poprzez zastosowanie operator true
w określonych kontekstach, jak opisano poniżej:
boolean_expression
: expression
;
Kontrolowanie wyrażenia warunkowego ?:
(§12.18) podlega tym samym regułom co boolean_expression, ale z powodu kolejności operatorów jest klasyfikowane jako null_coalescing_expression.
Aby można było utworzyć wartość typu , wymagany jest E
bool
w następujący sposób:
- Jeśli E jest niejawnie konwertowany na
bool
, wówczas w czasie wykonywania jest stosowana niejawna konwersja. - Alternatywnie, rozpoznawanie przeciążenia operatora jednoargumentowego (§12.4.4) jest używane do znalezienia unikalnej, najlepszej implementacji
operator true
naE
, a ta implementacja jest stosowana w czasie wykonywania. - Jeśli taki operator nie zostanie znaleziony, wystąpi błąd czasu powiązania.
ECMA C# draft specification