8 typów
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 tych zmiennych typów wartości bezpośrednio zawierają swoje dane, podczas gdy zmienne typów referencyjnych przechowują odwołania do ich danych, z których 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żywania ich pełnej kwalifikacji. 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 dynamic
lub typ. Dla każdego typu odwołania bez wartości null istnieje odpowiedni typ odwołania dopuszczający wartość null, dołączając element ?
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 , który jest znany 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_expression s (§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 |
Ostateczna 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ętowanym typem klasy, który dziedziczy bezpośrednio z object
klasy .
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 nie tylko odwołanie do punktu wejścia metody, ale także odwołanie do wystąpienia obiektu, na którym należy wywołać metodę. 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 odwołania zmienna typu wartości może zawierać wartość null
tylko wtedy, gdy typ wartości jest typem wartości dopuszczanej do wartości null (§8.3.12). Dla każdego typu wartości innej niż null istnieje odpowiadający typ wartości dopuszczającej 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ą generowane automatycznie.
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
ulong
wartość domyślna to .0
- 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
. -
E
wartość domyślna to0
, przekonwertowana na typE
.
- W przypadku
- W przypadku struct_type wartość domyślna to wartość wygenerowana przez ustawienie wszystkich pól typu wartości na wartość domyślną i wszystkich pól typu odwołania na
null
wartość . - W przypadku nullable_value_type wartość domyślna to 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 dopuszczanej do wartości 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 struct
typów 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 struct
typów w System
przestrzeni nazw, jak opisano w poniższej tabeli.
Słowo kluczowe | Typ aliasu |
---|---|
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 zadeklarowane elementy członkowskie iSystem.Int32
członkowie dziedziczone 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 podpisaną precyzją 32-bitową, niepodpisaną precyzją 32-bitową, podpisaną precyzją 64-bitową lub 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 żebyte
typy iushort
mają zakresy wartości, które są w pełni reprezentowane przy użyciuchar
typu, niejawnych konwersji z bajtu, bajtu lubushort
niechar
istnieją. -
char
Stałe typu są zapisywane jako character_literals lub jako integer_literalw połączeniu z rzutowania do typu char.
Przykład:
(char)10
jest taki sam jak'\x000A'
. przykład końcowy
Operatory checked
i unchecked
instrukcje służą do kontrolowania kontroli przepełnienia dla operacji arytmetycznych i konwersji typu całkowitego (§12.8.20).
checked
W kontekście przepełnienie generuje błąd czasu kompilacji lub powoduje System.OverflowException
zgłoszenie.
unchecked
W kontekście przepełnienia są ignorowane, a wszystkie bity o wysokiej kolejności, które nie mieszczą się w typie docelowym, są odrzucane.
Typy zmiennoprzecinkowe 8.3.7
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. Liczba niedokończona jest generowana 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 skracana naN. Sieci NaN są generowane przez nieprawidłowe operacje zmiennoprzecinkowe, takie jak dzielenie zera przez zero.
- Skończony zestaw wartości innych niż zero formularza × m × 2e, gdzie s jest 1 lub −1, a m i e są określane przez określony typ zmiennoprzecinkowy: dla
float
, 0 <m< 2²⁴ i −149 ≤ e ≤ 104, a dladouble
, 0 << 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 operand operatora binarnego jest typem zmiennoprzecinkowym, stosowane są standardowe promocje liczbowe, zgodnie z opisem w §12.4.7, a operacja jest wykonywana z dokładnością lub float
precyzjądouble
.
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 wartości reprezentującej w formacie docelowym.
- Jeśli wielkość wyniku operacji zmiennoprzecinkowych jest zbyt mała dla formatu docelowego, wynik operacji staje się dodatni zero lub zero ujemne.
- 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ę nazwą 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ą typ zmiennoprzecinkowa "rozszerzony" lub "długi podwójny" z większą dokładnością i dokładnością niż
double
typ, a niejawnie wykonują wszystkie operacje zmiennoprzecinkowe przy użyciu tego wyższego typu precyzji. Tylko w przypadku nadmiernego kosztu wydajności takie architektury sprzętowe mogą być wykonywane w celu wykonywania operacji zmiennoprzecinkowych z mniejszą precyzją i zamiast wymagać implementacji, aby przejąć zarówno wydajność, jak i precyzję, język C# umożliwia użycie większego typu 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
to forma (–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 ≤ Emax, gdzie Cmax wynosi co najmniej 1 × 10²⁸, Emin ≤ 0, i Emax ≥ 28. Typ decimal
nie musi obsługiwać podpisanych zer, niedociągnięć ani sieci NaN.
Wartość A decimal
jest reprezentowana jako liczba całkowita skalowana przez potęgę dziesięciu. Dla decimal
wartości bezwzględnej mniejszej niż 1.0m
, wartość jest dokładna do co najmniej 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 float
typów danych i double
liczby ułamkowe dziesiętne, takie jak 0.1
liczby dziesiętne, mogą być reprezentowane dokładnie w reprezentacji dziesiętnej.
float
W reprezentacjach i double
takie liczby często mają niezwiązane rozszerzenia binarne, dzięki czemu te reprezentacje są bardziej podatne na błędy zaokrąglania.
Jeśli którykolwiek operand operatora binarnego jest typu, stosowane są decimal
standardowe promocje liczbowe, zgodnie z opisem w §12.4.7, a operacja jest wykonywana z dokładnością double
.
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.
decimal
Jeśli operacja arytmetyczna generuje wynik, którego wielkość jest zbyt duża dla decimal
formatu, System.OverflowException
jest zgłaszany.
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 mogą decimal
generować wyjątki przepełnienia, a konwersje z decimal
do typów zmiennoprzecinkowych mogą spowodować utratę dokładności lub wyjątków przepełnienia. Z tych powodów nie istnieją niejawne konwersje między typami zmiennoprzecinkowych i decimal
, a bez jawnych rzutów, gdy zmiennoprzecinkowe i decimal
operandy są bezpośrednio mieszane w tym samym wyrażeniu.
8.3.9 Typ wartości logicznej
Typ bool
reprezentuje logiczne ilości logiczne. Możliwe wartości typu bool
to true
i false
. Reprezentacja jest opisana false
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, bool
a 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 zero lub wskaźnik o wartości null można przekonwertować na wartość
false
logiczną , a wartość całkowita lub zmiennoprzecinkowa niezerowa lub wskaźnik zmiennoprzecinkowa można przekonwertować na wartośćtrue
logiczną . 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 wyliczenia
Typ wyliczenia jest odrębnym typem z nazwanymi stałymi. Każdy typ wyliczenia ma typ bazowy, który ma byte
wartość , , 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 krotki
Typ krotki reprezentuje uporządkowaną, stałą długość sekwencji wartości z opcjonalnymi nazwami i poszczególnymi typami. Liczba elementów w typie krotki jest określana jako jego arity. Typ krotki jest zapisywany (T1 I1, ..., Tn In)
przy użyciu n ≥ 2, gdzie identyfikatory są opcjonalnymi nazwami I1...In
elementów krotki.
Ta składnia jest skrócona dla typu skonstruowanego z typami T1...Tn
z System.ValueTuple<...>
, który jest zestawem typów ogólnych struktur, które mogą bezpośrednio wyrażać typy krotki dowolnej arity między dwoma i siedmioma inkluzywnymi.
Nie ma potrzeby istnienia System.ValueTuple<...>
deklaracji, która bezpośrednio pasuje do arity dowolnego typu krotki z odpowiednią liczbą parametrów typu. Zamiast tego krotki z arity większym niż siedem są reprezentowane z ogólnym typem System.ValueTuple<T1, ..., T7, TRest>
struktury, który oprócz elementów krotki ma Rest
pole zawierające zagnieżdżone wartości pozostałych elementów przy użyciu innego System.ValueTuple<...>
typu. 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 są odrębne. Nazwa elementu krotki formularza ItemX
, gdzie X
jest dowolną sekwencją0
nieinicjowanych cyfr dziesiętnych, które mogą reprezentować położenie elementu krotki, jest dozwolone tylko w pozycji oznaczonej przez X
.
Opcjonalne nazwy elementów nie są reprezentowane w ValueTuple<...>
typach i nie są przechowywane w reprezentacji środowiska uruchomieniowego wartości krotki. Konwersje tożsamości (§10.2.2) istnieją między krotkami z sekwencjami konwertowanymi tożsamości typów elementów.
Nie new
można zastosować operatora §12.8.17.2 ze składnią new (T1, ..., Tn)
typu krotki . Wartości krotki można tworzyć na podstawie wyrażeń krotki (§12.8.6) lub stosując new
operator bezpośrednio do typu skonstruowanego na podstawie ValueTuple<...>
elementu .
Elementy krotki są polami publicznymi o nazwach Item1
, Item2
itp., i można uzyskać do niej dostęp za pośrednictwem dostępu do elementu członkowskiego 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 zItem...
nazwą odpowiadającą jej 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 nie, niektóre lub wszystkie elementy typu krotki.Typ krotki dla
pair4
elementu jest prawidłowy, ponieważ nazwyItem1
iItem2
pasują do ich pozycji, podczas gdy typ krotki dlapair5
elementu jest niedozwolony, ponieważ nazwyItem2
iItem123
nie.Deklaracje i
pair6
pair7
pokazują, że typy krotki są zamienne z skonstruowanymi typami formularzaValueTuple<...>
i żenew
operator jest dozwolony przy użyciu tej drugiej składni.Ostatni wiersz pokazuje, że elementy krotki mogą być dostępne za pomocą
Item
nazwy odpowiadającej ich pozycji, a także odpowiedniej nazwy elementu krotki, jeśli znajdują się w typie. przykład końcowy
8.3.12 Typy wartości dopuszczalnych do wartości null
Typ wartości dopuszczanej do wartości null może reprezentować wszystkie wartości swojego typu bazowego oraz dodatkową wartość null. Typ wartości dopuszczanej do wartości null jest zapisywany T?
, gdzie T
jest typem bazowym. Ta składnia jest skrócona dla System.Nullable<T>
metody , a dwie formy mogą być używane zamiennie.
Z drugiej strony, typ wartości bez wartości null jest dowolnym typem wartości innej niż i jego skrótem System.Nullable<T>
(dla T?
dowolnego ), plus dowolnego parametru typu, który jest ograniczony jako typ wartości innej niż T
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. Bazowy typ wartości dopuszczanej do wartości null nie może być typem wartości dopuszczanej do wartości null ani typem odwołania. Na przykład int??
jest nieprawidłowym typem. Typy referencyjne dopuszczane do wartości null są objęte §8.9.
Wystąpienie typu T?
wartości dopuszczanej do wartości 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
mówi się, że nie true
ma wartości null. Wystąpienie inne niż null zawiera znaną wartość i Value
zwraca wartość.
Wystąpienie, dla którego HasValue
mówi się, że ma false
wartość null. Wystąpienie o wartości null ma niezdefiniowaną wartość. Próba odczytania Value
wystąpienia o wartości null powoduje zgłoszenie elementu System.InvalidOperationException
. Proces uzyskiwania dostępu do właściwości Value wystąpienia dopuszczającego wartość jest określany jako unwrapping.
Oprócz konstruktora domyślnego każdy typ T?
wartości dopuszczania wartości null ma konstruktor publiczny z pojedynczym parametrem typu T
. Biorąc pod uwagę wartość x
typu T
, wywołanie konstruktora formularza
new T?(x)
Tworzy wystąpienie inne niż null, T?
dla którego Value
właściwość ma wartość x
. Proces tworzenia wystąpienia wartości innej niż null typu wartości dopuszczanej do wartości null dla danej wartości jest określany jako zawijanie.
Konwersje niejawne są dostępne od null
literału do T?
(§10.2.7) i od T
do T?
(§10.2.6).
Typ T?
wartości dopuszczanej do wartości null nie implementuje 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 a reference_type, zezwalając na konwersję dowolnej wartości value_type na i z typu object
. Boxing and unboxing umożliwia ujednolicony widok systemu typów, w którym wartość dowolnego typu może być ostatecznie traktowana object
jako .
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. Rozdzielczość niejednoznaczności w tych produkcjach jest opisana 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 nieliczeniowy nie jest 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_arguments '>'
;
type_arguments
: 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. W szczególności:
- 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, jeśli i tylko wtedy, gdy co najmniej jeden argument typu lub argumenty typu zawierające typy są 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 w czasie wykonywania wszystkich instrukcji i wyrażeń zawsze występuje z typami zamkniętymi, a typy otwarte są wykonywane tylko podczas przetwarzania w czasie kompilacji.
Dwa zamknięte typy konstruowane to tożsamość konwertowana (§10.2.2), jeśli są one skonstruowane z tego samego niezwiązanego typu ogólnego, a konwersja tożsamości istnieje między poszczególnymi odpowiadającymi im argumentami typu. Odpowiednie argumenty typu mogą samodzielnie być zamknięte skonstruowane typy lub krotki, które są kabrioletem tożsamości. Zamknięte skonstruowane typy, które są tożsamościami cabrio współużytkują pojedynczy zestaw zmiennych statycznych. W przeciwnym razie każdy zamknięty typ skonstruowany ma własny zestaw zmiennych statycznych. Ponieważ typ otwierania nie istnieje w czasie wykonywania, nie ma zmiennych statycznych skojarzonych z typem otwartym.
8.4.4 Typy powiązane i niepowią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 ruchu, 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, jest to przypadek, że typA
ten jest konwertowany na typC
o jeden z następujących: - 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 typem lubstruct
typem, ale nie typemenum
wartości dopuszczanej do 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
nieabstract
jest 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ższych parametrach
D
należy określić ograniczenie dla parametruT
typu, abyT
spełnić ograniczenie nałożone przez bazęclass
B<T>
. Z koleiclass
E
nie trzeba określać ograniczenia, ponieważList<T>
implementujeIEnumerable
element dla dowolnegoT
elementu .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 utworzyć wystąpienie z wieloma różnymi argumentami typu, parametry typu mają nieco inne operacje i ograniczenia niż inne typy.
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 ograniczeń, jeśli istnieją, zastosowanych do 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, z wyjątkiem sytuacji, gdy parametr typu jest znany jako typ odwołania (§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 konwersja istnieje z wyrażenia lambda do typu D
delegata, konwersja istnieje również na typ Expression<TDelegate>
drzewa wyrażeń . Podczas gdy konwersja wyrażenia lambda na typ delegata generuje delegata, który odwołuje się do kodu wykonywalnego wyrażenia lambda, konwersja na typ drzewa wyrażeń tworzy reprezentację drzewa wyrażeń wyrażenia. 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>
element , 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 exp drzewa wyrażeń odwołuje się do struktury danych, która opisuje wyrażeniex => x + 1
.przykład końcowy
Expression<TDelegate>
Udostępnia metodę Compile
wystąpienia, 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 del
definicje 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 i1
obie i2
wartości będą miały wartość 2
.
Powierzchnia interfejsu API dostarczana przez Expression<TDelegate>
program jest definiowana przez implementację poza wymaganiem dla metody opisanej Compile
powyżej.
Uwaga: Chociaż szczegóły interfejsu API podane dla drzew wyrażeń są zdefiniowane przez implementację, oczekuje się, że implementacja:
- Włączanie inspekcji i reagowania na strukturę drzewa wyrażeń utworzonego w wyniku konwersji z 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
object
, jeśli obaj są kandydatami. -
dynamic
nie można użyć jako- typ w object_creation_expression (§12.8.17.2)
- a class_base (§15.2.4)
- predefined_type w member_access (§12.8.7.1)
- operand
typeof
operatora - 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ść, następujące blokady:
- Istnieje niejawna konwersja tożsamości
- między
object
idynamic
- między skonstruowanymi typami, które są takie same podczas zastępowania za pomocą
dynamic
object
- między typami krotki, które są takie same podczas zamiany
dynamic
naobject
- między
- Niejawne i jawne konwersje do i z
object
programu 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 elementów:
-
sbyte
,byte
, ,short
,ushort
int
uint
long
ulong
char
float
double
decimal
lub .bool
- Wszystkie 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 wartość null
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. Konwersja tożsamości istnieje wśród typu odwołania dopuszczanego do wartości null i odpowiadającego mu typu odwołania innego niż null (§10.2.2).
Istnieją dwie formy wartości null dla typów odwołań:
-
nullable:
null
odwołania o wartości null. Domyślny stan null to może mieć wartość null. -
niepuste: Nie należy przypisywać wartości odwołania nienależące do
null
wartości null. Domyślny stan null nie ma wartości null.
Uwaga: 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 typem odwołania dopuszczanym do wartości null a odpowiadającym mu typem referencyjnym bez wartości null umożliwia kompilatorowi generowanie diagnostyki. 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
Typ odwołania bez wartości null jest typem odwołania formularza 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 jest używane wyrażenie, które może mieć wartość null, w przypadku gdy wymagana jest wartość not-null.
8.9.3 Typy referencyjne dopuszczane do wartości 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 intencję, że zmienne tego typu są dopuszczane do wartości 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 dopuszczalny do wartości null 8.9.4
8.9.4.1 Ogólne
Każdy wiersz kodu źródłowego ma kontekst dopuszczalny do wartości null. Adnotacje i ostrzeżenia flagi dla kontrolek kontekstu dopuszczające wartość null adnotacje dopuszczające wartość null (§8.9.4.3) i ostrzeżenia dopuszczające wartość 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 dopuszczane do wartości null zastępują ustawienia wprowadzone za pośrednictwem mechanizmu zewnętrznego.
Domyślny stan kontekstu dopuszczanego do wartości null jest zdefiniowany przez implementację.
W całej tej specyfikacji przyjmuje się, że cały kod języka C#, który nie zawiera dyrektyw dopuszczających wartość null lub o którym nie wprowadzono żadnej instrukcji dotyczącej bieżącego stanu kontekstu dopuszczalnego wartości null, przyjmuje się, że został skompilowany przy użyciu kontekstu dopuszczalnego wartości null, 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, odpowiada poprzedniej standardowej zachowaniu typów odwołań. notatka końcowa
8.9.4.2 Wyłączenie dopuszczania wartości null
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:
- Żadne ostrzeżenie nie jest generowane, gdy zmienna typu odwołania bez adnotacji jest inicjowana z wartością lub przypisaną wartością
null
. - Nie jest generowane ostrzeżenie, gdy zmienna typu odwołania, która prawdopodobnie ma wartość null.
- W przypadku dowolnego typu
T
odwołania adnotacja?
w elemencieT?
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 odwołania.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
!
forgiving o wartości 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 dopuszczane do wartości null
Gdy flaga ostrzeżenia jest wyłączona i flaga adnotacji jest włączona, kontekst dopuszczający wartość null jest adnotacjami.
Gdy kontekst dopuszczany do wartości null jest adnotacjami:
- W przypadku dowolnego typu
T
odwołania adnotacja?
w elemencieT?
wskazuje, żeT?
typ dopuszczający wartość null, natomiast nieznakowaneT
jest niepuste. - Nie są generowane żadne ostrzeżenia diagnostyczne związane z wartością null.
- Operator
!
forgiving o wartości null (§12.8.9) może zmienić przeanalizowany stan null operandu i jakie są generowane ostrzeżenia diagnostyczne czasu 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
8.9.4.4 Ostrzeżenia dopuszczające wartość null
Gdy flaga ostrzeżenia jest włączona, a flaga adnotacji jest wyłączona, kontekst dopuszczający wartość null jest ostrzegawczy.
Gdy kontekst dopuszczający wartość null jest wyświetlany, 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 zanotowania typu odwołania dopuszczanego do wartości null. - Operator
!
forgiving o wartości null (§12.8.9) służy do ustawiania stanu null operandu na wartość nie null.
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łączanie dopuszczania wartości null
Po włączeniu flagi ostrzeżenia i flagi adnotacji kontekst dopuszczający wartość null.
Po włączeniu kontekstu dopuszczanego do wartości null:
- W przypadku dowolnego typu
T
odwołania adnotacja?
w elemencieT?
sprawia, żeT?
typ dopuszczany do wartości null, natomiast nieznakowaneT
jest niepuste. - Kompilator może używać analizy przepływu statycznego do określania stanu null dowolnej zmiennej referencyjnej. Gdy są włączone ostrzeżenia dopuszczające wartość null, stan null zmiennej referencyjnej (§8.9.5) nie ma wartości null, może mieć wartość null, a może wartość domyślną i
- Operator
!
forgiving o wartości null (§12.8.9) ustawia stan null operandu na wartość 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
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ęść tego podklasy jest warunkowo normatywna.
Kompilator, który generuje ostrzeżenia diagnostyczne, jest zgodny z tymi regułami.
Każde wyrażenie ma jeden z trzech stanównull:
- 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 ma 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 typu odwołania dopuszczanego do wartości null to:
- Może ma wartość null, gdy jego deklaracja znajduje się w tekście, w którym jest włączona flaga adnotacji.
- Nie ma wartości null, gdy jego deklaracja znajduje się w tekście, w którym flaga adnotacji jest wyłączona.
- Domyślny stan null typu odwołania niezwiązanego z wartością null nie ma wartości null.
Uwaga:być może domyślny stan jest używany z nieobsługiwanymi parametrami typu, gdy typ jest typem niepustym, takim jak
string
i wyrażeniedefault(T)
jest wartością null. Ponieważ wartość null nie znajduje się w domenie dla typu niezwiązanego z wartością null, stan może być domyślny. notatka końcowa
Diagnostykę można utworzyć, gdy zmienna (§9.2.1) typu odwołania niezwiązanego z wartością null jest inicjowana lub przypisywana do wyrażenia, które może mieć wartość null, gdy ta zmienna jest zadeklarowana w tekście, w którym jest włączona flaga 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, jeśli kod został poprzedzony,if (p == null) throw NullReferenceException();
z wyjątkiem tego, że kod napisany może wygenerować ostrzeżenie, którego celem jest ostrzeżenie, ż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 s
null może ulec zmianie na wartość null po zamknięciu bloku zaznaczonego wartości 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 tworzenia kopii zapasowej dla
DisappearingProperty
elementu jest ustawione na wartość null, gdy jest odczytywane. 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
Koniec warunkowo normatywnego tekstu
ECMA C# draft specification