Dela via


8 typer

8.1 Allmänt

Typerna av C#-språket är indelade i två huvudkategorier: referenstyper och värdetyper. Både värdetyper och referenstyper kan vara generiska typer, som tar en eller flera typparametrar. Typparametrar kan ange både värdetyper och referenstyper.

type
    : reference_type
    | value_type
    | type_parameter
    | pointer_type     // unsafe code support
    ;

pointer_type (§23.3) är endast tillgänglig i osäker kod (§23).

Värdetyper skiljer sig från referenstyper i och med att variabler för värdetyperna direkt innehåller sina data, medan variabler av referenstyperna lagrar referenser till sina data, det senare kallas objekt. Med referenstyper är det möjligt för två variabler att referera till samma objekt, och därmed kan åtgärder på en variabel påverka objektet som refereras av den andra variabeln. Med värdetyper har variablerna var och en sin egen kopia av data och det är inte möjligt för åtgärder på den ena att påverka den andra.

Obs! När en variabel är en referens- eller utdataparameter har den inte sin egen lagring, men refererar till lagringen av en annan variabel. I det här fallet är ref- eller out-variabeln i själva verket ett alias för en annan variabel och inte en distinkt variabel. slutkommentar

C#:s typsystem är enhetligt så att ett värde av vilken typ som helst kan behandlas som ett objekt. Varje typ i C# härleds direkt eller indirekt från object klasstypen och object är den ultimata basklassen av alla typer. Värden för referenstyper behandlas som objekt genom att bara visa värdena som typ object. Värden för värdetyper behandlas som objekt genom att utföra boxnings- och avboxningsåtgärder (§8.3.13).

För enkelhetens skull skrivs vissa bibliotekstypnamn i den här specifikationen utan att använda deras fullständiga namnkvalifikation. Mer information finns i §C.5.

8.2 Referenstyper

8.2.1 Allmänt

En referenstyp är en klasstyp, en gränssnittstyp, en matristyp, en ombudstyp eller dynamic typen. För varje icke-nullbar referenstyp finns det en motsvarande nullbar referenstyp som anges genom att lägga ? till namnet på typen.

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 är endast tillgängligt i osäker kod (§23.3). nullable_reference_type diskuteras vidare i §8.9.

Ett referenstypvärde är en referens till en instans av typen, den senare kallas för ett objekt. Specialvärdet null är kompatibelt med alla referenstyper och anger avsaknaden av en instans.

8.2.2 Klasstyper

En klasstyp definierar en datastruktur som innehåller datamedlemmar (konstanter och fält), funktionsmedlemmar (metoder, egenskaper, händelser, indexerare, operatorer, instanskonstruktorer, finalizers och statiska konstruktorer) och kapslade typer. Klasstyper stöder arv, en mekanism där härledda klasser kan utöka och specialisera basklasser. Instanser av klasstyper skapas med hjälp av object_creation_expressions (§12.8.17.2).

Klasstyper beskrivs i §15.

Vissa fördefinierade klasstyper har särskild betydelse i C#-språket, enligt beskrivningen i tabellen nedan.

Klasstyp Beskrivning
System.Object Den ultimata basklassen för alla andra typer. Se §8.2.3.
System.String Strängtypen för C#-språket. Se §8.2.5.
System.ValueType Basklassen för alla värdetyper. Se §8.3.2.
System.Enum Basklassen för alla enum typer. Se §19.5.
System.Array Basklassen för alla matristyper. Se §17.2.2.
System.Delegate Basklassen för alla delegate typer. Se §20.1.
System.Exception Basklassen för alla undantagstyper. Se §21.3.

8.2.3 Objekttypen

Klasstypen object är den ultimata basklassen för alla andra typer. Varje typ i C# direkt eller indirekt härleds från object klasstypen.

Nyckelordet object är helt enkelt ett alias för den fördefinierade klassen System.Object.

8.2.4 Den dynamiska typen

Typen dynamic , till exempel object, kan referera till valfritt objekt. När åtgärder tillämpas på uttryck av typen dynamicskjuts deras upplösning upp tills programmet körs. Om åtgärden inte kan tillämpas på det refererade objektet på ett legitimt sätt anges därför inget fel under kompileringen. I stället utlöses ett undantag när åtgärdens lösning misslyckas vid körning.

Typen dynamic beskrivs ytterligare i §8.7 och dynamisk bindning i §12.3.1.

8.2.5 Strängtypen

Typen string är en förseglad klasstyp som ärver direkt från object. Instanser av string klassen representerar Unicode-teckensträngar.

Värden av typen string kan skrivas som strängliteraler (§6.4.5.6).

Nyckelordet string är helt enkelt ett alias för den fördefinierade klassen System.String.

8.2.6 Gränssnittstyper

Ett gränssnitt definierar ett kontrakt. En klass eller struct som implementerar ett gränssnitt ska följa avtalet. Ett gränssnitt kan ärva från flera basgränssnitt, och en klass eller struct kan implementera flera gränssnitt.

Gränssnittstyper beskrivs i §18.

8.2.7 Matristyper

En matris är en datastruktur som innehåller noll eller fler variabler som nås via beräknade index. Variablerna som finns i en matris, även kallade element i matrisen, är av samma typ och den här typen kallas elementtypen för matrisen.

Matristyper beskrivs i §17.

8.2.8 Delegera typer

Ett ombud är en datastruktur som refererar till en eller flera metoder. För instansmetoder refererar den också till motsvarande objektinstanser.

Obs! Den närmaste motsvarigheten till ett ombud i C eller C++ är en funktionspekare, men medan en funktionspekare bara kan referera till statiska funktioner kan ett ombud referera till både statiska metoder och instansmetoder. I det senare fallet lagrar ombudet inte bara en referens till metodens startpunkt, utan även en referens till den objektinstans där metoden ska anropas. slutkommentar

Ombudstyper beskrivs i §20.

8.3 Värdetyper

8.3.1 Allmänt

En värdetyp är antingen en structtyp eller en uppräkningstyp. C# innehåller en uppsättning fördefinierade structtyper som kallas för enkla typer. De enkla typerna identifieras via nyckelord.

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
    ;

Till skillnad från en variabel av en referenstyp kan en variabel av en värdetyp endast innehålla värdet null om värdetypen är en nullbar värdetyp (§8.3.12). För varje värdetyp som inte kan nulleras finns det en motsvarande nullbar värdetyp som anger samma uppsättning värden plus värdet null.

Tilldelning till en variabel av en värdetyp skapar en kopia av värdet som tilldelas. Detta skiljer sig från tilldelning till en variabel av en referenstyp, som kopierar referensen men inte det objekt som identifieras av referensen.

8.3.2 Typen System.ValueType

