Udostępnij za pośrednictwem


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 objectklasy . 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 classSystem.ValueTypeklasy , 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, shortushortintuintlongulongwartość domyślna to .0
    • W przypadku charparametru wartość domyślna to '\x0000'.
    • W przypadku floatparametru wartość domyślna to 0.0f.
    • W przypadku doubleparametru wartość domyślna to 0.0d.
    • W przypadku decimalparametru wartość domyślna to 0m (czyli wartość zero ze skalą 0).
    • W przypadku boolparametru wartość domyślna to false.
    • E wartość domyślna to 0, przekonwertowana na typ E.
  • 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 nullwartość .
  • 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 odczytania Value właściwości takiej wartości powoduje zgłoszenie wyjątku typu System.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 ij i k 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 i System.Int32 członkowie dziedziczone z System.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 typu int i 'a' jest literałem typu char. 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, shortushortintuintlongulongi .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 do 127, włącznie.
  • Typ byte reprezentuje niepodpisane 8-bitowe liczby całkowite z wartościami od 0 do 255, włącznie.
  • Typ short reprezentuje podpisane 16-bitowe liczby całkowite z wartościami od -32768 do 32767, włącznie.
  • Typ ushort reprezentuje niepodpisane 16-bitowe liczby całkowite z wartościami od 0 do 65535, włącznie.
  • Typ int reprezentuje podpisane 32-bitowe liczby całkowite z wartościami od -2147483648 do 2147483647, włącznie.
  • Typ uint reprezentuje niepodpisane 32-bitowe liczby całkowite z wartościami od 0 do 4294967295, włącznie.
  • Typ long reprezentuje podpisane 64-bitowe liczby całkowite z wartościami od -9223372036854775808 do 9223372036854775807, włącznie.
  • Typ ulong reprezentuje niepodpisane 64-bitowe liczby całkowite z wartościami od 0 do 18446744073709551615, włącznie.
  • Typ char reprezentuje niepodpisane 16-bitowe liczby całkowite z wartościami od 0 do 65535, włącznie. Zestaw możliwych wartości dla char typu odpowiada zestawowi znaków Unicode.

    Uwaga: Chociaż char ma taką samą reprezentację jak ushort, 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 że byte typy i ushort mają zakresy wartości, które są w pełni reprezentowane przy użyciu char typu, niejawnych konwersji z bajtu, bajtu lub ushort nie char 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 dla double, 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 formularza x * y / z, gdzie mnożenie generuje wynik, który znajduje się poza double zakresem, ale następny podział powoduje tymczasowy wynik z powrotem do double 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 ≤ eEmax, 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 decimalwartości bezwzględnej mniejszej niż 1.0m, wartość jest dokładna do co najmniej 28 miejsca dziesiętnego. Dla decimals 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ść falselogiczną , a wartość całkowita lub zmiennoprzecinkowa niezerowa lub wskaźnik zmiennoprzecinkowa można przekonwertować na wartość truelogiczną . 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 bytewartość , , sbyte, shortushort, int, , uintlong 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...Inelementó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, Item2itp., 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 z Item... 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, pair2i pair3 są prawidłowe, z nazwami nie, niektóre lub wszystkie elementy typu krotki.

Typ krotki dla pair4 elementu jest prawidłowy, ponieważ nazwy Item1 i Item2 pasują do ich pozycji, podczas gdy typ krotki dla pair5 elementu jest niedozwolony, ponieważ nazwy Item2 i Item123 nie.

Deklaracje i pair6pair7 pokazują, że typy krotki są zamienne z skonstruowanymi typami formularza ValueTuple<...>i że new 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 objectjako .

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, niech C 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 typ A ten jest konwertowany na typ C o jeden z następujących:
    • Konwersja tożsamości (§10.2.2)
    • Niejawna konwersja odwołania (§10.2.8)
    • Konwersja boksu (§10.2.9), pod warunkiem, że typ ten jest typem A wartości niepustej.
    • Niejawne odwołanie, boxing lub konwersja parametru typu z parametru A typu na C.
  • Jeśli ograniczenie jest ograniczeniem typu referencyjnego (class), typ A 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 i System.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), typ A spełnia jeden z następujących warunków:
    • Ajest typem lub struct typem, ale nie typem enum wartości dopuszczanej do wartości null.

    Uwaga: System.ValueType i System.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, typ A 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 elementem class , który nie jest abstrakcyjny i zawiera jawnie zadeklarowany publiczny konstruktor bez parametrów.
    • A nie abstract 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 parametru T typu, aby T spełnić ograniczenie nałożone przez bazę classB<T>. Z kolei classE nie trzeba określać ograniczenia, ponieważ List<T> implementuje IEnumerable element dla dowolnego Telementu .

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 Ddelegata, 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 na Expression<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ącej x + 1, a exp drzewa wyrażeń odwołuje się do struktury danych, która opisuje wyrażenie x => 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ć dynamicobject , jeśli obaj są kandydatami.
  • dynamic nie można użyć jako

Ze względu na tę równoważność, następujące blokady:

  • Istnieje niejawna konwersja tożsamości
    • między object i dynamic
    • między skonstruowanymi typami, które są takie same podczas zastępowania za pomocą dynamicobject
    • między typami krotki, które są takie same podczas zamiany dynamic na object
  • Niejawne i jawne konwersje do i z object programu mają również zastosowanie do i z dynamic.
  • 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 typu object 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, ushortintuintlongulongcharfloatdoubledecimallub .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 i R? 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 Todwołania adnotacja ? w elemencie T? generuje komunikat, a typ T? jest taki sam jak T.
  • W przypadku ograniczenia where T : C?parametru dowolnego typu adnotacja ? w pliku C? generuje komunikat, a typ C? jest taki sam jak C.
  • W przypadku ograniczenia where T : U?parametru dowolnego typu adnotacja ? w pliku U? generuje komunikat, a typ U? jest taki sam jak U.
  • 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 Todwołania adnotacja ? w elemencie T? wskazuje, że T? typ dopuszczający wartość null, natomiast nieznakowane T 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 Todwołania adnotacja ? w elemencie T? sprawia, że T? typ dopuszczany do wartości null, natomiast nieznakowane T 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żenie default(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;stan p 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ści P, 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