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 dynamic
skjuts 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 class
System.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
,long
ochulong
är0
standardvärdet . - För
char
är'\x0000'
standardvärdet . - För
float
är0.0f
standardvärdet . - För
double
är0.0d
standardvärdet . - För
decimal
är0m
standardvärdet (det vill: värdet noll med skalning 0). - För
bool
ärfalse
standardvärdet . - För en enum_type
E
är0
standardvärdet , konverterat till typenE
.
- För
- 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äsaValue
egenskapen för ett sådant värde gör att ett undantag av typenSystem.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
i
j
k
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 iSystem.Int32
och de medlemmar som ärvts frånSystem.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 typenint
och'a'
är en literal av typenchar
. 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
, , ulong
och char
. Integraltyperna har följande storlekar och intervall med värden:
- Typen
sbyte
representerar signerade 8-bitars heltal med värden från-128
till127
, inklusive. - Typen
byte
representerar osignerade 8-bitars heltal med värden från0
till255
, inklusive. - Typen
short
representerar signerade 16-bitars heltal med värden från-32768
till32767
, inklusive. - Typen
ushort
representerar osignerade 16-bitars heltal med värden från0
till65535
, inklusive. - Typen
int
representerar signerade 32-bitars heltal med värden från-2147483648
till2147483647
, inklusive. - Typen
uint
representerar osignerade 32-bitars heltal med värden från0
till4294967295
, inklusive. - Typen
long
representerar signerade 64-bitars heltal med värden från-9223372036854775808
till9223372036854775807
, inklusive. - Typen
ulong
representerar osignerade 64-bitars heltal med värden från0
till18446744073709551615
, inklusive. - Typen
char
representerar osignerade 16-bitars heltal med värden från0
till65535
, inklusive. Uppsättningen med möjliga värden förchar
typen motsvarar Unicode-teckenuppsättningen.Obs! Även om
char
har samma representation somushort
tillå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 typernabyte
ochushort
i synnerhet har värden som kan representeras fullt ut med hjälpchar
av typen, finns implicita konverteringar från sbyte, byte ellerushort
för attchar
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 , 0
float
< 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äretx * y / z
, där multiplikationen ger ett resultat som ligger utanfördouble
intervallet, men den efterföljande divisionen tar det tillfälliga resultatet tillbaka tilldouble
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 Emin ≤ e ≤ Emax, 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 decimal
s med ett absolut värde som är mindre än 1.0m
är värdet exakt till minst den 28:e decimalen. För decimal
s med ett absolut värde som är större än eller lika 1.0m
med ä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
false
och ett icke-nollintegrerat eller flyttalsvärde eller en icke-null-pekare kan konverteras till det booleska värdettrue
. 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 mednull
. 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 , , , , , , byte
sbyte
eller short
. ushort
int
uint
long
ulong
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
, Item2
osv., 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 detItem...
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
, ochpair3
är alla giltiga, med namn för nej, vissa eller alla tuppeln typ element.Tuppelns typ för
pair4
är giltig eftersom namnenItem1
ochItem2
matchar deras positioner, medan tuppelns typ förpair5
inte tillåts, eftersom namnenItem2
ochItem123
inte gör det.Deklarationerna för
pair6
ochpair7
visar att tupplar är utbytbara med konstruerade typer av formuläretValueTuple<...>
och att operatornnew
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 typenbool
- En
Value
egenskap av typenT
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.7T
T?
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 duC
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 typenA
kan konverteras till typC
av något av följande: - Om begränsningen är referenstypsbegränsningen (
class
) ska typenA
uppfylla något av följande:-
A
är en gränssnittstyp, klasstyp, ombudstyp, matristyp eller dynamisk typ.
System.ValueType
ochSystem.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 typenA
uppfylla något av följande:-
A
är enstruct
typ ellerenum
typ, men inte en nullbar värdetyp.
System.ValueType
ochSystem.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 typenA
inte varaabstract
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 enclass
som inte är abstrakt och innehåller en uttryckligen deklarerad offentlig konstruktor utan parametrar. -
A
är inteabstract
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 somT
tillämpas av basparameternT
class
B<T>
. Däremotclass
E
behöver du inte ange någon begränsning, eftersomList<T>
implementerarIEnumerable
för allaT
.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 D
finns ä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 tillExpression<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 returnerarx + 1
, och uttrycket tree exp refererar till en datastruktur som beskriver uttrycketx => 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örobject
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
ochdynamic
- mellan konstruerade typer som är desamma när du
dynamic
ersätter medobject
- mellan tuppelns typer som är desamma när du
dynamic
ersätter medobject
- mellan
- Implicita och explicita konverteringar till och från
object
gäller även för och fråndynamic
. - Signaturer som är desamma när du
dynamic
ersätter medobject
anses vara samma signatur. - Typen
dynamic
kan inte skiljas från typenobject
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
, ,decimal
ellerbool
. - 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
ochR?
representeras av samma underliggande typ,R
. En variabel av den underliggande typen kan antingen innehålla en referens till ett objekt eller vara värdetnull
, 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
T
genererar anteckningen?
iT?
ett meddelande och typenT?
är samma somT
. - För alla typer av parametervillkor
where T : C?
genererar anteckningen?
iC?
ett meddelande och typenC?
är samma somC
. - För alla typer av parametervillkor
where T : U?
genererar anteckningen?
iU?
ett meddelande och typenU?
är samma somU
. - 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
T
anger anteckningen?
iT?
attT?
en nullbar typ, medan den ej kommenteradeT
ä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
T
gör?
anteckningenT?
iT?
en nullbar typ, medan den ej kommenteradeT
ä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 uttrycketdefault(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 instruktionenint length = p.Length;
. Om det var null skulle instruktionen ha genererat enNullReferenceException
. Detta liknar beteendet om koden hade föregåtts avif (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 egenskapenP
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
ECMA C# draft specification