Alla värdetyper ärver implicit från classSystem.ValueType, som i sin tur ärver från klassen object. Det är inte möjligt för någon typ att härleda från en värdetyp, och värdetyper är därför implicit förseglade (§15.2.2.3).

Observera att det System.ValueType inte i sig är en value_type. I stället är det en class_type som alla value_types automatiskt härleds från.

8.3.3 Standardkonstruktorer

Alla värdetyper deklarerar implicit en offentlig parameterlös instanskonstruktor som kallas standardkonstruktor. Standardkonstruktorn returnerar en nollinitierad instans som kallas standardvärdet för värdetypen:

  • För alla simple_typeär standardvärdet det värde som genereras av ett bitmönster för alla nollor:
    • För sbyte, byte, short, ushort, int, uint, longoch ulongär 0standardvärdet .
    • För charär '\x0000'standardvärdet .
    • För floatär 0.0fstandardvärdet .
    • För doubleär 0.0dstandardvärdet .
    • För decimalär 0m standardvärdet (det vill: värdet noll med skalning 0).
    • För boolär falsestandardvärdet .
    • För en enum_typeEär 0standardvärdet , konverterat till typen E.
  • För en struct_type är standardvärdet det värde som skapas genom att ange alla värdetypsfält till standardvärdet och alla referenstypfält till null.
  • För en nullable_value_type är standardvärdet en instans där egenskapen HasValue är false. Standardvärdet kallas även null-värdet för den nullbara värdetypen. Försök att läsa Value egenskapen för ett sådant värde gör att ett undantag av typen System.InvalidOperationException genereras (§8.3.12).

Precis som andra instanskonstruktor anropas standardkonstruktorn för en värdetyp med hjälp av operatorn new .

Obs! Av effektivitetsskäl är det här kravet inte avsett att implementeringen ska generera ett konstruktoranrop. För värdetyper ger standardvärdeuttrycket (§12.8.21) samma resultat som med standardkonstruktorn. slutkommentar

Exempel: I koden nedan initieras variabler och ijk alla till noll.

class A
{
    void F()
    {
        int i = 0;
        int j = new int();
        int k = default(int);
    }
}

slutexempel

Eftersom varje värdetyp implicit har en offentlig parameterlös instanskonstruktor är det inte möjligt att en struct-typ innehåller en explicit deklaration av en parameterlös konstruktor. En structtyp tillåts dock att deklarera parameteriserade instanskonstruktorer (§16.4.9).

8.3.4 Struct-typer

En structtyp är en värdetyp som kan deklarera konstanter, fält, metoder, egenskaper, händelser, indexerare, operatorer, instanskonstruktorer, statiska konstruktorer och kapslade typer. Deklarationen av structtyper beskrivs i §16.

8.3.5 Enkla typer

C# innehåller en uppsättning fördefinierade struct typer som kallas enkla typer. De enkla typerna identifieras via nyckelord, men dessa nyckelord är helt enkelt alias för fördefinierade struct typer i System namnområdet, enligt beskrivningen i tabellen nedan.

Nyckelord Aliastyp
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

Eftersom en enkel typ alias en struct typ, varje enkel typ har medlemmar.

Exempel: int har medlemmarna deklarerats i System.Int32 och de medlemmar som ärvts från System.Object, och följande instruktioner är tillåtna:

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

slutexempel

Obs! De enkla typerna skiljer sig från andra struct-typer eftersom de tillåter vissa ytterligare åtgärder:

  • De flesta enkla typer tillåter att värden skapas genom att skriva literaler (§6.4.5), även om C# inte tillhandahåller literaler av structtyper i allmänhet. Exempel: 123 är en literal av typen int och 'a' är en literal av typen char. slutexempel
  • När operanderna i ett uttryck är alla enkla typkonstanter är det möjligt för en kompilator att utvärdera uttrycket vid kompileringstid. Ett sådant uttryck kallas för en constant_expression (§12.23). Uttryck som involverar operatorer som definierats av andra structtyper anses inte vara konstanta uttryck
  • Genom const deklarationer är det möjligt att deklarera konstanter av de enkla typerna (§15.4). Det går inte att ha konstanter av andra structtyper, men en liknande effekt tillhandahålls av statiska skrivskyddade fält.
  • Konverteringar med enkla typer kan delta i utvärderingen av konverteringsoperatorer som definierats av andra structtyper, men en användardefinierad konverteringsoperator kan aldrig delta i utvärderingen av en annan användardefinierad konverteringsoperator (§10.5.3).

slutkommentar.

8.3.6 Integraltyper

C# stöder nio integraltyper: sbyte, byte, short, ushort, int, uint, long, , ulongoch char. Integraltyperna har följande storlekar och intervall med värden:

  • Typen sbyte representerar signerade 8-bitars heltal med värden från -128 till 127, inklusive.
  • Typen byte representerar osignerade 8-bitars heltal med värden från 0 till 255, inklusive.
  • Typen short representerar signerade 16-bitars heltal med värden från -32768 till 32767, inklusive.
  • Typen ushort representerar osignerade 16-bitars heltal med värden från 0 till 65535, inklusive.
  • Typen int representerar signerade 32-bitars heltal med värden från -2147483648 till 2147483647, inklusive.
  • Typen uint representerar osignerade 32-bitars heltal med värden från 0 till 4294967295, inklusive.
  • Typen long representerar signerade 64-bitars heltal med värden från -9223372036854775808 till 9223372036854775807, inklusive.
  • Typen ulong representerar osignerade 64-bitars heltal med värden från 0 till 18446744073709551615, inklusive.
  • Typen char representerar osignerade 16-bitars heltal med värden från 0 till 65535, inklusive. Uppsättningen med möjliga värden för char typen motsvarar Unicode-teckenuppsättningen.

    Obs! Även om char har samma representation som ushorttillåts inte alla åtgärder som tillåts för den ena typen på den andra. slutkommentar

Alla signerade integraltyper representeras med hjälp av tvås komplementformat.

De integral_type icke-ära och binära operatorerna fungerar alltid med signerad 32-bitars precision, osignerad 32-bitars precision, signerad 64-bitars precision eller osignerad 64-bitars precision, enligt beskrivningen i §12.4.7.

Typen char klassificeras som en integrerad typ, men den skiljer sig från de andra integraltyperna på två sätt:

  • Det finns inga fördefinierade implicita konverteringar från andra typer till char typen. Även om typerna byte och ushort i synnerhet har värden som kan representeras fullt ut med hjälp char av typen, finns implicita konverteringar från sbyte, byte eller ushort för att char inte finnas.
  • Konstanter av typen char ska skrivas som character_literaleller som integer_literals i kombination med en gjuten till typ tecken.

Exempel: (char)10 är samma som '\x000A'. slutexempel

