Uwaga
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
8.1 Ogólne
Typy języka C# są podzielone na dwie główne kategorie: typy referencyjne i typy wartości. Oba typy wartości i typy referencyjne mogą być typami ogólnymi, które przyjmują co najmniej jeden parametr typu. Parametry typu mogą wyznaczać zarówno typy wartości, jak i typy referencyjne.
type
: reference_type
| value_type
| type_parameter
| pointer_type // unsafe code support
;
pointer_type (§23.3) jest dostępny tylko w niebezpiecznym kodzie (§23).
Typy wartości różnią się od typów referencyjnych w tym, że zmienne typów wartości bezpośrednio zawierają swoje dane, podczas gdy zmienne typów referencyjnych przechowują odwołania do ich danych, a te ostatnie są nazywane obiektami. W przypadku typów odwołań możliwe jest, aby dwie zmienne odwoły się do tego samego obiektu, co umożliwia operacje na jednej zmiennej, aby wpływały na obiekt, do którego odwołuje się druga zmienna. W przypadku typów wartości zmienne mają własną kopię danych i nie jest możliwe, aby operacje na jednym z nich miały wpływ na drugą.
Uwaga: jeśli zmienna jest parametrem referencyjnym lub wyjściowym, nie ma własnego magazynu, ale odwołuje się do magazynu innej zmiennej. W takim przypadku zmienna ref lub out jest w rzeczywistości aliasem dla innej zmiennej, a nie unikatową zmienną. notatka końcowa
System typów języka C#jest ujednolicony, tak aby można było traktować wartość dowolnego typu jako obiekt. Każdy typ w języku C# bezpośrednio lub pośrednio pochodzi z object
typu klasy i object
jest ostateczną klasą bazową wszystkich typów. Wartości typów referencyjnych są traktowane jako obiekty po prostu, wyświetlając wartości jako typ object
. Wartości typów wartości są traktowane jako obiekty, wykonując operacje boksowania i rozpasania (§8.3.13).
Dla wygody, w całej tej specyfikacji niektóre nazwy typów biblioteki są pisane bez użycia pełnej nazwy kwalifikowanej. Aby uzyskać więcej informacji, zobacz §C.5 .
8.2 Typy referencyjne
8.2.1 Ogólne
Typ odwołania to typ klasy, typ interfejsu, typ tablicy, typ delegata lub dynamic
typ. Dla każdego nienullowalnego typu referencyjnego istnieje odpowiedni typ referencyjny dopuszczający wartość null, poprzez dołączenie ?
do nazwy typu.
reference_type
: non_nullable_reference_type
| nullable_reference_type
;
non_nullable_reference_type
: class_type
| interface_type
| array_type
| delegate_type
| 'dynamic'
;
class_type
: type_name
| 'object'
| 'string'
;
interface_type
: type_name
;
array_type
: non_array_type rank_specifier+
;
non_array_type
: value_type
| class_type
| interface_type
| delegate_type
| 'dynamic'
| type_parameter
| pointer_type // unsafe code support
;
rank_specifier
: '[' ','* ']'
;
delegate_type
: type_name
;
nullable_reference_type
: non_nullable_reference_type nullable_type_annotation
;
nullable_type_annotation
: '?'
;
pointer_type jest dostępny tylko w niebezpiecznym kodzie (§23.3). nullable_reference_type omówiono dalej w §8.9.
Wartość typu odwołania jest odwołaniem do wystąpienia typu, znanego jako obiekt. Wartość null
specjalna jest zgodna ze wszystkimi typami referencyjnymi i wskazuje brak wystąpienia.
Typy klas 8.2.2
Typ klasy definiuje strukturę danych zawierającą elementy członkowskie danych (stałe i pola), składowe funkcji (metody, właściwości, zdarzenia, indeksatory, operatory, konstruktory wystąpień, finalizatory i konstruktory statyczne) oraz typy zagnieżdżone. Typy klas obsługują dziedziczenie, mechanizm, w którym klasy pochodne mogą rozszerzać i specjalizować klasy bazowe. Wystąpienia typów klas są tworzone przy użyciu object_creation_expressions (§12.8.17.2).
Typy klas są opisane w §15.
Niektóre wstępnie zdefiniowane typy klas mają specjalne znaczenie w języku C#, zgodnie z opisem w poniższej tabeli.
Typ klasy | Opis |
---|---|
System.Object |
Nadrzędna klasa bazowa wszystkich innych typów. Zobacz §8.2.3. |
System.String |
Typ ciągu języka C#. Zobacz §8.2.5. |
System.ValueType |
Klasa podstawowa wszystkich typów wartości. Zobacz §8.3.2. |
System.Enum |
Klasa bazowa wszystkich enum typów. Zobacz §19.5. |
System.Array |
Klasa podstawowa wszystkich typów tablic. Zobacz §17.2.2. |
System.Delegate |
Klasa bazowa wszystkich delegate typów. Zobacz §20.1. |
System.Exception |
Klasa podstawowa wszystkich typów wyjątków. Zobacz §21.3. |
8.2.3 Typ obiektu
Typ object
klasy jest najlepszą klasą bazową wszystkich innych typów. Każdy typ w języku C# bezpośrednio lub pośrednio pochodzi z object
typu klasy.
Słowo kluczowe object
to po prostu alias dla wstępnie zdefiniowanej klasy System.Object
.
8.2.4 Typ dynamiczny
Typ dynamic
, taki jak object
, może odwoływać się do dowolnego obiektu. Gdy operacje są stosowane do wyrażeń typu dynamic
, ich rozpoznawanie jest odroczone do momentu uruchomienia programu. W związku z tym, jeśli nie można legalnie zastosować operacji do przywoływanego obiektu, podczas kompilacji nie zostanie podany żaden błąd. Zamiast tego zostanie zgłoszony wyjątek, gdy rozwiązanie operacji zakończy się niepowodzeniem w czasie wykonywania.
Typ dynamic
jest dokładniej opisany w §8.7 i powiązanie dynamiczne w §12.3.1.
8.2.5 Typ ciągu
Typ string
jest zapieczętowaną klasą, która dziedziczy bezpośrednio z klasy object
.
string
Wystąpienia klasy reprezentują ciągi znaków Unicode.
string
Wartości typu można zapisywać jako literały ciągu (§6.4.5.6).
Słowo kluczowe string
to po prostu alias dla wstępnie zdefiniowanej klasy System.String
.
Typy interfejsów 8.2.6
Interfejs definiuje kontrakt. Klasa lub struktura, która implementuje interfejs, jest zgodna z jego umową. Interfejs może dziedziczyć z wielu interfejsów podstawowych, a klasa lub struktura może implementować wiele interfejsów.
Typy interfejsów są opisane w §18.
Typy tablic 8.2.7
Tablica to struktura danych zawierająca zero lub więcej zmiennych, do których uzyskuje się dostęp za pośrednictwem obliczonych indeksów. Zmienne zawarte w tablicy, nazywane również elementami tablicy, są tego samego typu, a ten typ jest nazywany typem elementu tablicy.
Typy tablic są opisane w §17.
8.2.8 Typy delegatów
Delegat to struktura danych, która odwołuje się do jednej lub kilku metod. Na przykład metody odnoszą się również do odpowiednich wystąpień obiektów.
Uwaga: najbliższym odpowiednikiem delegata w języku C lub C++ jest wskaźnik funkcji, ale wskaźnik funkcji może odwoływać się tylko do funkcji statycznych, delegat może odwoływać się do metod statycznych i wystąpień. W drugim przypadku delegat przechowuje zarówno odwołanie do punktu wejścia metody, jak i odwołanie do instancji obiektu, na którym wywoływana jest metoda. notatka końcowa
Typy delegatów są opisane w §20.
8.3 Typy wartości
8.3.1 Ogólne
Typ wartości jest typem struktury lub typem wyliczenia. Język C# udostępnia zestaw wstępnie zdefiniowanych typów struktur nazywanych prostymi typami. Proste typy są identyfikowane za pomocą słów kluczowych.
value_type
: non_nullable_value_type
| nullable_value_type
;
non_nullable_value_type
: struct_type
| enum_type
;
struct_type
: type_name
| simple_type
| tuple_type
;
simple_type
: numeric_type
| 'bool'
;
numeric_type
: integral_type
| floating_point_type
| 'decimal'
;
integral_type
: 'sbyte'
| 'byte'
| 'short'
| 'ushort'
| 'int'
| 'uint'
| 'long'
| 'ulong'
| 'char'
;
floating_point_type
: 'float'
| 'double'
;
tuple_type
: '(' tuple_type_element (',' tuple_type_element)+ ')'
;
tuple_type_element
: type identifier?
;
enum_type
: type_name
;
nullable_value_type
: non_nullable_value_type nullable_type_annotation
;
W przeciwieństwie do zmiennej typu referencyjnego, zmienna typu wartości może zawierać wartość null
tylko wtedy, gdy typ wartości jest typem wartości nulowalnej (§8.3.12). Dla każdego nienullowalnego typu wartości istnieje odpowiadający typ wartości, który może przyjąć wartość null, oznaczający ten sam zestaw wartości oraz wartość null
.
Przypisanie do zmiennej typu wartości powoduje utworzenie kopii przypisanej wartości. Różni się to od przypisania do zmiennej typu odwołania, która kopiuje odwołanie, ale nie obiekt zidentyfikowany przez odwołanie.
8.3.2 Typ System.ValueType
Wszystkie typy wartości niejawnie dziedziczą z class
System.ValueType
klasy , która z kolei dziedziczy z klasy object
. Nie można utworzyć żadnego typu pochodzącego z typu wartości, a typy wartości są zatem niejawnie zapieczętowane (§15.2.2.3).
Należy pamiętać, że System.ValueType
nie jest to value_type. Zamiast tego jest to class_type , z którego wszystkie value_typesą automatycznie uzyskiwane.
8.3.3 Konstruktory domyślne
Wszystkie typy wartości niejawnie deklarują publiczny konstruktor wystąpienia bez parametrów nazywany konstruktorem domyślnym. Konstruktor domyślny zwraca wystąpienie zainicjowane zero znane jako wartość domyślna dla typu wartości:
- Dla wszystkich simple_types wartość domyślna to wartość wygenerowana przez wzorzec bitowy wszystkich zer:
- W przypadku
sbyte
,byte
,short
,ushort
,int
,uint
,long
iulong
wartość domyślna to0
. - W przypadku
char
parametru wartość domyślna to'\x0000'
. - W przypadku
float
parametru wartość domyślna to0.0f
. - W przypadku
double
parametru wartość domyślna to0.0d
. - W przypadku
decimal
parametru wartość domyślna to0m
(czyli wartość zero ze skalą 0). - W przypadku
bool
parametru wartość domyślna tofalse
. - W przypadku enum_type
E
wartość domyślna to0
, przekonwertowana na typE
.
- W przypadku
- W przypadku struct_type wartość domyślna jest wartością wygenerowaną przez ustawienie wszystkich pól typu wartości na wartość domyślną i wszystkich pól typu odwołania na
null
. - W przypadku nullable_value_type wartością domyślną jest wystąpienie, dla którego
HasValue
właściwość ma wartość false. Wartość domyślna jest również znana jako wartość null typu wartości, które mogą być null. Próba odczytaniaValue
właściwości takiej wartości powoduje zgłoszenie wyjątku typuSystem.InvalidOperationException
(§8.3.12).
Podobnie jak każdy inny konstruktor wystąpienia, domyślny konstruktor typu wartości jest wywoływany przy użyciu new
operatora .
Uwaga: Ze względów wydajności to wymaganie nie jest przeznaczone do faktycznego wygenerowania wywołania konstruktora przez implementację. W przypadku typów wartości wyrażenie wartości domyślnej (§12.8.21) daje taki sam wynik, jak przy użyciu konstruktora domyślnego. notatka końcowa
Przykład: W poniższym kodzie zmienne
i
j
ik
są inicjowane do zera.class A { void F() { int i = 0; int j = new int(); int k = default(int); } }
przykład końcowy
Ponieważ każdy typ wartości niejawnie ma publiczny konstruktor wystąpienia bez parametrów, nie jest możliwe, aby typ struktury zawierał jawną deklarację konstruktora bez parametrów. Typ struktury jest jednak dozwolony do deklarowania sparametryzowanych konstruktorów wystąpień (§16.4.9).
Typy struktury 8.3.4
Typ struktury to typ wartości, który może deklarować stałe, pola, metody, właściwości, zdarzenia, indeksatory, operatory, konstruktory wystąpień, konstruktory statyczne i typy zagnieżdżone. Deklaracja typów struktur jest opisana w §16.
8.3.5 Typy proste
Język C# udostępnia zestaw wstępnie zdefiniowanych typów struct
nazywanych prostymi typami. Proste typy są identyfikowane za pomocą słów kluczowych, ale te słowa kluczowe są po prostu aliasami dla wstępnie zdefiniowanych typów struct
w przestrzeni nazw System
, jak opisano w poniższej tabeli.
Słowo kluczowe | typ z aliasem |
---|---|
sbyte |
System.SByte |
byte |
System.Byte |
short |
System.Int16 |
ushort |
System.UInt16 |
int |
System.Int32 |
uint |
System.UInt32 |
long |
System.Int64 |
ulong |
System.UInt64 |
char |
System.Char |
float |
System.Single |
double |
System.Double |
bool |
System.Boolean |
decimal |
System.Decimal |
Ponieważ prosty typ aliasuje typ struktury, każdy prosty typ ma elementy członkowskie.
Przykład:
int
ma członków zadeklarowanych wSystem.Int32
oraz członków odziedziczonych zSystem.Object
, a następujące instrukcje są dozwolone.int i = int.MaxValue; // System.Int32.MaxValue constant string s = i.ToString(); // System.Int32.ToString() instance method string t = 123.ToString(); // System.Int32.ToString() instance method
przykład końcowy
Uwaga: Proste typy różnią się od innych typów struktur, które zezwalają na niektóre dodatkowe operacje:
- Większość prostych typów zezwala na tworzenie wartości przez pisanie literałów (§6.4.5), chociaż język C# nie udostępnia literałów typów struktur w ogóle. Przykład:
123
jest literałem typuint
i'a'
jest literałem typuchar
. przykład końcowy- Gdy operandy wyrażenia są prostymi stałymi typami, kompilator może ocenić wyrażenie w czasie kompilacji. Takie wyrażenie jest nazywane constant_expression (§12.23). Wyrażenia obejmujące operatory zdefiniowane przez inne typy struktur nie są uważane za wyrażenia stałe
- Deklaracje
const
umożliwiają deklarowanie stałych typów prostych (§15.4). Nie można mieć stałych innych typów struktur, ale podobny efekt jest zapewniany przez statyczne pola readonly.- Konwersje obejmujące proste typy mogą uczestniczyć w ocenie operatorów konwersji zdefiniowanych przez inne typy struktur, ale operator konwersji zdefiniowany przez użytkownika nigdy nie może uczestniczyć w ocenie innego operatora konwersji zdefiniowanego przez użytkownika (§10.5.3).
uwaga końcowa.
Typy całkowite 8.3.6
Język C# obsługuje dziewięć typów całkowitych: sbyte
, byte
, short
, ushort
, int
, uint
, long
, ulong
i char
. Typy całkowite mają następujące rozmiary i zakresy wartości:
- Typ
sbyte
reprezentuje podpisane 8-bitowe liczby całkowite z wartościami od-128
do127
, włącznie. - Typ
byte
reprezentuje niepodpisane 8-bitowe liczby całkowite z wartościami od0
do255
, włącznie. - Typ
short
reprezentuje podpisane 16-bitowe liczby całkowite z wartościami od-32768
do32767
, włącznie. - Typ
ushort
reprezentuje niepodpisane 16-bitowe liczby całkowite z wartościami od0
do65535
, włącznie. - Typ
int
reprezentuje podpisane 32-bitowe liczby całkowite z wartościami od-2147483648
do2147483647
, włącznie. - Typ
uint
reprezentuje niepodpisane 32-bitowe liczby całkowite z wartościami od0
do4294967295
, włącznie. - Typ
long
reprezentuje podpisane 64-bitowe liczby całkowite z wartościami od-9223372036854775808
do9223372036854775807
, włącznie. - Typ
ulong
reprezentuje niepodpisane 64-bitowe liczby całkowite z wartościami od0
do18446744073709551615
, włącznie. - Typ
char
reprezentuje niepodpisane 16-bitowe liczby całkowite z wartościami od0
do65535
, włącznie. Zestaw możliwych wartości dlachar
typu odpowiada zestawowi znaków Unicode.Uwaga: Chociaż
char
ma taką samą reprezentację jakushort
, nie wszystkie operacje dozwolone na jednym typie są dozwolone w drugim. notatka końcowa
Wszystkie podpisane typy całkowite są reprezentowane przy użyciu formatu dopełnień dwóch.
Operatory integral_type jednoargumentowe i binarne zawsze działają z 32-bitową precyzją ze znakiem, 32-bitową precyzją bez znaku, 64-bitową precyzją ze znakiem, lub 64-bitową precyzją bez znaku, zgodnie z opisem w §12.4.7.
Typ char
jest klasyfikowany jako typ całkowity, ale różni się od innych typów całkowitych na dwa sposoby:
- Nie ma wstępnie zdefiniowanych niejawnych konwersji z innych typów na
char
typ. W szczególności, mimo że typybyte
iushort
mają zakresy wartości, które są w pełni reprezentowane przy użyciu typuchar
, nie istnieją niejawne konwersje z sbyte, byte lubushort
dochar
. - Stałe typu
char
powinny być zapisywane jako character_literal lub jako integer_literal w połączeniu z rzutowaniem do typu char.
Przykład:
(char)10
jest taki sam jak'\x000A'
. przykład końcowy
Operatory i instrukcje checked
i unchecked
służą do sprawdzania przepełnienia dla operacji arytmetycznych i konwersji typu całkowitego (§12.8.20). W kontekście checked
przepełnienie powoduje błąd czasu kompilacji lub zgłoszenie System.OverflowException
. W kontekście unchecked
przepełnienia są ignorowane, a wszystkie bity o wyższym rzędzie, które nie mieszczą się w docelowym typie, są odrzucane.
8.3.7 Typy zmiennoprzecinkowe
Język C# obsługuje dwa typy zmiennoprzecinkowe: float
i double
. Typy float
i double
są reprezentowane przy użyciu 32-bitowej pojedynczej precyzji i 64-bitowej podwójnej precyzji formatów IEC 60559, które zapewniają następujące zestawy wartości:
- Zero dodatnie i zero ujemne. W większości sytuacji dodatnie zero i zero ujemne zachowują się identycznie jak wartość prosta zero, ale niektóre operacje rozróżniają między nimi (§12.10.3).
- Nieskończoność dodatnia i nieskończoność ujemna. Nieskończoności są produkowane przez takie operacje, jak dzielenie liczby niezerowej przez zero.
Przykład:
1.0 / 0.0
daje nieskończoność dodatnią i–1.0 / 0.0
daje nieskończoność ujemną. przykład końcowy - Wartość Not-a-Number, często używana jako skrót NaN. Sieci NaN są generowane przez nieprawidłowe operacje zmiennoprzecinkowe, takie jak dzielenie zera przez zero.
- Skończony zbiór niezerowych wartości postaci s × m × 2e, gdzie s jest 1 lub −1, a m i e są określone przez określony typ zmiennoprzecinkowy: dla
float
, 0 <m< 2²⁴ i −149 ≤ e ≤ 104, a dladouble
, 0 <m< 2⁵³ i −1075 ≤ e ≤ 970. Zdenormalizowane liczby zmiennoprzecinkowe są uznawane za prawidłowe wartości inne niż zero. Język C# nie wymaga ani nie zabrania, aby zgodna implementacja obsługiwała zdenormalizowane liczby zmiennoprzecinkowe.
Typ float
może reprezentować wartości z zakresu od około 1,5 × 10⁻⁴⁵ do 3,4 × 10³⁸ z dokładnością 7 cyfr.
Typ double
może reprezentować wartości z zakresu od około 5,0 × 10⁻³²⁴ do 1,7 × 10³⁸ z dokładnością 15–16 cyfr.
Jeśli którykolwiek z operandów operatora binarnego jest typem zmiennoprzecinkowym, stosowane są standardowe promocje liczbowe, zgodnie z opisem w §12.4.7, a operacja jest wykonywana z float
lub double
precyzją.
Operatory zmiennoprzecinkowe, w tym operatory przypisania, nigdy nie generują wyjątków. Zamiast tego w wyjątkowych sytuacjach operacje zmiennoprzecinkowe generują zero, nieskończoność lub NaN, zgodnie z poniższym opisem:
- Wynik operacji zmiennoprzecinkowej jest zaokrąglany do najbliższej domyślnej wartości, która jest reprezentowana w docelowym formacie.
- Jeśli wielkość wyniku operacji zmiennoprzecinkowej jest zbyt mała dla formatu docelowego, wynik operacji staje się zerem dodatnim lub zerem ujemnym.
- Jeśli wielkość wyniku operacji zmiennoprzecinkowych jest zbyt duża dla formatu docelowego, wynik operacji staje się dodatnią nieskończonością lub nieskończonością ujemną.
- Jeśli operacja zmiennoprzecinkowa jest nieprawidłowa, wynik operacji staje się NaN.
- Jeśli jeden lub oba operandy operacji zmiennoprzecinkowych to NaN, wynik operacji staje się NaN.
Operacje zmiennoprzecinkowe mogą być wykonywane z wyższą precyzją niż typ wyniku operacji. Aby wymusić wartość typu zmiennoprzecinkowego do dokładnej precyzji jej typu, można użyć jawnego rzutowania (§12.9.7).
Przykład: Niektóre architektury sprzętowe obsługują rozszerzony lub długi podwójny typ zmiennoprzecinkowy z większym zakresem i dokładnością niż
double
typ, i niejawnie wykonują wszystkie operacje zmiennoprzecinkowe przy użyciu tego wyższego typu precyzji. Aby takie architektury sprzętowe mogły realizować operacje zmiennoprzecinkowe z niższą precyzją, wymaga to znacznego pogorszenia wydajności. Zamiast zmuszać implementację do rezygnacji zarówno z wydajności, jak i precyzji, język C# pozwala na użycie typu o wyższej precyzji dla wszystkich operacji zmiennoprzecinkowych. Poza dostarczaniem bardziej precyzyjnych wyników rzadko ma to jakiekolwiek wymierne skutki. Jednak w wyrażeniach formularzax * y / z
, gdzie mnożenie generuje wynik, który znajduje się pozadouble
zakresem, ale następny podział powoduje tymczasowy wynik z powrotem dodouble
zakresu, fakt, że wyrażenie jest oceniane w wyższym formacie zakresu, może spowodować wygenerowanie skończonego wyniku zamiast nieskończoności. przykład końcowy
8.3.8 Typ dziesiętny
Typ decimal
to 128-bitowy typ danych odpowiedni do obliczeń finansowych i pieniężnych. Typ decimal
może reprezentować wartości, w tym wartości z zakresu co najmniej -7,9 × 10⁻²⁸ do 7,9 × 10²⁸ z dokładnością co najmniej 28-cyfrową.
Skończony zestaw wartości typu decimal
ma postać (–1)v × c × 10⁻e, gdzie znak v wynosi 0 lub 1, współczynnik c jest podawany przez 0 ≤ c<Cmax, a skala e jest taka, że Emin ≤ e e ≤ Emax, gdzie Cmax wynosi co najmniej 1 × 10²⁸, Emin ≤ 0, i Emax ≥ 28. Typ decimal
nie musi obsługiwać zanajękowanych zer, nieskończoności ani wartości NaN.
Wartość A decimal
jest reprezentowana jako liczba całkowita skalowana przez potęgę dziesięciu. Dla decimal
-ów o wartości bezwzględnej mniejszej niż 1.0m
, wartość jest dokładna co najmniej do 28 miejsca dziesiętnego. Dla decimal
s z wartością bezwzględną większą lub równą 1.0m
, wartość jest dokładna do co najmniej 28 cyfr. W przeciwieństwie do typów danych float
i double
, liczby ułamkowe dziesiętne, takie jak 0.1
, mogą być dokładnie reprezentowane w formacie dziesiętnym.
float
i double
w reprezentacjach, takie liczby często mają nieskończone rozwinięcia binarne, co sprawia, że te reprezentacje są bardziej podatne na błędy zaokrąglania.
Jeśli którykolwiek operand operatora binarnego jest typu decimal
, stosowane są standardowe promocje liczbowe, zgodnie z opisem w §12.4.7, a operacja jest wykonywana z dokładnością .
Wynikiem operacji na wartościach typu decimal
jest to, co wynikałoby z obliczenia dokładnego wyniku (zachowanie skali zgodnie z definicją dla każdego operatora), a następnie zaokrąglenie w celu dopasowania do reprezentacji. Wyniki są zaokrąglane do najbliższej wartości reprezentującej, a gdy wynik jest równie zbliżony do dwóch godnych reprezentowania wartości, do wartości, która ma liczbę parzystą w najmniej znaczącej pozycji cyfry (jest to nazywane "zaokrąglanie bankiera"). Oznacza to, że wyniki są dokładne do co najmniej 28 miejsca dziesiętnego. Należy pamiętać, że zaokrąglanie może spowodować wygenerowanie wartości zerowej z wartości innej niż zero.
Jeśli operacja arytmetyczna decimal
generuje wynik, którego wielkość jest zbyt duża dla formatu decimal
, System.OverflowException
zostaje zgłoszony.
Typ decimal
ma większą precyzję, ale może mieć mniejszy zakres niż typy zmiennoprzecinkowe. W związku z tym konwersje z typów zmiennoprzecinkowych do decimal
mogą generować wyjątki przepełnienia, a konwersje z decimal
do typów zmiennoprzecinkowych mogą spowodować utratę dokładności lub wyjątki przepełnienia. Z tych powodów nie istnieją niejawne konwersje między typami zmiennoprzecinkowymi a decimal
, a bez jawnych rzutów występuje błąd kompilacji, gdy operandy zmiennoprzecinkowe i decimal
są bezpośrednio mieszane w tym samym wyrażeniu.
8.3.9 Typ wartości logicznej
Typ bool
reprezentuje logiczne wartości typu Boolean. Możliwe wartości typu bool
to true
i false
. Reprezentacja false
jest opisana w §8.3.3. Chociaż reprezentacja elementu true
jest nieokreślona, różni się ona od tej z .false
Między i innymi typami bool
wartości nie istnieją żadne standardowe konwersje. W szczególności bool
typ jest odrębny i oddzielony od typów całkowitych, a bool
wartość nie może być używana zamiast wartości całkowitej, i na odwrót.
Uwaga: W językach C i C++ wartość całkowita lub zmiennoprzecinkowa wynosząca zero lub wskaźnik o wartości null mogą być przekonwertowane na wartość logiczną
false
, a wartość całkowita lub zmiennoprzecinkowa niezerowa lub wskaźnik nienullowy mogą być przekonwertowane na wartość logicznątrue
. W języku C# takie konwersje są wykonywane przez jawne porównanie wartości całkowitej lub zmiennoprzecinkowej z zerem lub jawnie porównując odwołanie do obiektu .null
notatka końcowa
8.3.10 Typy wyliczeniowe
Typ wyliczenia jest odrębnym typem z nazwanymi stałymi. Każdy typ wyliczenia ma typ bazowy, który powinien być byte
, sbyte
, short
, ushort
, int
, uint
, long
lub ulong
. Zestaw wartości typu wyliczenia jest taki sam jak zestaw wartości bazowego typu. Wartości typu wyliczenia nie są ograniczone do wartości nazwanych stałych. Typy wyliczenia są definiowane za pomocą deklaracji wyliczenia (§19.2).
8.3.11 Typy krotek
Typ krotki reprezentuje uporządkowaną, stałej długości sekwencję wartości z opcjonalnymi nazwami i indywidualnymi typami. Liczba elementów w typie krotki jest określana jako jego arność. Typ krotki jest reprezentowany jako (T1 I1, ..., Tn In)
dla n ≥ 2, gdzie identyfikatory I1...In
są opcjonalnymi nazwami elementów krotki.
Ta składnia jest skrótem dla typu skonstruowanego z typów T1...Tn
z System.ValueTuple<...>
, który stanowi zestaw typów ogólnych strukturalnych, zdolnych bezpośrednio wyrażać typy krotek o dowolnej maksymalnej liczbie argumentów między dwoma a siedmioma, łącznie.
Nie ma potrzeby, aby istniała System.ValueTuple<...>
deklaracja, która bezpośrednio pasuje do liczby argumentów dowolnego typu krotki z odpowiadającą liczbą parametrów typowych. Zamiast tego krotka o liczbie elementów większej niż siedem jest reprezentowana za pomocą ogólnego typu System.ValueTuple<T1, ..., T7, TRest>
struktury, który oprócz elementów krotki ma pole Rest
zawierające zagnieżdżoną wartość pozostałych elementów, używając innego typu System.ValueTuple<...>
. Takie zagnieżdżanie może być obserwowane na różne sposoby, np. poprzez obecność Rest
pola. Jeśli wymagane jest tylko jedno dodatkowe pole, używany jest typ System.ValueTuple<T1>
struktury ogólnej; ten typ nie jest traktowany jako typ krotki. Jeśli wymagane jest więcej niż siedem dodatkowych pól, System.ValueTuple<T1, ..., T7, TRest>
jest używany rekursywnie.
Nazwy elementów w typie krotki muszą być unikalne. Nazwa elementu krotki w postaci ItemX
, gdzie X
jest dowolną sekwencją niepoprzedzonych przez 0
cyfr dziesiętnych, które mogą reprezentować pozycję elementu krotki, jest dozwolona tylko w pozycji oznaczonej przez X
.
Opcjonalne nazwy elementów nie są reprezentowane w typach ValueTuple<...>
i w środowisku uruchomieniowym nie są przechowywane w reprezentacji krotki wartości. Konwersje tożsamości (§10.2.2) istnieją między krotkami, które mają sekwencje typów elementów konwertowalne na zasadzie tożsamości.
Operatora new
nie można zastosować ze składnią typów krotek . Wartości krotki można tworzyć na podstawie wyrażeń krotki (§12.8.6) lub stosując operator new
bezpośrednio do typu skonstruowanego z ValueTuple<...>
.
Elementy krotki są polami publicznymi o nazwach Item1
, Item2
itp., do których można uzyskać dostęp za pomocą odwołania do elementu krotki w wartości krotki (§12.8.7). Ponadto jeśli typ krotki ma nazwę danego elementu, ta nazwa może służyć do uzyskiwania dostępu do danego elementu.
Uwaga: nawet jeśli duże krotki są reprezentowane przy użyciu wartości zagnieżdżonych
System.ValueTuple<...>
, dostęp do każdego elementu krotki można nadal uzyskiwać bezpośrednio za pomocąItem...
nazwy odpowiadającej jego pozycji. notatka końcowa
Przykład: biorąc pod uwagę następujące przykłady:
(int, string) pair1 = (1, "One"); (int, string word) pair2 = (2, "Two"); (int number, string word) pair3 = (3, "Three"); (int Item1, string Item2) pair4 = (4, "Four"); // Error: "Item" names do not match their position (int Item2, string Item123) pair5 = (5, "Five"); (int, string) pair6 = new ValueTuple<int, string>(6, "Six"); ValueTuple<int, string> pair7 = (7, "Seven"); Console.WriteLine($"{pair2.Item1}, {pair2.Item2}, {pair2.word}");
Typy krotek dla
pair1
,pair2
ipair3
są prawidłowe, z nazwami dla żadnych, niektórych lub wszystkich elementów typu krotki.Typ krotki dla
pair4
jest prawidłowy, ponieważ nazwyItem1
iItem2
pasują do swoich pozycji, natomiast typ krotki dlapair5
jest niedozwolony, ponieważ nazwyItem2
iItem123
nie pasują.Deklaracje dla
pair6
ipair7
pokazują, że typy krotek są zamienne ze skonstruowanymi typami w formieValueTuple<...>
, i że operatornew
jest dozwolony z tą drugą składnią.Ostatni wiersz pokazuje, że elementy krotki mogą być dostępne za pomocą nazwy
Item
odpowiadającej ich pozycji, a także poprzez odpowiednią nazwę elementu krotki, jeśli jest ona częścią typu. przykład końcowy
8.3.12 Typy wartości mogące przyjmować wartość null
Typ wartości dopuszczający null może reprezentować wszystkie wartości swojego typu bazowego oraz dodatkową wartość null. Typ wartości nullable jest zapisywany T?
, gdzie T
jest typem bazowym. Składnia System.Nullable<T>
jest skróconą formą System.Nullable<T>
, a obie formy mogą być używane zamiennie.
Z drugiej strony, typ wartości bez wartości null jest dowolnym typem wartości innym niż System.Nullable<T>
i jego skrótem T?
(dla dowolnego T
), plus dowolnym parametrem typu, który jest ograniczony jako typ wartości bez wartości null (czyli dowolny parametr typu z ograniczeniem typu wartości (§15.2.5)). Typ System.Nullable<T>
określa ograniczenie typu wartości dla T
, co oznacza, że bazowy typ typu wartości dopuszczanej do wartości null może być dowolnym typem wartości innej niż null. Podstawowy typ wartości dopuszczającej wartość null nie może być typem dopuszczającym wartość null ani typem referencyjnym. Na przykład int??
jest nieprawidłowym typem. Typy referencyjne zdolne do przyjmowania wartości null są objęte §8.9.
Wystąpienie wartościowego typu T?
, który dopuszcza wartość null, ma dwie publiczne właściwości tylko do odczytu.
-
HasValue
Właściwość typubool
-
Value
Właściwość typuT
Wystąpienie, dla którego HasValue
jest true
, jest uważane za mające wartość różną od null. Wystąpienie inne niż null zawiera znaną wartość i Value
zwraca wartość.
Wystąpienie, dla którego HasValue
jest uznawane za false
, jest uznawane za null. Wystąpienie o wartości null ma niezdefiniowaną wartość. Próba odczytania Value
w przypadku wystąpienia o wartości null powoduje wyrzucenie wyjątku System.InvalidOperationException
. Proces uzyskiwania dostępu do właściwości Value wystąpienia typu dopuszczającego wartość null jest określany jako unwrapping.
Oprócz konstruktora domyślnego każdy typ wartości nullable ma konstruktor publiczny z jednym parametrem typu T?
. Biorąc pod uwagę wartość x
typu T
, wywołanie konstruktora formularza
new T?(x)
Tworzy wystąpienie nie będące wartością null T?
, dla którego właściwość Value
ma wartość x
. Proces tworzenia nie-nullowego wystąpienia typu wartość nullable dla danej wartości jest określany jako zawijanie.
Konwersje niejawne można dokonywać od null
literału do T?
(§10.2.7) oraz od T
do T?
(§10.2.6).
Typ T?
wartości, która może być null, nie implementuje żadnych interfejsów (§18). W szczególności oznacza to, że nie implementuje żadnego interfejsu, który wykonuje typ bazowy T
.
8.3.13 Boxing i rozpakowywanie
Koncepcja boksowania i rozpakowania zapewnia most między value_types i reference_types, zezwalając na konwersję dowolnej wartości value_type na i z typu object
. Boxing i unboxing umożliwiają ujednolicony widok systemu typów, w którym wartość dowolnego typu może być ostatecznie traktowana jako object
.
Boxing jest opisany bardziej szczegółowo w §10.2.9 i rozpakowywanie jest opisane w §10.3.7.
8.4 Typy skonstruowane
8.4.1 Ogólne
Sama deklaracja typu ogólnego określa typ ogólny , który jest używany jako "strategia" do tworzenia wielu różnych typów, stosując argumenty typu. Argumenty typu są zapisywane w nawiasach kątowych (<
i >
) bezpośrednio po nazwie typu ogólnego. Typ zawierający co najmniej jeden argument typu jest nazywany typem skonstruowanym. Typ skonstruowany może być używany w większości miejsc w języku, w którym może pojawić się nazwa typu. Niezwiązany typ ogólny może być używany tylko w typeof_expression (§12.8.18).
Typy konstruowane mogą być również używane w wyrażeniach jako proste nazwy (§12.8.4) lub podczas uzyskiwania dostępu do elementu członkowskiego (§12.8.7).
Podczas oceniania namespace_or_type_name są brane pod uwagę tylko typy ogólne z prawidłową liczbą parametrów typu. W związku z tym można użyć tego samego identyfikatora do identyfikowania różnych typów, o ile typy mają różne liczby parametrów typu. Jest to przydatne podczas mieszania ogólnych i niegenerycznych klas w tym samym programie.
Przykład:
namespace Widgets { class Queue {...} class Queue<TElement> {...} } namespace MyApplication { using Widgets; class X { Queue q1; // Non-generic Widgets.Queue Queue<int> q2; // Generic Widgets.Queue } }
przykład końcowy
Szczegółowe reguły wyszukiwania nazw w namespace_or_type_name produkcji opisano w §7.8. Rozwiązanie niejednoznaczności w tych produkcjach jest opisane w §6.2.5.
Type_name może zidentyfikować skonstruowany typ, mimo że nie określa bezpośrednio parametrów typu. Może się tak zdarzyć, gdy typ jest zagnieżdżony w deklaracji ogólnej class
, a typ wystąpienia zawierającej deklarację jest niejawnie używany do wyszukiwania nazw (§15.3.9.7).
Przykład:
class Outer<T> { public class Inner {...} public Inner i; // Type of i is Outer<T>.Inner }
przykład końcowy
Typ, który nie jest wyliczeniowy, nie może być używany jako unmanaged_type (§8.8).
8.4.2 Argumenty typu
Każdy argument na liście argumentów typu jest po prostu typem.
type_argument_list
: '<' type_argument (',' type_argument)* '>'
;
type_argument
: type
| type_parameter nullable_type_annotation?
;
Każdy argument typu spełnia wszelkie ograniczenia dotyczące odpowiedniego parametru typu (§15.2.5). Argument typu odwołania, którego wartość null nie jest zgodna z wartością null parametru typu, spełnia ograniczenie; można jednak wydać ostrzeżenie.
8.4.3 Otwarte i zamknięte typy
Wszystkie typy mogą być klasyfikowane jako typy otwarte lub zamknięte. Typ otwarty to typ, który obejmuje parametry typu. Szczególnie:
- Parametr typu definiuje typ otwarty.
- Typ tablicy jest typem otwartym, jeśli i tylko wtedy, gdy jego typ elementu jest typem otwartym.
- Skonstruowany typ jest typem otwartym, jeśli i tylko wtedy, gdy co najmniej jeden argument typu jest typem otwartym. Skonstruowany typ zagnieżdżony jest typem otwartym wtedy i tylko wtedy, gdy co najmniej jeden z jego argumentów typu lub jeden z argumentów typu jego typów zawierających jest typem otwartym.
Zamknięty typ to typ, który nie jest typem otwartym.
W czasie wykonywania cały kod w ramach deklaracji typu ogólnego jest wykonywany w kontekście zamkniętego skonstruowanego typu, który został utworzony przez zastosowanie argumentów typu do deklaracji ogólnej. Każdy parametr typu w typie ogólnym jest powiązany z określonym typem czasu wykonywania. Przetwarzanie wszystkich instrukcji i wyrażeń w czasie wykonywania zawsze odbywa się z typami zamkniętymi, a typy otwarte występują tylko w procesie kompilacji.
Dwa zamknięte typy skonstruowane są konwertowane identycznie (§10.2.2), jeśli są skonstruowane z tego samego niezwiązanego typu ogólnego, a konwersja tożsamości istnieje między odpowiadającymi argumentami typów. Odpowiednie argumenty typu mogą same w sobie być zamknięte typy skonstruowane lub krotki, które są zamienialne przez tożsamość. Zamknięte konstrukcje typów, które są tożsamościowo przekształcalne, dzielą jeden zestaw zmiennych statycznych. W przeciwnym razie każdy zamknięty typ skonstruowany ma własny zestaw zmiennych statycznych. Ponieważ typ otwarty nie istnieje w czasie wykonywania, nie ma zmiennych statycznych związanych z typem otwartym.
8.4.4 Typy związane i niezwiązane
Termin typ niepowiązany odnosi się do typu niegenerykowego lub niezwiązanego typu ogólnego. Termin typ ograniczenia odnosi się do typu niegenerykowego lub skonstruowanego typu.
Typ niepowiązany odnosi się do jednostki zadeklarowanej przez deklarację typu. Niezwiązany typ ogólny nie jest typem i nie może być używany jako typ zmiennej, argumentu lub wartości zwracanej albo jako typ podstawowy. Jedyną konstrukcją, w której można odwoływać się do typu ogólnego bez wiązania, jest typeof
wyrażenie (§12.8.18).
8.4.5 Satysfakcjonujące ograniczenia
Przy każdym odwołaniu się do skonstruowanego typu lub metody ogólnej podane argumenty typu są sprawdzane względem ograniczeń parametrów typu zadeklarowanych dla typu ogólnego lub metody (§15.2.5). Dla każdej where
klauzuli argument A
typu odpowiadający nazwanego parametrowi typu jest sprawdzany względem każdego ograniczenia w następujący sposób:
- Jeśli ograniczenie jest typem, typem
class
interfejsu lub parametrem typu, niechC
reprezentuje to ograniczenie za pomocą podanych argumentów typu zastąpionych dowolnymi parametrami typu, które pojawiają się w ograniczeniu. Aby spełnić ograniczenie, musi być spełnione, że typA
jest konwertowany na typC
za pomocą jednego z następujących sposobów: - Jeśli ograniczenie jest ograniczeniem typu referencyjnego (
class
), typA
spełnia jeden z następujących warunków:-
A
jest typem interfejsu, typem klasy, typem delegata, typem tablicy lub typem dynamicznym.
Uwaga:
System.ValueType
iSystem.Enum
są typami referencyjnymi, które spełniają to ograniczenie. notatka końcowa-
A
jest parametrem typu, który jest znany jako typ odwołania (§8.2).
-
- Jeśli ograniczenie jest ograniczeniem typu wartości (
struct
), typA
spełnia jeden z następujących warunków:-
A
jest typemstruct
lub typemenum
, ale nie jest typem wartości dopuszczającej wartości null.
Uwaga:
System.ValueType
iSystem.Enum
są typami referencyjnymi, które nie spełniają tego ograniczenia. notatka końcowa-
A
to parametr typu, który ma ograniczenie typu wartości (§15.2.5).
-
- Jeśli ograniczenie jest ograniczeniem
new()
konstruktora, typA
nie powinien byćabstract
i ma publiczny konstruktor bez parametrów. Jest to spełnione, jeśli spełniony jest jeden z następujących warunków:-
A
jest typem wartości, ponieważ wszystkie typy wartości mają publiczny konstruktor domyślny (§8.3.3). -
A
jest parametrem typu mającym ograniczenie konstruktora (§15.2.5). -
A
to parametr typu, który ma ograniczenie typu wartości (§15.2.5). -
A
jest elementemclass
, który nie jest abstrakcyjny i zawiera jawnie zadeklarowany publiczny konstruktor bez parametrów. -
A
nie jestabstract
i ma konstruktor domyślny (§15.11.5).
-
Błąd czasu kompilacji występuje, jeśli co najmniej jedno ograniczenie parametru typu nie jest spełnione przez podane argumenty typu.
Ponieważ parametry typu nie są dziedziczone, ograniczenia nigdy nie są dziedziczone.
Przykład: W poniższym
D
należy określić ograniczenie dla typu parametruT
, abyT
spełniało ograniczenie nałożone przez bazęclass
B<T>
. W przeciwieństwie do tego,class
E
nie musi określać ograniczenia, ponieważList<T>
implementujeIEnumerable
dla dowolnegoT
.class B<T> where T: IEnumerable {...} class D<T> : B<T> where T: IEnumerable {...} class E<T> : B<List<T>> {...}
przykład końcowy
8.5 Parametry typu
Parametr typu to identyfikator określający typ wartości lub typ odwołania, z którego parametr jest powiązany w czasie wykonywania.
type_parameter
: identifier
;
Ponieważ parametr typu można zainstancjonować z wieloma argumentami różnych typów, operacje i ograniczenia parametrów typu są nieco inne niż w przypadku innych typów.
Uwaga: należą do nich:
- Parametru typu nie można używać bezpośrednio do deklarowania klasy bazowej (§15.2.4.2) ani interfejsu (§18.2.4).
- Reguły wyszukiwania elementów członkowskich dla parametrów typu zależą od zastosowanych ograniczeń, o ile takie istnieją dla parametru typu. Zostały one szczegółowo opisane w §12.5.
- Dostępne konwersje parametru typu zależą od ograniczeń, jeśli istnieją, zastosowanych do parametru typu. Są one szczegółowo opisane w §10.2.12 i §10.3.8.
- Literał
null
nie może być konwertowany na typ podany przez parametr typu, chyba że parametr typu jest znany jako typ referencyjny (§10.2.12). Można jednak użyć wyrażenia domyślnego (§12.8.21). Ponadto wartość o typie podanym przez parametr typu może być porównywana z wartością null przy użyciu parametru==
i!=
(§12.12.7), chyba że parametr typu ma ograniczenie typu wartości.- Wyrażenie
new
(§12.8.17.2) może być używane tylko z parametrem typu, jeśli parametr typu jest ograniczony przez constructor_constraint lub ograniczenie typu wartości (§15.2.5).- Parametr typu nie może być używany w dowolnym miejscu w atrybucie.
- Nie można użyć parametru typu w dostępie składowym (§12.8.7) ani nazwy typu (§7.8) do identyfikacji statycznego elementu członkowskiego lub typu zagnieżdżonego.
- Nie można użyć parametru typu jako unmanaged_type (§8.8).
notatka końcowa
Jako typ parametry typu są wyłącznie konstrukcją czasu kompilacji. W czasie wykonywania każdy parametr typu jest powiązany z typem czasu wykonywania określonym przez podanie argumentu typu do deklaracji typu ogólnego. W związku z tym typ zmiennej zadeklarowanej przy użyciu parametru typu będzie w czasie wykonywania typu zamkniętego typu §8.4.3. Wykonywanie w czasie wykonywania wszystkich instrukcji i wyrażeń obejmujących parametry typu używa typu, który został podany jako argument typu dla tego parametru.
8.6 Typy drzewa wyrażeń
Drzewa wyrażeń umożliwiają reprezentację wyrażeń lambda jako struktury danych zamiast kodu wykonywalnego. Drzewa wyrażeń to wartości typów drzewa wyrażeń formularza System.Linq.Expressions.Expression<TDelegate>
, gdzie TDelegate
jest dowolnym typem delegata. W pozostałej części tej specyfikacji te typy będą określane przy użyciu skrótu Expression<TDelegate>
.
Jeśli istnieje konwersja z wyrażenia lambda na typ delegata D
, to istnieje również konwersja na typ drzewa wyrażeń Expression<TDelegate>
. Podczas gdy konwersja wyrażenia lambda na typ delegata generuje delegata, który odwołuje się do kodu wykonywalnego dla wyrażenia lambda, konwersja na typ drzewa wyrażeń tworzy reprezentację drzewa wyrażeń tego wyrażenia lambda. Więcej szczegółów tej konwersji podano w §10.7.3.
Przykład: Poniższy program reprezentuje wyrażenie lambda zarówno jako kod wykonywalny, jak i jako drzewo wyrażeń. Ponieważ istnieje konwersja na
Func<int,int>
, istnieje również konwersja naExpression<Func<int,int>>
:Func<int,int> del = x => x + 1; // Code Expression<Func<int,int>> exp = x => x + 1; // Data
Po tych przydziałach delegat
del
odwołuje się do metody zwracającejx + 1
, a drzewo wyrażeń exp odwołuje się do struktury danych, która opisuje wyrażeniex => x + 1
.przykład końcowy
Expression<TDelegate>
zapewnia metodę wystąpienia Compile
, która tworzy delegata typu TDelegate
:
Func<int,int> del2 = exp.Compile();
Wywołanie tego delegata powoduje wykonanie kodu reprezentowanego przez drzewo wyrażeń. W związku z tym, biorąc pod uwagę powyższe definicje, del
i del2
są równoważne, a następujące dwie instrukcje będą miały taki sam efekt:
int i1 = del(1);
int i2 = del2(1);
Po wykonaniu tego kodu zarówno i1
, jak i i2
będą miały wartość 2
.
Powierzchnia API dostarczana przez Expression<TDelegate>
jest definiowana przez implementację, poza wymaganiem dla opisanej powyżej metody Compile
.
Uwaga: Chociaż szczegóły interfejsu API podane dla drzew wyrażeń są zdefiniowane przez implementację, oczekuje się, że implementacja:
- Umożliwienie inspekcji struktury drzewa wyrażeń i odpowiedzi na nie, które powstało w wyniku konwersji wyrażenia lambda
- Włącz tworzenie drzew wyrażeń programowo w kodzie użytkownika
notatka końcowa
8.7 Typ dynamiczny
Typ dynamic
używa powiązania dynamicznego, jak opisano szczegółowo w §12.3.2, w przeciwieństwie do powiązania statycznego, który jest używany przez wszystkie inne typy.
Typ dynamic
jest uznawany za identyczny object
z wyjątkiem następujących kwestii:
- Operacje na wyrażeniach typu
dynamic
mogą być dynamicznie powiązane (§12.3.3). - Wnioskowanie typu (§12.6.3) będzie preferować
dynamic
nadobject
, jeśli obie są kandydatami. -
dynamic
nie można użyć jako- typ w wyrażeniu tworzenia obiektu (§12.8.17.2)
- klasa_bazowa (§15.2.4)
- typ_predefiniowany w dostęp_do_członka (§12.8.7.1)
- operand operatora
typeof
- argument atrybutu
- ograniczenie
- typ metody rozszerzenia
- jakakolwiek część argumentu typu w struct_interfaces (§16.2.5) lub interface_type_list (§15.2.4.1).
Ze względu na tę równoważność, zachodzi następujące twierdzenie:
- Istnieje niejawna konwersja tożsamości
- między
object
idynamic
- między skonstruowanymi typami, które są takie same przy zastąpieniu
dynamic
przezobject
- między typami tupli, które są identyczne przy zamianie
dynamic
naobject
- między
- Niejawne i jawne konwersje do i z
object
mają również zastosowanie do i zdynamic
. - Podpisy, które są takie same podczas zastępowania
dynamic
za pomocąobject
, są uznawane za ten sam podpis. - Typ
dynamic
jest nie do odróżnienia od typuobject
w czasie wykonywania. - Wyrażenie typu
dynamic
jest określane jako wyrażenie dynamiczne.
8.8 Typy niezarządzane
unmanaged_type
: value_type
| pointer_type // unsafe code support
;
unmanaged_type to dowolny typ, który nie jest ani reference_type, ani type_parameter, który nie jest ograniczony do tego, aby nie być niezarządzanym, i nie zawiera pól instancji, których typ nie jest unmanaged_type. Innymi słowy, unmanaged_type jest jednym z następujących:
-
sbyte
,byte
,short
,ushort
,int
,uint
,long
,ulong
,char
,float
,double
,decimal
, lubbool
. - Dowolny enum_type.
- Każda zdefiniowana przez użytkownika struktura struct_type, która zawiera tylko pola instancji typu unmanaged_type.
- Dowolny parametr typu, który musi być niezarządzany.
- Każde pointer_type (§23.3).
8.9 Typy referencyjne i nullowalność
8.9.1 Ogólne
Typ odwołania dopuszczającego wartość null jest oznaczany przez dołączenie nullable_type_annotation (?
) do typu odwołania bez wartości null. Nie ma żadnej semantycznej różnicy między typem odwołania nienależącym do wartości null a odpowiadającym mu typem dopuszczanym do wartości null, oba mogą być odwołaniem do obiektu lub null
. Obecność lub brak nullable_type_annotation deklaruje, czy wyrażenie ma zezwalać na wartości null, czy nie. Kompilator może zapewnić diagnostykę, gdy wyrażenie nie jest używane zgodnie z tym zamiarem. Stan null wyrażenia jest zdefiniowany w §8.9.5. Istnieje konwersja tożsamości pomiędzy typem odwołania dopuszczającym null a jego odpowiadającym typem odwołania niedopuszczającym null (§10.2.2).
Istnieją dwie formy nullowalności dla typów referencyjnych:
-
nullable: można przypisać
null
. Domyślny stan null to może mieć wartość null. -
niemające wartości null: Odwołaniu niemającemu wartości null nie należy przypisywać wartości
null
null. Domyślny stan null nie ma wartości null.
Uwagi: Typy
R
iR?
są reprezentowane przez ten sam typ bazowy,R
. Zmienna tego typu bazowego może zawierać odwołanie do obiektu lub wartośćnull
, która wskazuje "brak odwołania". notatka końcowa
Rozróżnienie składniowe między nullowalnym typem referencyjnym a odpowiadającym mu nienullowalnym typem referencyjnym pozwala kompilatorowi generować diagnostykę. Kompilator musi zezwolić na nullable_type_annotation zgodnie z definicją w §8.2.1. Diagnostyka musi być ograniczona do ostrzeżeń. Ani obecność ani brak adnotacji dopuszczanych do wartości null, ani stan kontekstu dopuszczalnego do wartości null nie może zmienić czasu kompilacji lub zachowania środowiska uruchomieniowego programu z wyjątkiem zmian w żadnych komunikatach diagnostycznych generowanych w czasie kompilacji.
8.9.2 Typy odwołań bez wartości null
Niezmienny typ referencyjny jest typem referencyjnym w formie T
, gdzie T
jest nazwą typu. Domyślny stan null zmiennej, która nie może zawierać wartości null, nie jest równa null. Ostrzeżenia mogą być generowane, gdy używane jest wyrażenie, które może mieć wartość null, tam, gdzie wymagana jest wartość nie-null.
8.9.3 Typy referencyjne dopuszczające wartość null
Typ odwołania formularza T?
(na przykład string?
) jest typem odwołania dopuszczanym do wartości null. Domyślny stan null zmiennej dopuszczanej do wartości null może mieć wartość null. Adnotacja ?
wskazuje, że zmienne tego typu mogą przyjmować wartość null. Kompilator może rozpoznać te intencje w celu wystawienia ostrzeżeń. Gdy kontekst adnotacji dopuszczający wartość null jest wyłączony, użycie tej adnotacji może wygenerować ostrzeżenie.
Kontekst dopuszczający wartości null 8.9.4
8.9.4.1 Ogólne
Każdy wiersz kodu źródłowego ma kontekst z możliwością wartości null. Flagi adnotacji i ostrzeżeń dla kontrolek kontekstu adnotacji dozwalających null (§8.9.4.3) i ostrzeżeń dozwalających null (§8.9.4.4), odpowiednio. Każda flaga może być włączona lub wyłączona. Kompilator może używać analizy przepływu statycznego do określania stanu null dowolnej zmiennej referencyjnej. Stan null zmiennej referencyjnej (§8.9.5) nie ma wartości null, może mieć wartość null, a może wartość domyślną.
Kontekst dopuszczalny do wartości null może być określony w kodzie źródłowym za pośrednictwem dyrektyw dopuszczanych do wartości null (§6.5.9) i/lub za pośrednictwem określonego mechanizmu specyficznego dla implementacji zewnętrznego kodu źródłowego. Jeśli oba podejścia są używane, dyrektywy pozwalające na wartości null zastępują ustawienia dokonane przez mechanizm zewnętrzny.
Domyślny stan kontekstu dopuszczanego do wartości null jest zdefiniowany przez implementację.
W całej tej specyfikacji zakłada się, że cały kod języka C#, który nie zawiera dyrektyw nullable ani nie ma określonego bieżącego stanu kontekstu nullable, został skompilowany przy użyciu kontekstu nullable, w którym włączono zarówno adnotacje, jak i ostrzeżenia.
Uwaga: Kontekst dopuszczalny do wartości null, w którym obie flagi są wyłączone, jest zgodny z poprzednim standardowym zachowaniem typów referencyjnych. notatka końcowa
Wyłączenie funkcji Nullable
Gdy flagi ostrzeżenia i adnotacji są wyłączone, kontekst dopuszczający wartość null jest wyłączony.
Gdy kontekst dopuszczalny do wartości null jest wyłączony:
- Nie zostanie wygenerowane żadne ostrzeżenie, gdy zmienna typu odwołania bez adnotacji jest inicjowana lub przypisywana wartość
null
. - Nie będzie wygenerowane ostrzeżenie, gdy zmienna typu referencyjnego może mieć wartość null.
- W przypadku dowolnego typu
T
odwołania, adnotacja?
wT?
generuje komunikat, a typT?
jest taki sam jakT
. - W przypadku ograniczenia
where T : C?
parametru dowolnego typu adnotacja?
w plikuC?
generuje komunikat, a typC?
jest taki sam jakC
. - W przypadku ograniczenia
where T : U?
parametru dowolnego typu adnotacja?
w plikuU?
generuje komunikat, a typU?
jest taki sam jakU
. - Ograniczenie ogólne
class?
generuje komunikat ostrzegawczy. Parametr typu musi być typem referencyjnym.Uwaga: ten komunikat jest scharakteryzowany jako "informacyjny", a nie "ostrzeżenie", tak aby nie mylić go ze stanem ustawienia ostrzeżenia dopuszczalnego do wartości null, które nie jest powiązane. notatka końcowa
- Operator
!
ignorujący wartość null (§12.8.9) nie ma wpływu.
Przykład:
#nullable disable annotations string? s1 = null; // Informational message; ? is ignored string s2 = null; // OK; null initialization of a reference s2 = null; // OK; null assignment to a reference char c1 = s2[1]; // OK; no warning on dereference of a possible null; // throws NullReferenceException c1 = s2![1]; // OK; ! is ignored
przykład końcowy
8.9.4.3 Adnotacje obsługujące wartości null
Kiedy flaga ostrzeżeń jest wyłączona, a flaga adnotacji jest włączona, kontekst wartości null to adnotacje.
Gdy kontekst, w którym dopuszczalne są wartości null, jest określony przez adnotacje:
- W przypadku dowolnego typu
T
odwołania adnotacja?
wT?
wskazuje, żeT?
to typ dopuszczający wartość null, natomiast nieoznaczoneT
nie dopuszcza wartości null. - Nie są generowane żadne ostrzeżenia diagnostyczne związane z wartością null.
- Operator
!
tworzący stan null (§12.8.9) może zmienić analizowany stan null operandu i wygenerowane ostrzeżenia diagnostyczne podczas kompilacji.
Przykład:
#nullable disable warnings #nullable enable annotations string? s1 = null; // OK; ? makes s2 nullable string s2 = null; // OK; warnings are disabled s2 = null; // OK; warnings are disabled char c1 = s2[1]; // OK; warnings are disabled; throws NullReferenceException c1 = s2![1]; // No warnings
przykład końcowy
Ostrzeżenia dotyczące typu nullable
Gdy flaga ostrzeżenia jest włączona, a flaga adnotacji jest wyłączona, kontekst dopuszczający wartość null jest ostrzegawczy.
Gdy kontekst nullable jest ostrzeżenia, kompilator może wygenerować diagnostykę w następujących przypadkach:
- Zmienna referencyjna, która została określona jako może mieć wartość null, jest wyłuszczana.
- Zmienna referencyjna typu innego niż null jest przypisywana do wyrażenia, które może mieć wartość null.
- Element
?
jest używany do oznaczenia typu odwołania, które może przyjmować wartość null. - Operator
!
ignorujący wartość null (§12.8.9) służy do ustawiania operandu jako niepusty.
Przykład:
#nullable disable annotations #nullable enable warnings string? s1 = null; // OK; ? makes s2 nullable string s2 = null; // OK; null-state of s2 is "maybe null" s2 = null; // OK; null-state of s2 is "maybe null" char c1 = s2[1]; // Warning; dereference of a possible null; // throws NullReferenceException c1 = s2![1]; // The warning is suppressed
przykład końcowy
8.9.4.5 Włączenie obsługi typów nullable
Gdy zarówno flaga ostrzeżenia, jak i flaga adnotacji są włączone, kontekst dopuszczający wartość jest włączony.
Gdy kontekst dopuszczający wartości null jest włączony:
- W przypadku dowolnego typu
T
odwołania, adnotacja?
w elemencieT?
sprawia, żeT?
jest typem dopuszczającym wartości null, natomiastT
bez adnotacji jest nieodpuszczający wartości null. - Kompilator może używać analizy przepływu statycznego do określania stanu null dowolnej zmiennej referencyjnej. Gdy są włączone ostrzeżenia o wartości null, stan null dla zmiennej referencyjnej (§8.9.5) jest nie ma wartości 'null', może mieć wartość null, lub może być wartością domyślną oraz
- Operator
!
zapewniający nietraktowanie jako null (§12.8.9) ustawia stan operandu na nie null. - Kompilator może wydać ostrzeżenie, jeśli wartość null parametru typu nie jest zgodna z wartością null odpowiedniego argumentu typu.
8.9.5 Wartości null i stany null
8.9.5.1 Ogólne
Kompilator nie jest wymagany do przeprowadzenia żadnej analizy statycznej ani nie jest wymagany do wygenerowania żadnych ostrzeżeń diagnostycznych związanych z wartością null.
Pozostała część tej podklauzuli jest warunkowo normatywna.
Analiza przepływu 8.9.5.2
Kompilator, który generuje ostrzeżenia diagnostyczne, jest zgodny z tymi regułami.
Każde wyrażenie ma jeden z trzech stanów null:
- może mieć wartość null: wartość wyrażenia może mieć wartość null.
- być może wartość domyślna: wartość wyrażenia może mieć wartość domyślną dla tego typu.
- not null: wartość wyrażenia nie jest wartością null.
Domyślny stan null wyrażenia jest określany przez jego typ i stan flagi adnotacji po zadeklarowaniu:
- Domyślny stan null dla typu odwołania dopuszczającego wartość null to:
- Może ma wartość null, gdy jego deklaracja znajduje się w tekście, w którym jest włączona flaga adnotacji.
- Nie jest nullem, gdy deklaracja znajduje się w tekście, w którym flaga adnotacji jest nieaktywna.
- Domyślny stan typu odwołania niemającego wartości null nie jest wartością null.
Uwaga: Stan może być domyślny jest używany z nieskrępowanymi parametrami typu, gdy typ jest niemogący być nullem, taki jak
string
, a wyrażeniedefault(T)
jest wartością null. Ponieważ wartość null nie znajduje się w domenie dla typu nieprzyjmującego wartości null, stan może być domyślny. notatka końcowa
Diagnostykę można utworzyć, gdy zmienna (§9.2.1) typu odniesienia niezdolnego do przyjęcia wartości null jest inicjowana lub przypisywana do wyrażenia, które może mieć wartość null, w sytuacji, gdy ta zmienna jest zadeklarowana w tekście z włączoną flagą adnotacji.
Przykład: Rozważmy następującą metodę, w której parametr ma wartość null, a ta wartość jest przypisywana do typu niezwiązanego z wartością null:
#nullable enable public class C { public void M(string? p) { // Warning: Assignment of maybe null value to non-nullable variable string s = p; } }
Kompilator może wydać ostrzeżenie, w którym parametr, który może mieć wartość null, jest przypisany do zmiennej, która nie powinna mieć wartości null. Jeśli parametr jest sprawdzany pod kątem wartości null przed przypisaniem, kompilator może użyć tego w analizie stanu nullowalności i nie wydać ostrzeżenia.
#nullable enable public class C { public void M(string? p) { if (p != null) { string s = p; // No warning // Use s } } }
przykład końcowy
Kompilator może zaktualizować stan null zmiennej w ramach analizy.
Przykładowy: kompilator może wybrać aktualizację stanu na podstawie dowolnych instrukcji w programie:
#nullable enable public void M(string? p) { int length = p.Length; // Warning: p is maybe null string s = p; // No warning. p is not null if (s != null) { int l2 = s.Length; // No warning. s is not null } int l3 = s.Length; // Warning. s is maybe null }
W poprzednim przykładzie kompilator może zdecydować, że po instrukcji
int length = p.Length;
stanp
jest nie-null. Gdyby to miało wartość null, instrukcja ta zgłosiłaby wartośćNullReferenceException
. Jest to podobne do zachowania, jakie wystąpiłoby, gdyby kod został poprzedzonyif (p == null) throw NullReferenceException();
, z tym wyjątkiem, że kod w obecnej formie może wygenerować ostrzeżenie, informujące, że wyjątek może zostać zgłoszony niejawnie. przykład końcowy
W dalszej części metody kod sprawdza, czy s
nie jest odwołaniem o wartości null. Stan zerowy/niczny s
może stać się zerowy/jak null po zamknięciu bloku sprawdzającego wartość null. Kompilator może wnioskować, że s
może mieć wartość null, ponieważ kod został napisany, aby założyć, że może mieć wartość null. Ogólnie rzecz biorąc, gdy kod zawiera sprawdzanie wartości null, kompilator może wnioskować, że wartość mogłaby być null.
Przykład: każde z następujących wyrażeń zawiera jakąś formę sprawdzania wartości null. Stan nulości
o
może ulec zmianie z nie-null na null po każdej z poniższych instrukcji.#nullable enable public void M(string s) { int length = s.Length; // No warning. s is not null _ = s == null; // Null check by testing equality. The null state of s is maybe null length = s.Length; // Warning, and changes the null state of s to not null _ = s?.Length; // The ?. is a null check and changes the null state of s to maybe null if (s.Length > 4) // Warning. Changes null state of s to not null { _ = s?[4]; // ?[] is a null check and changes the null state of s to maybe null _ = s.Length; // Warning. s is maybe null } }
Deklaracje zarówno auto-właściwości, jak i zdarzeń podobnych do pól korzystają z pola zapasowego wygenerowanego przez kompilator. Analiza stanu null może wnioskować, że przypisanie do zdarzenia lub właściwości jest przypisaniem do pola zapasowego wygenerowanego przez kompilator.
Przykład: kompilator może określić, że zapisywanie właściwości automatycznej lub zdarzenia podobnego do pola zapisuje odpowiednie pole pomocnicze wygenerowane przez kompilator. Stan null właściwości jest zgodny z wartością pola zapasowego.
class Test { public string P { get; set; } public Test() {} // Warning. "P" not set to a non-null value. static void Main() { var t = new Test(); int len = t.P.Length; // No warning. Null state is not null. } }
W poprzednim przykładzie konstruktor nie ustawia
P
na wartość niepustą, a kompilator może wyświetlić ostrzeżenie. Nie ma ostrzeżenia, gdy uzyskuje się dostęp do właściwościP
, ponieważ typ tej właściwości jest typem odwołania bez wartości null. przykład końcowy
Kompilator może traktować właściwość (§15.7) jako zmienną ze stanem lub jako niezależne akcesory dostępu get i set (§15.7.3).
Przykład: kompilator może wybrać, czy zapisywanie we właściwości zmienia stan null odczytywania właściwości, czy odczytywanie właściwości zmienia stan null tej właściwości.
class Test { private string? _field; public string? DisappearingProperty { get { string tmp = _field; _field = null; return tmp; } set { _field = value; } } static void Main() { var t = new Test(); if (t.DisappearingProperty != null) { int len = t.DisappearingProperty.Length; // No warning. A compiler can assume property is stateful } } }
W poprzednim przykładzie pole pomocnicze dla
DisappearingProperty
zostaje ustawione na null podczas odczytu. Jednak kompilator może założyć, że odczytywanie właściwości nie zmienia stanu null tego wyrażenia. przykład końcowy
Kompilator może użyć dowolnego wyrażenia, które wyłusza zmienną, właściwość lub zdarzenie, aby ustawić stan null na wartość nie null. Gdyby miało wartość null, wyrażenie dereferencji rzuciłoby wyjątek NullReferenceException
:
Przykład:
public class C { private C? child; public void M() { _ = child.child.child; // Warning. Dereference possible null value var greatGrandChild = child.child.child; // No warning. } }
przykład końcowy
8.9.5.3 Konwersje typów
Kompilator, który generuje ostrzeżenia diagnostyczne, jest zgodny z tymi regułami.
Uwaga: Różnice w adnotacjach nullowalności najwyższego poziomu lub zagnieżdżonych adnotacjach nullowalności w typach nie mają wpływu na to, czy konwersja między typami jest dozwolona, ponieważ nie ma różnicy semantycznej między typem referencyjnym nienullowalnym a odpowiadającym mu typem nullowalnym (§8.9.1). notatka końcowa
Kompilator może wydać ostrzeżenie, gdy anotacje nullowalności różnią się między dwoma typami, na najwyższym poziomie lub zagnieżdżone, podczas gdy konwersja jest zawężająca.
Przykład: typy różniące się adnotacjami najwyższego poziomu
#nullable enable public class C { public void M1(string p) { _ = (string?)p; // No warning, widening } public void M2(string? p) { _ = (string)p; // Warning, narrowing _ = (string)p!; // No warning, suppressed } }
przykład końcowy
Przykład: typy o zróżnicowanych zagnieżdżonych adnotacjach nullowości
#nullable enable public class C { public void M1((string, string) p) { _ = ((string?, string?))p; // No warning, widening } public void M2((string?, string?) p) { _ = ((string, string))p; // Warning, narrowing _ = ((string, string))p!; // No warning, suppressed } }
przykład końcowy
Kompilator może przestrzegać reguł wariancji interfejsu (§18.2.3.3), wariancji delegata (§20.4) i kowariancji tablicy (§17.6) podczas określania, czy ma zostać wyświetlone ostrzeżenie dotyczące konwersji typów.
#nullable enable public class C { public void M1(IEnumerable<string> p) { IEnumerable<string?> v1 = p; // No warning } public void M2(IEnumerable<string?> p) { IEnumerable<string> v1 = p; // Warning IEnumerable<string> v2 = p!; // No warning } public void M3(Action<string?> p) { Action<string> v1 = p; // No warning } public void M4(Action<string> p) { Action<string?> v1 = p; // Warning Action<string?> v2 = p!; // No warning } public void M5(string[] p) { string?[] v1 = p; // No warning } public void M6(string?[] p) { string[] v1 = p; // Warning string[] v2 = p!; // No warning } }
przykład końcowy
Kompilator może wydać ostrzeżenie, gdy nullowalność różni się w obie strony w typach, które nie zezwalają na konwersję wariantu.
#nullable enable public class C { public void M1(List<string> p) { List<string?> v1 = p; // Warning List<string?> v2 = p!; // No warning } public void M2(List<string?> p) { List<string> v1 = p; // Warning List<string> v2 = p!; // No warning } }
przykład końcowy
Koniec warunkowo normatywnego tekstu
ECMA C# draft specification