Operatorerna checked och unchecked -instruktionerna används för att kontrollera spillkontroll för aritmetiska åtgärder och konverteringar av integraltyp (§12.8.20). I ett checked sammanhang genererar ett spill ett kompileringstidsfel eller orsakar att ett System.OverflowException genereras. I ett unchecked sammanhang ignoreras spill och alla högordningsbitar som inte får plats i måltypen ignoreras.

8.3.7 Flyttalstyper

C# stöder två flyttalstyper: float och double. Typerna float och double representeras med hjälp av IEC 60559-format med 32-bitars enkel precision och 64-bitars dubbel precision, vilket ger följande uppsättningar värden:

  • Positiv noll och negativ nolla. I de flesta situationer fungerar positiv noll och negativ noll identiskt som det enkla värdet noll, men vissa åtgärder skiljer mellan de två (§12.10.3).
  • Positiv oändlighet och negativ oändlighet. Infinities produceras av sådana åtgärder som att dividera ett tal som inte är noll med noll.

    Exempel: 1.0 / 0.0 ger positiv oändlighet och –1.0 / 0.0 ger negativ oändlighet. slutexempel

  • Värdet Not-a-Number , ofta förkortat NaN. NaN skapas av ogiltiga flyttalsåtgärder, till exempel att dividera noll med noll.
  • Den ändliga uppsättningen med värden som inte är noll för formulärets × m × 2e, där s är 1 eller −1, och m och e bestäms av den särskilda flyttalstypen: För , 0float< 2²⁴ och −149 ≤ e ≤ 104, och för <, 0< 2⁵³ och −1075 ≤ e ≤ 970.< Avnormaliserade flyttalsnummer anses vara giltiga värden som inte är noll. C# varken kräver eller förbjuder att en överensstämmande implementering stöder avnormaliserade flyttalsnummer.

Typen float kan representera värden från cirka 1,5 × 10⁻⁴⁵ till 3,4 × 10³⁸ med en precision på 7 siffror.

Typen double kan representera värden från cirka 5,0 × 10⁻³²⁴ till 1,7 × 10³⁰⁸ med en precision på 15–16 siffror.

Om någon av operanderna för en binär operator är en flyttalstyp tillämpas standard numeriska kampanjer, enligt beskrivningen i §12.4.7, och åtgärden utförs med float eller double precision.

Flyttalsoperatorerna, inklusive tilldelningsoperatorerna, skapar aldrig undantag. I undantagsfall skapar flyttalsåtgärder i stället noll, oändlighet eller NaN enligt beskrivningen nedan:

  • Resultatet av en flyttalsåtgärd avrundas till närmaste representerande värde i målformatet.
  • Om storleken på resultatet av en flyttalåtgärd är för liten för målformatet blir resultatet av åtgärden positiv noll eller negativ nolla.
  • Om resultatet av en flyttalsåtgärd är för stort för målformatet blir resultatet av åtgärden positiv oändlighet eller negativ oändlighet.
  • Om en flyttalsåtgärd är ogiltig blir resultatet av åtgärden NaN.
  • Om en eller båda operanderna i en flyttalsåtgärd är NaN blir resultatet av åtgärden NaN.

Flyttalsåtgärder kan utföras med högre precision än åtgärdens resultattyp. För att framtvinga ett värde av en flyttalstyp till den exakta precisionen av dess typ kan en explicit gjutning (§12.9.7) användas.

Exempel: Vissa maskinvaruarkitekturer stöder en flyttal av typen "extended" eller "long double" med större intervall och precision än double typen, och utför implicit alla flyttalsåtgärder med den här typen av högre precision. Endast till orimliga prestandakostnader kan sådana maskinvaruarkitekturer göras för att utföra flyttalsåtgärder med mindre precision, och i stället för att kräva en implementering för att förlora både prestanda och precision, tillåter C# att en högre precisionstyp används för alla flyttalsåtgärder. Förutom att leverera mer exakta resultat har detta sällan några mätbara effekter. Men i uttryck för formuläret x * y / z, där multiplikationen ger ett resultat som ligger utanför double intervallet, men den efterföljande divisionen tar det tillfälliga resultatet tillbaka till double intervallet, kan det faktum att uttrycket utvärderas i ett högre intervallformat orsaka att ett ändligt resultat skapas i stället för en oändlighet. slutexempel

8.3.8 Decimaltypen

Typen decimal är en 128-bitars datatyp som lämpar sig för finansiella och monetära beräkningar. Typen decimal kan representera värden inklusive värden i intervallet minst -7,9 × 10⁻²⁸ till 7,9 × 10²⁸, med minst 28-siffrig precision.

Den ändliga uppsättningen värden av typen decimal är av formatet (–1)v × c × 10⁻e, Om tecknet v är 0 eller 1 ges koefficienten c av 0 ≤ c<Cmax, och skala e är sådan att EmineEmax, där Cmax är minst 1 × 10²⁸, Emin ≤ 0, och Emax ≥ 28. Typen decimal stöder inte nödvändigtvis signerade nollor, infiniteter eller NaN:er.

A decimal representeras som ett heltal som skalas av en effekt på tio. För decimals med ett absolut värde som är mindre än 1.0mär värdet exakt till minst den 28:e decimalen. För decimals med ett absolut värde som är större än eller lika 1.0mmed är värdet exakt minst 28 siffror. I motsats till datatyperna float och double kan decimaltal som 0.1 kan representeras exakt i decimalrepresentationen. I representationerna float och double har sådana tal ofta icke-avslutande binära expansioner, vilket gör dessa representationer mer benägna att avrunda fel.

Om någon av operanderna av en binär operator är av decimal typen tillämpas standard numeriska kampanjer, enligt beskrivningen i §12.4.7, och åtgärden utförs med double precision.

Resultatet av en åtgärd på värden av typen decimal är det som skulle uppstå vid beräkning av ett exakt resultat (bevara skalning, enligt definitionen för varje operator) och sedan avrundning för att passa representationen. Resultaten avrundas till närmaste representerande värde och, när ett resultat är lika nära två representerande värden, till det värde som har ett jämnt tal i den minst signifikanta sifferpositionen (detta kallas "bankirens avrundning"). Det innebär att resultaten är exakta till minst den 28:e decimalplatsen. Observera att avrundning kan ge ett nollvärde från ett värde som inte är noll.

Om en decimal aritmetikåtgärd ger ett resultat vars storlek är för stor för decimal formatet genereras en System.OverflowException .

Typen decimal har större precision men kan ha ett mindre intervall än flyttalstyperna. Därför kan konverteringar från flyttalstyperna till decimal skapa undantag för spill, och konverteringar från decimal till flyttalstyper kan orsaka förlust av precisions- eller spillfel. Därför finns det inga implicita konverteringar mellan flyttaltyperna och decimal, och utan explicita avbildningar uppstår ett kompileringsfel när flyttal och decimal operander blandas direkt i samma uttryck.

8.3.9 Booltypen

Typen bool representerar booleska logiska kvantiteter. Möjliga värden av typen bool är true och false. Representationen av false beskrivs i §8.3.3. Även om representationen av true är ospecificerad, ska den skilja sig från den i false.

Det finns inga standardkonverteringar mellan bool och andra värdetyper. I synnerhet bool är typen distinkt och separat från de integrerade typerna, ett bool värde kan inte användas i stället för ett integralvärde och vice versa.

Obs! På språken C och C++ kan ett heltals- eller flyttalsvärde eller en null-pekare konverteras till det booleska värdet falseoch ett icke-nollintegrerat eller flyttalsvärde eller en icke-null-pekare kan konverteras till det booleska värdet true. I C# utförs sådana konverteringar genom att uttryckligen jämföra ett heltals- eller flyttalsvärde med noll, eller genom att uttryckligen jämföra en objektreferens med null. slutkommentar

8.3.10 Uppräkningstyper

En uppräkningstyp är en distinkt typ med namngivna konstanter. Varje uppräkningstyp har en underliggande typ, som ska vara , , , , , , bytesbyte eller short. ushortintuintlongulong Uppsättningen med värden för uppräkningstypen är samma som uppsättningen med värden för den underliggande typen. Värden för uppräkningstypen är inte begränsade till värdena för de namngivna konstanterna. Uppräkningstyper definieras genom uppräkningsdeklarationer (§19.2).

8.3.11 Tuppeln typer

En tuppelns typ representerar en ordnad sekvens med fast längd med värden med valfria namn och enskilda typer. Antalet element i en tuppelns typ kallas dess aritet. En tupplartyp skrivs (T1 I1, ..., Tn In) med n ≥ 2, där identifierarna I1...In är valfria tuppelns elementnamn.

Den här syntaxen är kortfattad för en typ som är konstruerad med typerna T1...Tn från System.ValueTuple<...>, som ska vara en uppsättning generiska structtyper som direkt kan uttrycka tuppelns typer av ariitet mellan två och sju inkluderande. Det behöver inte finnas någon System.ValueTuple<...> deklaration som direkt matchar ariteten för någon tuppelns typ med ett motsvarande antal typparametrar. I stället representeras tupplar med en aritet som är större än sju med en allmän structtyp System.ValueTuple<T1, ..., T7, TRest> som förutom tupplar har ett Rest fält som innehåller ett kapslat värde för de återstående elementen, med hjälp av en annan System.ValueTuple<...> typ. Sådan kapsling kan observeras på olika sätt, t.ex. via förekomsten av ett Rest fält. Om endast ett enda ytterligare fält krävs används den generiska structtypen System.ValueTuple<T1> . Den här typen betraktas inte som en tuppeltyp i sig. Om fler än sju ytterligare fält krävs System.ValueTuple<T1, ..., T7, TRest> används rekursivt.

Elementnamn inom en tuppelns typ ska vara distinkta. Ett tuppelns elementnamn i formuläret ItemX, där X är en sekvens med icke-initierade0 decimalsiffror som kan representera positionen för ett tupplarelement, tillåts endast vid den position som anges av X.

De valfria elementnamnen representeras inte i typerna ValueTuple<...> och lagras inte i körningsrepresentationen av ett tupppelvärde. Identitetskonverteringar (§10.2.2) finns mellan tupplar med identitetskonverterbara sekvenser av elementtyper.

Operatorn new§12.8.17.2 kan inte tillämpas med tuppelns typsyntax new (T1, ..., Tn). Tupplar kan skapas från tupplar (§12.8.6) eller genom att tillämpa operatorn new direkt på en typ som är konstruerad från ValueTuple<...>.

Tuppelns element är offentliga fält med namnen Item1, Item2osv., och kan nås via en medlemsåtkomst på ett tuppelns värde (§12.8.7. Om tuppelns typ dessutom har ett namn på ett visst element kan det namnet användas för att komma åt det aktuella elementet.

Obs! Även om stora tupplar representeras med kapslade System.ValueTuple<...> värden kan varje tupplar fortfarande nås direkt med det Item... namn som motsvarar dess position. slutkommentar

Exempel: Med följande exempel:

(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}");

Tuppeln typer för pair1, pair2, och pair3 är alla giltiga, med namn för nej, vissa eller alla tuppeln typ element.

Tuppelns typ för pair4 är giltig eftersom namnen Item1 och Item2 matchar deras positioner, medan tuppelns typ för pair5 inte tillåts, eftersom namnen Item2 och Item123 inte gör det.

Deklarationerna för pair6 och pair7 visar att tupplar är utbytbara med konstruerade typer av formuläret ValueTuple<...>och att operatorn new tillåts med den senare syntaxen.

Den sista raden visar att tuppelns element kan nås med det Item namn som motsvarar deras position, samt av motsvarande tuppelns elementnamn, om det finns i typen . slutexempel

8.3.12 Nullable-värdetyper

En nullbar värdetyp kan representera alla värden av den underliggande typen plus ytterligare ett null-värde. En nullbar värdetyp skrivs T?, där T är den underliggande typen. Den här syntaxen är kortfattad för System.Nullable<T>, och de två formulären kan användas omväxlande.

Omvänt är en icke-nullbar värdetyp en annan värdetyp än System.Nullable<T> och dess kort stavelse T? (för alla T), plus alla typparametrar som är begränsade till en värdetyp som inte kan null (dvs. valfri typparameter med en begränsning för värdetyp (§15.2.5)). Typen System.Nullable<T> anger begränsningen för värdetyp för T, vilket innebär att den underliggande typen av en nullbar värdetyp kan vara valfri värdetyp som inte kan null. Den underliggande typen av en nullbar värdetyp kan inte vara en nullbar värdetyp eller en referenstyp. Till exempel int?? är en ogiltig typ. Nullbara referenstyper omfattas av §8.9.

En instans av en nullbar värdetyp T? har två offentliga skrivskyddade egenskaper:

  • En HasValue egenskap av typen bool
  • En Value egenskap av typen T

En instans som HasValue sägs true vara icke-null. En instans som inte är null innehåller ett känt värde och Value returnerar det värdet.

En instans som HasValue sägs false vara null. En null-instans har ett odefinierat värde. Om du försöker läsa Value av en null-instans genereras en System.InvalidOperationException . Processen för att komma åt egenskapen Value för en nullbar instans kallas för att packa upp.

Förutom standardkonstruktorn har varje nullbar värdetyp T? en offentlig konstruktor med en enda parameter av typen T. Givet ett värde x av typen T, en konstruktoranrop av formuläret

new T?(x)

skapar en icke-null-instans T? där egenskapen Value är x. Processen att skapa en icke-null-instans av en nullbar värdetyp för ett angivet värde kallas omslutning.

Implicita konverteringar är tillgängliga från literalen till (§10.2.7TT?

Den nullbara värdetypen T? implementerar inga gränssnitt (§18). I synnerhet innebär det att det inte implementerar något gränssnitt som den underliggande typen T gör.

8.3.13 Boxning och avboxning

Begreppet boxning och avboxning ger en brygga mellan value_types och reference_typegenom att tillåta att alla värden för en value_type konverteras till och från typen object. Boxning och unboxing möjliggör en enhetlig vy av typsystemet där ett värde av vilken typ som helst i slutändan kan behandlas som en object.

Boxning beskrivs mer detaljerat i §10.2.9 och unboxing beskrivs i §10.3.7.

8.4 Konstruerade typer

8.4.1 Allmänt

En allmän typdeklaration anger i sig en obundet allmän typ som används som en "skiss" för att bilda många olika typer, genom att tillämpa typargument. Typargumenten skrivs inom vinkelparenteser (< och >) omedelbart efter namnet på den generiska typen. En typ som innehåller minst ett typargument kallas för en konstruerad typ. En konstruerad typ kan användas på de flesta platser på det språk där ett typnamn kan visas. En obunden allmän typ kan endast användas inom en typeof_expression (§12.8.18).

Konstruerade typer kan också användas i uttryck som enkla namn (§12.8.4) eller vid åtkomst till en medlem (§12.8.7).

När en namespace_or_type_name utvärderas beaktas endast generiska typer med rätt antal typparametrar. Därför är det möjligt att använda samma identifierare för att identifiera olika typer, så länge typerna har olika antal typparametrar. Detta är användbart när du blandar generiska och icke-generiska klasser i samma program.

Exempel:

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
    }
}

slutexempel

Detaljerade regler för namnsökning i namespace_or_type_name produktioner beskrivs i §7.8. Upplösningen av tvetydigheter i dessa produktioner beskrivs i §6.2.5. En type_name kan identifiera en konstruerad typ även om den inte anger typparametrar direkt. Detta kan inträffa när en typ är kapslad i en allmän class deklaration och instanstypen för den innehållande deklarationen implicit används för namnsökning (§15.3.9.7).

Exempel:

class Outer<T>
{
    public class Inner {...}

    public Inner i; // Type of i is Outer<T>.Inner
}

slutexempel

En icke-uppräkningskonstruktion får inte användas som en unmanaged_type (§8.8).

8.4.2 Typargument

Varje argument i en typargumentlista är helt enkelt en typ.

type_argument_list
    : '<' type_arguments '>'
    ;

type_arguments
    : type_argument (',' type_argument)*
    ;   

type_argument
    : type
    | type_parameter nullable_type_annotation?
    ;

Varje typargument ska uppfylla eventuella begränsningar för motsvarande typparameter (§15.2.5). Ett referenstypargument vars nullbarhet inte matchar parametern nullability för typparametern uppfyller villkoret. dock kan en varning utfärdas.

8.4.3 Öppna och stängda typer

Alla typer kan klassificeras som antingen öppna typer eller stängda typer. En öppen typ är en typ som omfattar typparametrar. Mer specifikt:

  • En typparameter definierar en öppen typ.
  • En matristyp är en öppen typ om och endast om dess elementtyp är en öppen typ.
  • En konstruerad typ är en öppen typ om och endast om ett eller flera av dess typargument är en öppen typ. En konstruerad kapslad typ är en öppen typ om och endast om ett eller flera av dess typargument eller typargumenten för dess innehållande typer är en öppen typ.

En stängd typ är en typ som inte är en öppen typ.

Vid körning körs all kod i en allmän typdeklaration i kontexten för en sluten konstruktionstyp som skapades genom att använda typargument i den allmänna deklarationen. Varje typparameter inom den allmänna typen är bunden till en viss körningstyp. Körningsbearbetningen av alla instruktioner och uttryck sker alltid med stängda typer och öppna typer sker endast under kompileringstidsbearbetning.

Två stängda konstruktionstyper är identitetskonverterade (§10.2.2) om de konstrueras av samma obundna generiska typ och det finns en identitetskonvertering mellan vart och ett av deras motsvarande typargument. Motsvarande typargument kan själva vara stängda konstruerade typer eller tupplar som är identitetsvertibla. Stängda konstruerade typer som är identitetsvertibla delar en enda uppsättning statiska variabler. Annars har varje sluten konstruerad typ en egen uppsättning statiska variabler. Eftersom det inte finns någon öppen typ vid körning finns det inga statiska variabler som är associerade med en öppen typ.

8.4.4 Bundna och obundna typer

Termen obunden typ refererar till en icke-generisk typ eller en obundna generisk typ. Termen bindningstyp refererar till en icke-generisk typ eller en konstruerad typ.

En obunden typ refererar till entiteten som deklarerats av en typdeklaration. En obundet allmän typ är inte i sig en typ och kan inte användas som typ av variabel, argument eller returvärde eller som bastyp. Den enda konstruktion där en obunden generisk typ kan refereras till är typeof uttrycket (§12.8.18).

8.4.5 Tillfredsställande begränsningar

När en konstruerad typ eller generisk metod refereras kontrolleras de angivna typargumenten mot de typparameterbegränsningar som deklareras för den generiska typen eller metoden (§15.2.5). För varje where sats kontrolleras typargumentet A som motsvarar den namngivna typparametern mot varje villkor enligt följande:

  • Om villkoret är en class typ, en gränssnittstyp eller en typparameter kan du C representera den begränsningen med de angivna typargumenten som ersätts med alla typparametrar som visas i villkoret. För att uppfylla villkoret ska det vara så att typen A kan konverteras till typ C av något av följande:
    • En identitetskonvertering (§10.2.2)
    • En implicit referenskonvertering (§10.2.8)
    • En boxningskonvertering (§10.2.9), förutsatt att den typen A är en värdetyp som inte kan nollföras.
    • En implicit referens, boxning eller typparameterkonvertering från en typparameter A till C.
  • Om begränsningen är referenstypsbegränsningen (class) ska typen A uppfylla något av följande:
    • A är en gränssnittstyp, klasstyp, ombudstyp, matristyp eller dynamisk typ.

    System.ValueType och System.Enum är referenstyper som uppfyller den här begränsningen. slutkommentar

    • A är en typparameter som är känd för att vara en referenstyp (§8.2).
  • Om villkoret är villkoret för värdetyp (struct) ska typen A uppfylla något av följande:
    • A är en struct typ eller enum typ, men inte en nullbar värdetyp.

    System.ValueType och System.Enum är referenstyper som inte uppfyller den här begränsningen. slutkommentar

    • A är en typparameter med villkoret för värdetyp (§15.2.5).
  • Om begränsningen är konstruktorbegränsningen new()ska typen A inte vara abstract och ska ha en offentlig parameterlös konstruktor. Detta är uppfyllt om något av följande är sant:
    • A är en värdetyp, eftersom alla värdetyper har en offentlig standardkonstruktor (§8.3.3).
    • A är en typparameter med konstruktorvillkoret (§15.2.5).
    • A är en typparameter med villkoret för värdetyp (§15.2.5).
    • A är en class som inte är abstrakt och innehåller en uttryckligen deklarerad offentlig konstruktor utan parametrar.
    • A är inte abstract och har en standardkonstruktor (§15.11.5).

Ett kompileringsfel inträffar om en eller flera av en typparameters begränsningar inte uppfylls av de angivna typargumenten.

Eftersom typparametrar inte ärvs ärvs inte heller begränsningar.

Exempel: I följande D måste du ange begränsningen för dess typparameter så att den uppfyller den begränsning som T tillämpas av basparametern TclassB<T>. Däremot classE behöver du inte ange någon begränsning, eftersom List<T> implementerar IEnumerable för alla T.

class B<T> where T: IEnumerable {...}
class D<T> : B<T> where T: IEnumerable {...}
class E<T> : B<List<T>> {...}

slutexempel

8.5 Typparametrar

En typparameter är en identifierare som anger en värdetyp eller referenstyp som parametern är bunden till vid körning.

type_parameter
    : identifier
    ;

Eftersom en typparameter kan instansieras med många olika typargument har typparametrarna något olika åtgärder och begränsningar än andra typer.

Obs! Följande är:

  • En typparameter kan inte användas direkt för att deklarera en basklass (§15.2.4.2) eller gränssnitt (§18.2.4).
  • Reglerna för medlemssökning för typparametrar beror på eventuella begränsningar som tillämpas på typparametern. De beskrivs i §12.5.
  • Vilka konverteringar som är tillgängliga för en typparameter beror på eventuella begränsningar som tillämpas på typparametern. De beskrivs i §10.2.12 och §10.3.8.
  • Literalen null kan inte konverteras till en typ som anges av en typparameter, förutom om typparametern är känd för att vara en referenstyp (§10.2.12). Ett standarduttryck (§12.8.21) kan dock användas i stället. Dessutom kan ett värde med en typ som anges av en typparameter jämföras med null med hjälp av och == (!=) om inte typparametern har villkoret värdetyp.
  • Ett new uttryck (§12.8.17.2) kan bara användas med en typparameter om typparametern begränsas av en constructor_constraint eller begränsningen för värdetyp (§15.2.5).
  • En typparameter kan inte användas någonstans inom ett attribut.
  • En typparameter kan inte användas i en medlemsåtkomst (§12.8.7) eller typnamn (§7.8) för att identifiera en statisk medlem eller en kapslad typ.
  • En typparameter kan inte användas som en unmanaged_type (§8.8).

slutkommentar

Som typ är typparametrar enbart en kompileringstidskonstruktion. Vid körningen är varje typparameter bunden till en körningstyp som angavs genom att ange ett typargument till den allmänna typdeklarationen. Därför kommer typen av en variabel som deklareras med en typparameter vid körning att vara en stängd konstruktionstyp §8.4.3. Körningen av alla instruktioner och uttryck som involverar typparametrar använder den typ som angavs som typargument för parametern.

8.6 Uttrycksträdtyper

Med uttrycksträd kan lambda-uttryck representeras som datastrukturer i stället för körbar kod. Uttrycksträd är värden för uttrycksträdstyper i formuläret System.Linq.Expressions.Expression<TDelegate>, där TDelegate är valfri delegattyp. För resten av den här specifikationen refereras dessa typer till med hjälp av förkortningen Expression<TDelegate>.

Om det finns en konvertering från ett lambda-uttryck till en ombudstyp Dfinns även en konvertering till uttrycksträdstypen Expression<TDelegate>. Medan konverteringen av ett lambda-uttryck till en delegattyp genererar ett ombud som refererar till körbar kod för lambda-uttrycket, skapar konvertering till en uttrycksträdstyp en uttrycksträdsrepresentation av lambda-uttrycket. Mer information om denna konvertering ges i §10.7.3.

Exempel: Följande program representerar ett lambda-uttryck både som körbar kod och som ett uttrycksträd. Eftersom det finns en konvertering till Func<int,int>finns det också en konvertering till Expression<Func<int,int>>:

Func<int,int> del = x => x + 1;             // Code
Expression<Func<int,int>> exp = x => x + 1; // Data

Efter dessa tilldelningar refererar ombudet del till en metod som returnerar x + 1, och uttrycket tree exp refererar till en datastruktur som beskriver uttrycket x => x + 1.

slutexempel

Expression<TDelegate> tillhandahåller en instansmetod Compile som skapar ett ombud av typen TDelegate:

Func<int,int> del2 = exp.Compile();

Om du anropar det här ombudet körs koden som representeras av uttrycksträdet. Med tanke på definitionerna ovan del , och del2 är likvärdiga, och följande två instruktioner kommer att ha samma effekt:

int i1 = del(1);
int i2 = del2(1);

När du har kört den här koden i1 har i2 båda värdet 2.

API-ytan som tillhandahålls av Expression<TDelegate> är implementeringsdefinierad utöver kravet på en Compile metod som beskrivs ovan.

Obs! Även om informationen om API:et som tillhandahålls för uttrycksträd är implementeringsdefinierad förväntas en implementering:

  • Aktivera kod för att inspektera och svara på strukturen för ett uttrycksträd som skapats som ett resultat av en konvertering från ett lambda-uttryck
  • Aktivera att uttrycksträd skapas programmatiskt i användarkoden

slutkommentar

8.7 Den dynamiska typen

Typen dynamic använder dynamisk bindning, enligt beskrivningen i §12.3.2, i motsats till statisk bindning som används av alla andra typer.

Typen dynamic anses vara identisk object med, förutom i följande avseenden:

  • Åtgärder för uttryck av typen dynamic kan vara dynamiskt bundna (§12.3.3).
  • Typinferens (§12.6.3) föredrar dynamic framför object om båda är kandidater.
  • dynamic kan inte användas som
    • typen i en object_creation_expression (§12.8.17.2)
    • a class_base (§15.2.4)
    • en predefined_type i en member_access (§12.8.7.1)
    • operand för operatorn typeof
    • ett attributargument
    • en begränsning
    • en tilläggsmetodtyp
    • någon del av ett typargument inom struct_interfaces (§16.2.5) eller interface_type_list (§15.2.4.1).

På grund av den här ekvivalensen gäller följande:

  • Det finns en implicit identitetskonvertering
    • mellan object och dynamic
    • mellan konstruerade typer som är desamma när du dynamic ersätter med object
    • mellan tuppelns typer som är desamma när du dynamic ersätter med object
  • Implicita och explicita konverteringar till och från object gäller även för och från dynamic.
  • Signaturer som är desamma när du dynamic ersätter med object anses vara samma signatur.
  • Typen dynamic kan inte skiljas från typen object vid körning.
  • Ett uttryck av typen dynamic kallas för ett dynamiskt uttryck.

8.8 Ohanterade typer

unmanaged_type
    : value_type
    | pointer_type     // unsafe code support
    ;

En unmanaged_type är en typ som varken är en reference_type eller en type_parameter som inte är begränsad till ohanterad och som inte innehåller några instansfält vars typ inte är en unmanaged_type. Med andra ord är en unmanaged_type något av följande:

  • sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, , decimaleller bool.
  • Alla enum_type.
  • Alla användardefinierade struct_type som innehåller instansfält för endast unmanaged_types.
  • Valfri typparameter som är begränsad till att vara ohanterad.
  • Vilken som helst pointer_type (§23.3).

8.9 Referenstyper och nullbarhet

8.9.1 Allmänt

En nullbar referenstyp anges genom att en nullable_type_annotation (?) läggs till i en referenstyp som inte går att nolla. Det finns ingen semantisk skillnad mellan en icke-nullbar referenstyp och dess motsvarande nullbara typ, båda kan antingen vara en referens till ett objekt eller null. Förekomsten eller frånvaron av nullable_type_annotation deklarerar om ett uttryck är avsett att tillåta null-värden eller inte. En kompilator kan tillhandahålla diagnostik när ett uttryck inte används enligt den avsikten. Null-tillståndet för ett uttryck definieras i §8.9.5. Det finns en identitetskonvertering mellan en referenstyp som kan ogiltigförklaras och dess motsvarande referenstyp som inte kan ogiltigförklaras (§10.2.2).

Det finns två former av nullbarhet för referenstyper:

  • nullable: En nullable-reference-type kan tilldelas null. Standardvärdet för null är kanske null.
  • icke-nullbar: En icke-nullbar referens ska inte tilldelas ett null värde. Standardvärdet för null är inte null.

Obs! Typerna R och R? representeras av samma underliggande typ, R. En variabel av den underliggande typen kan antingen innehålla en referens till ett objekt eller vara värdet null, vilket indikerar "ingen referens". slutkommentar

Den syntaktiska skillnaden mellan en nullbar referenstyp och dess motsvarande referenstyp som inte kan nollföras gör det möjligt för en kompilator att generera diagnostik. En kompilator måste tillåta nullable_type_annotation enligt definitionen i §8.2.1. Diagnostiken måste begränsas till varningar. Varken förekomsten eller frånvaron av null-anteckningar eller tillståndet för den nullbara kontexten kan ändra kompileringstiden eller körningsbeteendet för ett program förutom ändringar i diagnostikmeddelanden som genereras vid kompileringstiden.

8.9.2 Referenstyper som inte kan upphävas

En icke-nullbar referenstyp är en referenstyp för formuläret T, där T är namnet på typen. Standardvärdet för null-tillstånd för en variabel som inte kan nollas är inte null. Varningar kan genereras när ett uttryck som kanske är null används där ett värde som inte är null krävs.

8.9.3 Nullable-referenstyper

En referenstyp för formuläret T? (till exempel string?) är en nullbar referenstyp. Standardvärdet null för en nullbar variabel är kanske null. ? Kommentaren anger avsikten att variabler av den här typen är nullbara. En kompilator kan identifiera dessa avsikter för att utfärda varningar. När den nullbara anteckningskontexten är inaktiverad kan en varning genereras med hjälp av den här anteckningen.

8.9.4 Null-kontext

8.9.4.1 Allmänt

Varje rad med källkod har en nullbar kontext. Anteckningsflaggor och varningsflaggor för den nullbara kontexten styr nullbara anteckningar (§8.9.4.3) respektive nullbara varningar (§8.9.4.4). Varje flagga kan aktiveras eller inaktiveras. En kompilator kan använda statisk flödesanalys för att fastställa nulltillståndet för alla referensvariabler. En referensvariabels null-tillstånd (§8.9.5) är antingen inte null, kanske null eller kanske standard.

Den nullbara kontexten kan anges i källkoden via null-direktiv (§6.5.9) och/eller via någon implementeringsspecifik mekanism utanför källkoden. Om båda metoderna används ersätter null-direktiv de inställningar som görs via en extern mekanism.

Standardtillståndet för den nullbara kontexten är implementering definierat.

I den här specifikationen ska all C#-kod som inte innehåller null-direktiv, eller om vilken ingen instruktion görs om det aktuella nullbara kontexttillståndet, antas ha sammanställts med hjälp av en nullbar kontext där både anteckningar och varningar är aktiverade.

Obs! En nullbar kontext där båda flaggorna är inaktiverade matchar det tidigare standardbeteendet för referenstyper. slutkommentar

8.9.4.2 Nullable disable

När både varnings- och anteckningsflaggor inaktiveras inaktiveras den nullbara kontexten.

När den nullbara kontexten är inaktiverad:

  • Ingen varning ska genereras när en variabel av en ej kommenterad referenstyp initieras med, eller tilldelas ett värde av, null.
  • Ingen varning ska genereras när en variabel av en referenstyp som eventuellt har värdet null.
  • För alla referenstyper Tgenererar anteckningen ? i T? ett meddelande och typen T? är samma som T.
  • För alla typer av parametervillkor where T : C?genererar anteckningen ? i C? ett meddelande och typen C? är samma som C.
  • För alla typer av parametervillkor where T : U?genererar anteckningen ? i U? ett meddelande och typen U? är samma som U.
  • Den allmänna begränsningen class? genererar ett varningsmeddelande. Typparametern måste vara en referenstyp.

    Obs! Det här meddelandet kännetecknas av "information" snarare än "varning", för att inte förväxla det med tillståndet för den nullbara varningsinställningen, som inte är relaterad. slutkommentar

  • Operatorn ! null-forgiving (§12.8.9) har ingen effekt.

Exempel:

#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

slutexempel

8.9.4.3 Ogiltiga anteckningar

När varningsflaggan är inaktiverad och anteckningsflaggan är aktiverad är den nullbara kontexten anteckningar.

När den nullbara kontexten är anteckningar:

  • För alla referenstyper Tanger anteckningen ? i T? att T? en nullbar typ, medan den ej kommenterade T är icke-nullbar.
  • Inga diagnostikvarningar som rör nullabilitet genereras.
  • Operatorn ! null-forgiving (§12.8.9) kan ändra det analyserade null-tillståndet för dess operande och vilken kompileringstid diagnostikvarningar genereras.

Exempel:

#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

slutexempel

8.9.4.4 Nullable-varningar

När varningsflaggan är aktiverad och anteckningsflaggan är inaktiverad är den nullbara kontexten varningar.

När den nullbara kontexten är varningar kan en kompilator generera diagnostik i följande fall:

  • En referensvariabel som har bedömts vara null är derefererad.
  • En referensvariabel av en icke-nullbar typ tilldelas till ett uttryck som kanske är null.
  • ? Används för att notera en nullbar referenstyp.
  • Operatorn ! null-forgiving (§12.8.9) används för att ange null-tillståndet för dess operande till inte null.

Exempel:

#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

slutexempel

8.9.4.5 Nullable enable

När både varningsflaggan och anteckningsflaggan är aktiverade aktiveras den nullbara kontexten.

När den nullbara kontexten är aktiverad:

  • För alla referenstyper Tgör ? anteckningen T? i T? en nullbar typ, medan den ej kommenterade T är icke-nullbar.
  • En kompilator kan använda statisk flödesanalys för att fastställa nulltillståndet för alla referensvariabler. När nullbara varningar är aktiverade är referensvariabelns nulltillstånd (§8.9.5) antingen inte null, kanske null, eller kanske standard och
  • Operatorn ! null-forgiving (§12.8.9) anger null-tillståndet för operanden till inte null.
  • En kompilator kan utfärda en varning om nullbarheten för en typparameter inte matchar nullbarheten för motsvarande typargument.

8.9.5 Nullabilities och null states

En kompilator krävs inte för att utföra någon statisk analys och behöver inte heller generera några diagnostikvarningar relaterade till nullabilitet.

Resten av den här underklienten är villkorligt normativ.

En kompilator som genererar diagnostikvarningar följer dessa regler.

Varje uttryck har ett av tre null-tillstånd:

  • kanske null: Värdet för uttrycket kan utvärderas till null.
  • kanske standard: Värdet för uttrycket kan utvärderas till standardvärdet för den typen.
  • inte null: Uttryckets värde är inte null.

Standardvärdet null för ett uttryck bestäms av dess typ och status för anteckningsflaggan när det deklareras:

  • Standardvärdet null för en null-referenstyp är:
    • Kanske null när dess deklaration finns i text där anteckningsflaggan är aktiverad.
    • Inte null när dess deklaration finns i text där anteckningsflaggan är inaktiverad.
  • Standardvärdet null för en icke-nullbar referenstyp är inte null.

Obs! Standardtillståndet kanske används med parametrar av typen ej tränad typ när typen är en icke-nullbar typ, till exempel string och uttrycket default(T) är null-värdet. Eftersom null inte finns i domänen för den icke-nullbara typen är tillståndet kanske standard. slutkommentar

En diagnostik kan skapas när en variabel (§9.2.1) av en referenstyp som inte kan nulliseras initieras eller tilldelas ett uttryck som kanske är null när variabeln deklareras i text där anteckningsflaggan är aktiverad.

Exempel: Tänk på följande metod där en parameter är nullbar och det värdet tilldelas till en typ som inte kan null:

#nullable enable
public class C
{
    public void M(string? p)
    {
        // Warning: Assignment of maybe null value to non-nullable variable
        string s = p;
    }
}

En kompilator kan utfärda en varning där parametern som kan vara null tilldelas till en variabel som inte ska vara null. Om parametern är null-kontrollerad före tilldelningen kan en kompilator använda den i dess nullbara tillståndsanalys och inte utfärda en varning:

#nullable enable
public class C
{
    public void M(string? p)
    {
        if (p != null)
        {
            string s = p; // No warning
            // Use s
        }
    }
}

slutexempel

En kompilator kan uppdatera null-tillståndet för en variabel som en del av analysen.

Exempel: En kompilator kan välja att uppdatera tillståndet baserat på eventuella instruktioner i programmet:

#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
}

I föregående exempel kan en kompilator besluta att null-tillståndet för p inte är null efter instruktionen int length = p.Length;. Om det var null skulle instruktionen ha genererat en NullReferenceException. Detta liknar beteendet om koden hade föregåtts av if (p == null) throw NullReferenceException(); förutom att koden som skrivs kan ge en varning, vars syfte är att varna för att ett undantag kan genereras implicit. slutexempel

Senare i metoden kontrollerar koden att det s inte är en null-referens. Null-tillståndet s för kan ändras till kanske null när det null-markerade blocket har stängts. En kompilator kan dra slutsatsen att s kanske är null eftersom koden skrevs för att anta att den kan ha varit null. När koden innehåller en null-kontroll kan en kompilator i allmänhet dra slutsatsen att värdet kan ha varit null:

Exempel: Vart och ett av följande uttryck innehåller någon form av en null-kontroll. Null-tillståndet för o kan ändras från inte null till kanske null efter var och en av dessa instruktioner:

#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
    }
}

Både automatisk egenskap och fältliknande händelsedeklarationer använder ett kompilatorgenererat bakgrundsfält. Null-tillståndsanalys kan härleda att tilldelningen till händelsen eller egenskapen är en tilldelning till ett kompilatorgenererat bakgrundsfält.

Exempel: En kompilator kan avgöra att skriva en automatisk egenskap eller fältliknande händelse skriver motsvarande kompilatorgenererade bakgrundsfält. Egenskapens null-tillstånd matchar värdet för bakgrundsfältet.

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.
    }
}

I föregående exempel anger konstruktorn inte P till ett värde som inte är null, och en kompilator kan utfärda en varning. Det finns ingen varning när egenskapen P nås, eftersom egenskapens typ är en referenstyp som inte kan upphävas. slutexempel

En kompilator kan behandla en egenskap (§15.7) som antingen en variabel med tillstånd, eller som oberoende get- och set-accessorer (§15.7.3).

Exempel: En kompilator kan välja om skrivning till en egenskap ändrar null-tillståndet för att läsa egenskapen, eller om läsning av en egenskap ändrar egenskapens null-tillstånd.

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
        }
    }
}

I föregående exempel är bakgrundsfältet för DisappearingProperty värdet null när det läse. En kompilator kan dock anta att läsning av en egenskap inte ändrar null-tillståndet för uttrycket. slutexempel

En kompilator kan använda alla uttryck som refererar till en variabel, egenskap eller händelse för att ange null-tillståndet till inte null. Om det var null skulle dereference-uttrycket ha genererat en NullReferenceException:

Exempel:


public class C
{
    private C? child;
   
    public void M()
    {
        _ = child.child.child; // Warning. Dereference possible null value
        var greatGrandChild = child.child.child; // No warning. 
    }
}

slutexempel

Slut på villkorsstyrd normativ text