8 Typen
8.1 Algemeen
De typen C#-taal zijn onderverdeeld in twee hoofdcategorieën: verwijzingstypen en waardetypen. Zowel waardetypen als verwijzingstypen kunnen algemene typen zijn, die een of meer typeparameters gebruiken. Typeparameters kunnen zowel waardetypen als referentietypen aanwijzen.
type
: reference_type
| value_type
| type_parameter
| pointer_type // unsafe code support
;
pointer_type (§23.3) is alleen beschikbaar in onveilige code (§23).
Waardetypen verschillen van verwijzingstypen in die variabelen van de waardetypen rechtstreeks hun gegevens bevatten, terwijl variabelen van de verwijzingstypen verwijzingen naar hun gegevens opslaan, de laatste wel objecten genoemd. Met verwijzingstypen is het mogelijk dat twee variabelen verwijzen naar hetzelfde object, waardoor bewerkingen op één variabele van invloed zijn op het object waarnaar wordt verwezen door de andere variabele. Met waardetypen hebben de variabelen elk hun eigen kopie van de gegevens en is het niet mogelijk om bewerkingen op de ene te beïnvloeden.
Opmerking: Wanneer een variabele een verwijzings- of uitvoerparameter is, heeft deze geen eigen opslag, maar verwijst naar de opslag van een andere variabele. In dit geval is de verw- of outvariabele effectief een alias voor een andere variabele en niet een afzonderlijke variabele. eindnotitie
Het typesysteem van C# is geïntegreerd, zodat een waarde van elk type kan worden behandeld als een object. Elk type in C# is direct of indirect afgeleid van het object
klassetype en object
is de ultieme basisklasse van alle typen. Waarden van referentietypen worden behandeld als objecten door de waarden als type object
te bekijken. Waarden van waardetypen worden behandeld als objecten door boks- en uitboxbewerkingen uit te voeren (§8.3.13).
Voor het gemak worden sommige bibliotheektypenamen geschreven zonder de volledige naamkwalificatie te gebruiken. Raadpleeg §C.5 voor meer informatie.
8.2 Referentietypen
8.2.1 Algemeen
Een verwijzingstype is een klassetype, een interfacetype, een matrixtype, een gemachtigde of het dynamic
type. Voor elk niet-null-verwijzingstype is er een bijbehorend verwijzingstype dat kan worden vermeld door de ?
naam van het type toe te voegen.
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 is alleen beschikbaar in onveilige code (§23.3). nullable_reference_type wordt verder besproken in §8.9.
Een waarde van het verwijzingstype is een verwijzing naar een exemplaar van het type, de laatste ook wel een object genoemd. De speciale waarde null
is compatibel met alle referentietypen en geeft aan dat er geen exemplaar is.
8.2.2 Klassetypen
Een klassetype definieert een gegevensstructuur die gegevensleden (constanten en velden), functieleden (methoden, eigenschappen, gebeurtenissen, indexeerfuncties, operators, instantieconstructors, finalizers en statische constructors) en geneste typen bevat. Klassetypen ondersteunen overname, een mechanisme waarbij afgeleide klassen basisklassen kunnen uitbreiden en specialiseren. Exemplaren van klassetypen worden gemaakt met object_creation_expression s (§12.8.17.2).
Klassetypen worden beschreven in §15.
Bepaalde vooraf gedefinieerde klassetypen hebben speciale betekenis in de C#-taal, zoals beschreven in de onderstaande tabel.
Klassetype | Beschrijving |
---|---|
System.Object |
De ultieme basisklasse van alle andere typen. Zie §8.2.3. |
System.String |
Het tekenreekstype van de C#-taal. Zie §8.2.5. |
System.ValueType |
De basisklasse van alle waardetypen. Zie §8.3.2. |
System.Enum |
De basisklasse van alle enum typen. Zie §19.5. |
System.Array |
De basisklasse van alle matrixtypen. Zie §17.2.2. |
System.Delegate |
De basisklasse van alle delegate typen. Zie §20.1. |
System.Exception |
De basisklasse van alle uitzonderingstypen. Zie §21.3. |
8.2.3 Het objecttype
Het object
klassetype is de ultieme basisklasse van alle andere typen. Elk type in C# is direct of indirect afgeleid van het object
klassetype.
Het trefwoord object
is gewoon een alias voor de vooraf gedefinieerde klasse System.Object
.
8.2.4 Het dynamische type
Het dynamic
type, zoals object
, kan verwijzen naar elk object. Wanneer bewerkingen worden toegepast op expressies van het type dynamic
, wordt de resolutie uitgesteld totdat het programma wordt uitgevoerd. Als de bewerking dus niet legitiem kan worden toegepast op het object waarnaar wordt verwezen, wordt er geen fout gegeven tijdens de compilatie. In plaats daarvan wordt er een uitzondering gegenereerd wanneer de oplossing van de bewerking mislukt tijdens runtime.
Het dynamic
type wordt verder beschreven in §8.7 en dynamische binding in §12.3.1.
8.2.5 Het tekenreekstype
Het string
type is een verzegeld klassetype dat rechtstreeks wordt overgenomen van object
. Exemplaren van de string
klasse vertegenwoordigen Unicode-tekenreeksen.
Waarden van het string
type kunnen worden geschreven als letterlijke tekenreeksen (§6.4.5.6).
Het trefwoord string
is gewoon een alias voor de vooraf gedefinieerde klasse System.String
.
8.2.6 Interfacetypen
Een interface definieert een contract. Een klasse of struct die een interface implementeert, voldoet aan het contract. Een interface kan overnemen van meerdere basisinterfaces en een klasse of struct kan meerdere interfaces implementeren.
Interfacetypen worden beschreven in §18.
8.2.7 Matrixtypen
Een matrix is een gegevensstructuur die nul of meer variabelen bevat, die toegankelijk zijn via berekende indexen. De variabelen in een matrix, ook wel de elementen van de matrix genoemd, zijn allemaal van hetzelfde type en dit type wordt het elementtype van de matrix genoemd.
Matrixtypen worden beschreven in §17.
8.2.8 Gedelegeerde typen
Een gemachtigde is een gegevensstructuur die verwijst naar een of meer methoden. Zo verwijst het ook naar de bijbehorende objectexemplaren.
Opmerking: Het dichtstbijzijnde equivalent van een gemachtigde in C of C++ is een functiepointer, maar terwijl een functiepointer alleen kan verwijzen naar statische functies, kan een gemachtigde verwijzen naar zowel statische als exemplaarmethoden. In het laatste geval slaat de gedelegeerde niet alleen een verwijzing op naar het toegangspunt van de methode, maar ook naar het objectexemplaren waarop de methode moet worden aangeroepen. eindnotitie
Typen gemachtigden worden beschreven in §20.
8.3 Waardetypen
8.3.1 Algemeen
Een waardetype is een structtype of een opsommingstype. C# biedt een set vooraf gedefinieerde structtypen die de eenvoudige typen worden genoemd. De eenvoudige typen worden geïdentificeerd via trefwoorden.
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
;
In tegenstelling tot een variabele van een verwijzingstype kan een variabele van een waardetype alleen de waarde null
bevatten als het waardetype een null-waardetype is (§8.3.12). Voor elk niet-null-waardetype is er een bijbehorend waardetype dat dezelfde set waarden plus de waarde null
aangeeft.
Door de toewijzing aan een variabele van een waardetype wordt een kopie gemaakt van de waarde die wordt toegewezen. Dit verschilt van toewijzing tot een variabele van een verwijzingstype, waarmee de verwijzing wordt gekopieerd, maar niet het object dat wordt geïdentificeerd door de verwijzing.
8.3.2 Het type System.ValueType
Alle waardetypen nemen impliciet over van de class
System.ValueType
, die op zijn beurt overgenomen wordt van klasse object
. Het is niet mogelijk voor elk type om af te leiden van een waardetype en waardetypen worden dus impliciet verzegeld (§15.2.2.3).
Houd er rekening mee dat dit System.ValueType
zelf geen value_type is. In plaats daarvan is het een class_type waaruit alle value_types automatisch worden afgeleid.
8.3.3 Standaardconstructors
Alle waardetypen declareren impliciet een constructor voor openbare parameterloze exemplaren die de standaardconstructor wordt genoemd. De standaardconstructor retourneert een met nul geïnitialiseerd exemplaar dat de standaardwaarde wordt genoemd voor het waardetype:
- Voor alle simple_types is de standaardwaarde de waarde die wordt geproduceerd door een bitpatroon van alle nullen:
- Voor
sbyte
,byte
, ,short
,ushort
,int
, ,uint
, enlong
,ulong
de standaardwaarde is0
. - Voor
char
is de standaardwaarde'\x0000'
. - Voor
float
is de standaardwaarde0.0f
. - Voor
double
is de standaardwaarde0.0d
. - Voor
decimal
, de standaardwaarde is (dat wil0m
gezegd, waarde nul met schaal 0). - Voor
bool
is de standaardwaardefalse
. - Voor een enum_type
E
wordt0
de standaardwaarde geconverteerd naar het typeE
.
- Voor
- Voor een struct_type is de standaardwaarde de waarde die wordt geproduceerd door alle waardetypevelden in te stellen op de standaardwaarde en alle verwijzingstypevelden op
null
. - Voor een nullable_value_type is de standaardwaarde een exemplaar waarvoor de
HasValue
eigenschap onwaar is. De standaardwaarde wordt ook wel de null-waarde van het type null-waarde genoemd. Als u deValue
eigenschap van een dergelijke waarde probeert te lezen, wordt een uitzondering van het typeSystem.InvalidOperationException
gegenereerd (§8.3.12).
Net als elke andere instantieconstructor wordt de standaardconstructor van een waardetype aangeroepen met behulp van de new
operator.
Opmerking: Om efficiëntieredenen is deze vereiste niet bedoeld om de implementatie daadwerkelijk een constructor-aanroep te laten genereren. Voor waardetypen produceert de standaardwaarde-expressie (§12.8.21) hetzelfde resultaat als het gebruik van de standaardconstructor. eindnotitie
Voorbeeld: In de onderstaande code worden variabelen
i
enj
k
allemaal geïnitialiseerd tot nul.class A { void F() { int i = 0; int j = new int(); int k = default(int); } }
eindvoorbeeld
Omdat elk waardetype impliciet een constructor voor een openbaar parameterloos exemplaar heeft, is het niet mogelijk dat een structtype een expliciete declaratie van een parameterloze constructor bevat. Een structtype mag echter geparameteriseerde exemplaarconstructors declareren (§16.4.9).
8.3.4 Struct-typen
Een structtype is een waardetype dat constanten, velden, methoden, eigenschappen, gebeurtenissen, indexeerfuncties, operators, instantieconstructors, statische constructors en geneste typen kan declareren. De declaratie van structtypen wordt beschreven in §16.
8.3.5 Eenvoudige typen
C# biedt een set vooraf gedefinieerde struct
typen, de eenvoudige typen genoemd. De eenvoudige typen worden geïdentificeerd via trefwoorden, maar deze trefwoorden zijn gewoon aliassen voor vooraf gedefinieerde struct
typen in de System
naamruimte, zoals beschreven in de onderstaande tabel.
Trefwoord | Aliastype |
---|---|
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 |
Omdat een eenvoudig type aliassen een structtype heeft, heeft elk eenvoudig type leden.
Voorbeeld:
int
heeft de leden gedeclareerd enSystem.Int32
de leden overgenomen vanSystem.Object
, en de volgende instructies zijn toegestaan: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
eindvoorbeeld
Opmerking: De eenvoudige typen verschillen van andere structtypen omdat ze bepaalde aanvullende bewerkingen toestaan:
- Met de meeste eenvoudige typen kunnen waarden worden gemaakt door letterlijke waarden te schrijven (§6.4.5), hoewel C# geen voorzieningen biedt voor letterlijke gegevens van structtypen in het algemeen. Voorbeeld:
123
is een letterlijke tekst van het typeint
en'a'
is een letterlijke van het typechar
. eindvoorbeeld- Wanneer de operanden van een expressie alle eenvoudige typeconstanten zijn, is het mogelijk dat de compiler de expressie tijdens het compileren evalueert. Een dergelijke expressie wordt een constant_expression genoemd (§12.23). Expressies met operatoren die door andere structtypen zijn gedefinieerd, worden niet beschouwd als constante expressies
- Via
const
declaraties is het mogelijk om constanten van de eenvoudige typen (§15.4) te declareren. Het is niet mogelijk om constanten van andere structtypen te hebben, maar een vergelijkbaar effect wordt geleverd door statische leesvelden.- Conversies met eenvoudige typen kunnen deelnemen aan de evaluatie van conversieoperators die zijn gedefinieerd door andere structtypen, maar een door de gebruiker gedefinieerde conversieoperator kan nooit deelnemen aan de evaluatie van een andere door de gebruiker gedefinieerde conversieoperator (§10.5.3).
eindnotitie.
8.3.6 Integrale typen
C# ondersteunt negen integrale typen: sbyte
, , short
byte
, ushort
, int
, uint
, , long
en ulong
char
. De integrale typen hebben de volgende grootten en bereiken van waarden:
- Het
sbyte
type vertegenwoordigt ondertekende 8-bits gehele getallen met waarden van-128
tot127
en met. - Het
byte
type vertegenwoordigt niet-ondertekende 8-bits gehele getallen met waarden van0
tot255
en met. - Het
short
type vertegenwoordigt ondertekende 16-bits gehele getallen met waarden van-32768
tot32767
en met. - Het
ushort
type vertegenwoordigt niet-ondertekende 16-bits gehele getallen met waarden van0
tot65535
en met. - Het
int
type vertegenwoordigt ondertekende 32-bits gehele getallen met waarden van-2147483648
tot2147483647
en met. - Het
uint
type vertegenwoordigt niet-ondertekende 32-bits gehele getallen met waarden van0
tot4294967295
en met. - Het
long
type vertegenwoordigt ondertekende 64-bits gehele getallen met waarden van-9223372036854775808
tot9223372036854775807
en met. - Het
ulong
type vertegenwoordigt niet-ondertekende 64-bits gehele getallen met waarden van0
tot18446744073709551615
en met. - Het
char
type vertegenwoordigt niet-ondertekende 16-bits gehele getallen met waarden van0
tot65535
en met. De set mogelijke waarden voor hetchar
type komt overeen met de Unicode-tekenset.Opmerking: Hoewel
char
deze dezelfde weergave heeft alsushort
, zijn niet alle bewerkingen toegestaan voor het ene type op het andere. eindnotitie
Alle ondertekende integrale typen worden weergegeven met behulp van de complementindeling van twee.
De integral_type unaire en binaire operatoren werken altijd met ondertekende 32-bits precisie, niet-ondertekende 32-bits precisie, ondertekende 64-bits precisie of niet-ondertekende 64-bits precisie, zoals beschreven in §12.4.7.
Het char
type wordt geclassificeerd als een integraal type, maar verschilt op twee manieren van de andere integrale typen:
- Er zijn geen vooraf gedefinieerde impliciete conversies van andere typen naar het
char
type. In het bijzonder, ook al hebben debyte
enushort
typen bereiken met waarden die volledig kunnen worden vertegenwoordigd met behulp van hetchar
type, impliciete conversies van sbyte, byte ofushort
om niet techar
bestaan. - Constanten van het
char
type worden geschreven als character_literals of als integer_literalin combinatie met een cast naar het type char.
Voorbeeld:
(char)10
is hetzelfde als'\x000A'
. eindvoorbeeld
De checked
operatoren unchecked
en instructies worden gebruikt voor het controleren van overloopcontrole op integrale rekenkundige bewerkingen en conversies (§12.8.20). In een checked
context produceert een overloop een compilatietijdfout of veroorzaakt een System.OverflowException
gegenereerde fout. In een unchecked
context worden overloop genegeerd en worden alle bits in hoge volgorde die niet in het doeltype passen, verwijderd.
8.3.7 Typen drijvende komma
C# ondersteunt twee typen drijvende komma: float
en double
. De float
typen double
worden weergegeven met behulp van de 32-bits indelingen voor één precisie en 64-bits dubbelprecisie IEC 60559, die de volgende waardensets bieden:
- Positieve nul en negatieve nul. In de meeste gevallen gedragen positieve nul en negatief nul zich identiek als de eenvoudige waarde nul, maar bepaalde bewerkingen maken onderscheid tussen de twee (§12.10.3).
- Positieve oneindigheid en negatieve oneindigheid. Infinities worden geproduceerd door bewerkingen zoals het delen van een niet-nulgetal door nul.
Voorbeeld:
1.0 / 0.0
resulteert in positief oneindigheid en–1.0 / 0.0
levert negatieve oneindigheid op. eindvoorbeeld - De not-a-number-waarde , vaak afgekort NaN. NaN's worden geproduceerd door ongeldige drijvendekommabewerkingen, zoals nul delen door nul.
- De eindige set niet-nulwaarden van het formulier × m × 2e, indien s 1 of −1 en m en e worden bepaald door het specifieke drijvendekommatype: Voor
float
, 0 <m< 2²⁴ en −149 ≤ e ≤ 104, en voordouble
0 <m< 2⁵³ en −1075 ≤ e ≤ 970. Gedenormaliseerde drijvendekommagetalnummers worden beschouwd als geldige niet-nulwaarden. C# vereist noch verbiedt niet dat een conforme implementatie gedenormaliseerde drijvendekommanummers ondersteunt.
Het float
type kan waarden vertegenwoordigen van ongeveer 1,5 × 10⁻⁴⁵ tot 3,4 × 10³⁸ met een precisie van 7 cijfers.
Het double
type kan waarden vertegenwoordigen van ongeveer 5,0 × 10⁻³²⁴ tot 1,7 × 10³⁰⁸ met een precisie van 15-16 cijfers.
Als een van de operanden van een binaire operator een drijvendekommatype is, worden standaard numerieke promoties toegepast, zoals beschreven in §12.4.7 en wordt de bewerking uitgevoerd met float
of double
precisie.
De operatoren voor drijvende komma, met inbegrip van de toewijzingsoperatoren, produceren nooit uitzonderingen. In uitzonderlijke situaties produceren drijvendekommabewerkingen nul, oneindigheid of NaN, zoals hieronder wordt beschreven:
- Het resultaat van een drijvende-kommabewerking wordt afgerond op de dichtstbijzijnde vertegenwoordigbare waarde in de doelindeling.
- Als de grootte van het resultaat van een drijvende-kommabewerking te klein is voor de doelindeling, wordt het resultaat van de bewerking positief nul of negatief nul.
- Als de grootte van het resultaat van een drijvendekommage-bewerking te groot is voor de doelindeling, wordt het resultaat van de bewerking positief oneindig of negatief oneindig.
- Als een drijvendekommage-bewerking ongeldig is, wordt het resultaat van de bewerking NaN.
- Als een of beide operanden van een drijvendekommabewerking NaN zijn, wordt het resultaat van de bewerking NaN.
Drijvendekommabewerkingen kunnen met een hogere precisie worden uitgevoerd dan het resultaattype van de bewerking. Als u een waarde van een drijvende-kommatype tot de exacte precisie van het type wilt afdwingen, kan een expliciete cast (§12.9.7) worden gebruikt.
Voorbeeld: Sommige hardwarearchitecturen ondersteunen een 'uitgebreid' of 'lang dubbel' drijvendekommatype met een groter bereik en een grotere precisie dan het
double
type, en voeren impliciet alle drijvendekommabewerkingen uit met dit hogere precisietype. Alleen tegen overmatige kosten in prestaties kunnen dergelijke hardwarearchitecturen worden gemaakt om drijvendekommage-bewerkingen met minder precisie uit te voeren en in plaats van een implementatie te vereisen om zowel prestaties als precisie te verliezen, kan C# een hoger precisietype gebruiken voor alle drijvendekommabewerkingen. Afgezien van het leveren van nauwkeurigere resultaten, heeft dit zelden meetbare effecten. In expressies van het formulierx * y / z
, waarbij de vermenigvuldiging echter een resultaat produceert dat zich buiten hetdouble
bereik bevindt, maar de volgende deling het tijdelijke resultaat weer in hetdouble
bereik brengt, kan het feit dat de expressie wordt geëvalueerd in een hogere bereiknotatie ertoe leiden dat een eindig resultaat wordt geproduceerd in plaats van een oneindigheid. eindvoorbeeld
8.3.8 Het decimale type
Het decimal
type is een 128-bits gegevenstype dat geschikt is voor financiële en monetaire berekeningen. Het decimal
type kan waarden vertegenwoordigen, inclusief waarden in het bereik ten minste -7,9 × 10⁻²⁸ tot 7,9 × 10²⁸, met ten minste 28 cijfers.
De eindige set waarden van het type decimal
is van het formulier (–1)v × c × 10⁻e, wanneer het teken v 0 of 1 is, wordt de coëfficiënt c gegeven door 0 ≤ cmax< en de schaal e zodanig dat Emin ≤ e ≤ Emax, waarbij Cmax ten minste 1 × 10²⁸, Emin ≤ 0 is, en Emax ≥ 28. Het decimal
type biedt niet noodzakelijkerwijs ondersteuning voor ondertekende nullen, infinities of NaN's.
Een decimal
wordt weergegeven als een geheel getal dat wordt geschaald door een macht van tien. Voor decimal
s met een absolute waarde kleiner dan 1.0m
, is de waarde exact tot ten minste de 28e decimale plaats. Voor decimal
s met een absolute waarde groter dan of gelijk aan 1.0m
, is de waarde exact tot ten minste 28 cijfers. In tegenstelling tot de float
gegevenstypen kunnen double
decimale breuknummers, zoals 0.1
exact worden weergegeven in de decimale weergave. In de float
en double
representaties hebben dergelijke getallen vaak niet-afsluitbare binaire uitbreidingen, waardoor deze weergaven gevoeliger zijn voor afrondingsfouten.
Als een van de operanden van een binaire operator van het decimal
type is, worden standaard numerieke promoties toegepast, zoals beschreven in §12.4.7 en wordt de bewerking met double
precisie uitgevoerd.
Het resultaat van een bewerking op waarden van het type decimal
is dat het resultaat zou zijn van het berekenen van een exact resultaat (behoud van de schaal, zoals gedefinieerd voor elke operator) en vervolgens afronden op de weergave. Resultaten worden afgerond op de dichtstbijzijnde vertegenwoordigbare waarde en, wanneer een resultaat even dicht bij twee vertegenwoordigbare waarden ligt, tot de waarde met een even getal op de minst significante cijferpositie (dit staat bekend als 'afronden van bankier'). Dat wil zeggen dat de resultaten exact zijn tot ten minste de 28e decimale plaats. Afronding kan een nulwaarde opleveren van een niet-nulwaarde.
Als een decimal
rekenkundige bewerking een resultaat produceert waarvan de grootte te groot is voor de decimal
notatie, wordt er een System.OverflowException
gegenereerd.
Het decimal
type heeft een grotere precisie, maar kan een kleiner bereik hebben dan de typen drijvende komma. Conversies van de drijvende-kommatypen naar decimal
kunnen dus overloopuitzonderingen produceren en conversies van decimal
naar de typen drijvende komma kunnen leiden tot verlies van precisie- of overloopuitzonderingen. Om deze redenen bestaan er geen impliciete conversies tussen de typen drijvende komma en decimal
, en zonder expliciete casts, treedt er een compilatiefout op wanneer drijvende komma en decimal
operanden rechtstreeks in dezelfde expressie worden gemengd.
8.3.9 Het Bool-type
Het bool
type vertegenwoordigt booleaanse logische hoeveelheden. De mogelijke waarden van het type bool
zijn true
en false
. De weergave daarvan false
wordt beschreven in §8.3.3. Hoewel de vertegenwoordiging van true
de gegevens niet is aangegeven, moet deze afwijken van die van false
.
Er bestaan geen standaardconversies tussen bool
en andere waardetypen. Het type is met name bool
uniek en gescheiden van de integrale typen, een bool
waarde kan niet worden gebruikt in plaats van een integrale waarde en omgekeerd.
Opmerking: in de C- en C++-talen kan een nul-integraal- of drijvendekommawaarde, of een null-aanwijzer worden geconverteerd naar de Booleaanse waarde
false
, en een niet-nul-integraal- of drijvendekommawaarde, of een niet-null-aanwijzer kan worden geconverteerd naar de Booleaanse waardetrue
. In C# worden dergelijke conversies uitgevoerd door expliciet een integrale of drijvende-kommawaarde te vergelijken met nul of door een objectverwijzing expliciet tenull
vergelijken met . eindnotitie
8.3.10 Opsommingstypen
Een opsommingstype is een uniek type met benoemde constanten. Elk opsommingstype heeft een onderliggend type, dat moet zijnbyte
, , sbyte
, ushort
short
, , int
, , uint
of long
ulong
. De set waarden van het opsommingstype is hetzelfde als de set waarden van het onderliggende type. Waarden van het opsommingstype zijn niet beperkt tot de waarden van de benoemde constanten. Opsommingstypen worden gedefinieerd via opsommingsdeclaraties (§19.2).
8.3.11 Tuple-typen
Een tupletype vertegenwoordigt een geordende, vaste lengte van waarden met optionele namen en afzonderlijke typen. Het aantal elementen in een tupletype wordt de ariteit ervan genoemd. Een tupletype wordt geschreven (T1 I1, ..., Tn In)
met n ≥ 2, waarbij de id's I1...In
optionele tuple-elementnamen zijn.
Deze syntaxis is een afkorting van een type dat is samengesteld met de typen T1...Tn
waaruit System.ValueTuple<...>
wordt samengesteld. Dit moet een set algemene structtypen zijn die rechtstreeks tupletypen kunnen uitdrukken van elke arple tussen twee en zeven inclusief.
Er hoeft geen declaratie te bestaan System.ValueTuple<...>
die rechtstreeks overeenkomt met de ariteit van een tupletype met een corresponderend aantal typeparameters. In plaats daarvan worden tuples met een arity groter dan zeven weergegeven met een algemeen structtype System.ValueTuple<T1, ..., T7, TRest>
dat naast tuple-elementen een Rest
veld bevat dat een geneste waarde van de resterende elementen bevat, met behulp van een ander System.ValueTuple<...>
type. Dergelijke nesting kan op verschillende manieren worden waargenomen, bijvoorbeeld via de aanwezigheid van een Rest
veld. Als er slechts één extra veld is vereist, wordt het algemene structtype System.ValueTuple<T1>
gebruikt. Dit type wordt niet beschouwd als een tupletype op zichzelf. Wanneer meer dan zeven extra velden vereist zijn, System.ValueTuple<T1, ..., T7, TRest>
wordt recursief gebruikt.
Elementnamen binnen een tupeltype zijn verschillend. Een tuple-elementnaam van het formulier ItemX
, waarbij X
een reeks niet-geïnitieerde0
decimalen die de positie van een tuple-element kunnen vertegenwoordigen, alleen is toegestaan op de positie die wordt aangeduid door X
.
De optionele elementnamen worden niet weergegeven in de ValueTuple<...>
typen en worden niet opgeslagen in de runtimeweergave van een tuple-waarde. Identiteitsconversies (§10.2.2) bestaan tussen tuples met identiteits-converteerbare reeksen elementen.
De new
operator §12.8.17.2 kan niet worden toegepast met de tuple-typesyntaxis new (T1, ..., Tn)
. Tuple-waarden kunnen worden gemaakt op basis van tuple-expressies (§12.8.6), of door de new
operator rechtstreeks toe te passen op een type dat is samengesteld van ValueTuple<...>
.
Tuple-elementen zijn openbare velden met de namen Item1
, Item2
enzovoort, en kunnen worden geopend via een lidtoegang tot een tuple-waarde (§12.8.7. Als het tuple-type bovendien een naam voor een bepaald element heeft, kan die naam worden gebruikt om toegang te krijgen tot het betreffende element.
Opmerking: Zelfs wanneer grote tuples worden weergegeven met geneste
System.ValueTuple<...>
waarden, kan elk tuple-element nog steeds rechtstreeks worden geopend met de naam die overeenkomt met deItem...
positie ervan. eindnotitie
Voorbeeld: Gegeven de volgende voorbeelden:
(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}");
De tupeltypen voor
pair1
,pair2
enpair3
zijn allemaal geldig, met namen voor nee, sommige of alle tuple-typeelementen.Het tuple-type
pair4
is geldig omdat de namenItem1
enItem2
hun posities overeenkomen, terwijl het tuple-type voorpair5
niet is toegestaan, omdat de namenItem2
enItem123
niet.De declaraties voor
pair6
enpair7
demonstreren dat tuple-typen uitwisselbaar zijn met samengestelde typen van het formulierValueTuple<...>
en dat denew
operator is toegestaan met de laatste syntaxis.De laatste regel laat zien dat tuple-elementen kunnen worden geopend op basis van de naam die overeenkomt met hun
Item
positie, evenals de bijbehorende tuple-elementnaam, indien aanwezig in het type. eindvoorbeeld
8.3.12 Typen null-waarden
Een type null-waarde kan alle waarden van het onderliggende type vertegenwoordigen plus een extra null-waarde. Er wordt een type null-waarde geschreven T?
, waarbij T
het onderliggende type is. Deze syntaxis is afkorting voor System.Nullable<T>
en de twee formulieren kunnen door elkaar worden gebruikt.
Omgekeerd is een niet-null-waardetype een ander waardetype dan System.Nullable<T>
en de verkorte T?
waarde (voor elke T
waarde), plus een typeparameter die is beperkt tot een niet-null-waardetype (dat wil gezegd een typeparameter met een waardetypebeperking (§15.2.5)). Het System.Nullable<T>
type geeft de waardetypebeperking op T
, wat betekent dat het onderliggende type van een type null-waarde elk niet-null-waardetype kan zijn. Het onderliggende type van een null-waardetype kan geen type null-waarde of een verwijzingstype zijn. Is bijvoorbeeld int??
een ongeldig type. Null-referentietypen worden behandeld in §8.9.
Een exemplaar van een type null-waarde T?
heeft twee openbare eigenschappen met het kenmerk Alleen-lezen:
- Een
HasValue
eigenschap van het typebool
- Een
Value
eigenschap van het typeT
Een exemplaar waarvan HasValue
wordt true
gezegd dat deze niet null is. Een niet-null-exemplaar bevat een bekende waarde en Value
retourneert die waarde.
Een exemplaar waarvan HasValue
wordt false
gezegd dat deze null is. Een null-exemplaar heeft een niet-gedefinieerde waarde. Als u probeert de Value
waarde van een null-exemplaar te lezen, wordt er een System.InvalidOperationException
fout gegenereerd. Het proces voor het openen van de eigenschap Waarde van een null-exemplaar wordt ook wel uitgepakt.
Naast de standaardconstructor heeft elk type null-waarde T?
een openbare constructor met één parameter van het type T
. Gegeven een waarde x
van het type T
, een constructor die het formulier aanroept
new T?(x)
maakt een niet-null-exemplaar waarvan T?
de Value
eigenschap is x
. Het proces voor het maken van een niet-null-exemplaar van een null-waardetype voor een bepaalde waarde wordt wrapping genoemd.
Impliciete conversies zijn beschikbaar van letterlijk null
tot T?
(§10.2.7) en van T
tot T?
(§10.2.6).
Het type T?
null-waarde implementeert geen interfaces (§18). Dit betekent met name dat er geen interface wordt geïmplementeerd die door het onderliggende type T
wordt gebruikt.
8.3.13 Boksen en uitpakken
Het concept van boksen en uitpakken biedt een brug tussen value_type en reference_type door elke waarde van een value_type te converteren naar en van type object
. Boksen en uitpakken maakt een uniforme weergave van het typesysteem mogelijk waarbij een waarde van elk type uiteindelijk kan worden behandeld als een object
.
Boksen wordt uitgebreid beschreven in §10.2.9 en het uitpakken wordt beschreven in §10.3.7.
8.4 Samengestelde typen
8.4.1 Algemeen
Een algemene typedeclaratie geeft op zichzelf een niet-afhankelijk algemeen type aan dat wordt gebruikt als een 'blauwdruk' om veel verschillende typen te vormen, door typeargumenten toe te passen. De typeargumenten worden geschreven tussen punthaken (<
en >
) direct na de naam van het algemene type. Een type dat ten minste één typeargument bevat, wordt een samengesteld type genoemd. Een samengesteld type kan op de meeste plaatsen in de taal worden gebruikt waarin een typenaam kan worden weergegeven. Een niet-afhankelijk algemeen type kan alleen worden gebruikt binnen een typeof_expression (§12.8.18).
Samengestelde typen kunnen ook worden gebruikt in expressies als eenvoudige namen (§12.8.4) of bij toegang tot een lid (§12.8.7).
Wanneer een namespace_or_type_name wordt geëvalueerd, worden alleen algemene typen met het juiste aantal typeparameters overwogen. Het is dus mogelijk om dezelfde id te gebruiken om verschillende typen te identificeren, zolang de typen verschillende getallen van typeparameters hebben. Dit is handig bij het combineren van algemene en niet-generieke klassen in hetzelfde programma.
Voorbeeld:
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 } }
eindvoorbeeld
De gedetailleerde regels voor het opzoeken van namen in de namespace_or_type_name producties worden beschreven in §7.8. De oplossing van dubbelzinnigheden in deze producties wordt beschreven in §6.2.5. Een type_name kan een samengesteld type identificeren, ook al worden er geen typeparameters rechtstreeks opgegeven. Dit kan gebeuren wanneer een type is genest binnen een algemene class
declaratie en het exemplaartype van de declaratie impliciet wordt gebruikt voor het opzoeken van namen (§15.3.9.7).
Voorbeeld:
class Outer<T> { public class Inner {...} public Inner i; // Type of i is Outer<T>.Inner }
eindvoorbeeld
Een niet-geconstrueerd type mag niet worden gebruikt als een unmanaged_type (§8.8).
8.4.2 Typeargumenten
Elk argument in een lijst met typeargumenten is gewoon een type.
type_argument_list
: '<' type_arguments '>'
;
type_arguments
: type_argument (',' type_argument)*
;
type_argument
: type
| type_parameter nullable_type_annotation?
;
Elk typeargument moet voldoen aan eventuele beperkingen voor de overeenkomstige typeparameter (§15.2.5). Een verwijzingstypeargument waarvan de null-waarde niet overeenkomt met de null-waarde van de typeparameter voldoet aan de beperking; er kan echter een waarschuwing worden afgegeven.
8.4.3 Open en gesloten typen
Alle typen kunnen worden geclassificeerd als open typen of gesloten typen. Een open type is een type dat typeparameters omvat. Specifieke opdrachten:
- Een typeparameter definieert een open type.
- Een matrixtype is een open type als en alleen als het elementtype een open type is.
- Een samengesteld type is een open type als en alleen als een of meer van de bijbehorende typeargumenten een open type is. Een geconstrueerd geneste type is een open type als en alleen als een of meer van de bijbehorende typeargumenten of de typeargumenten van het betreffende type(n) een open type is.
Een gesloten type is een type dat geen open type is.
Tijdens runtime wordt alle code binnen een algemene typedeclaratie uitgevoerd in de context van een gesloten samengesteld type dat is gemaakt door typeargumenten toe te passen op de algemene declaratie. Elke typeparameter binnen het algemene type is gebonden aan een bepaald runtimetype. De runtimeverwerking van alle instructies en expressies vindt altijd plaats met gesloten typen en geopende typen vinden alleen plaats tijdens de verwerking van compileertijd.
Twee gesloten geconstrueerde typen zijn de converteerbare identiteit (§10.2.2) als ze zijn samengesteld uit hetzelfde niet-afhankelijke algemene type en er bestaat een identiteitsconversie tussen elk van de bijbehorende typeargumenten. De bijbehorende typeargumenten kunnen zelf gesloten samengestelde typen of tuples zijn die identiteit converteerbaar zijn. Gesloten samengestelde typen die identiteit converteerbaar zijn, delen één set statische variabelen. Anders heeft elk gesloten samengesteld type een eigen set statische variabelen. Omdat er tijdens runtime geen open type bestaat, zijn er geen statische variabelen gekoppeld aan een open type.
8.4.4 Gebonden en niet-afhankelijke typen
Het niet-afhankelijke type verwijst naar een niet-algemeen type of een niet-afhankelijk algemeen type. Het afhankelijke type term verwijst naar een niet-algemeen type of een samengesteld type.
Een niet-afhankelijk type verwijst naar de entiteit die is gedeclareerd door een typedeclaratie. Een niet-afhankelijk algemeen type is zelf geen type en kan niet worden gebruikt als het type variabele, argument of retourwaarde, of als basistype. De enige constructie waarin naar een niet-afhankelijk algemeen type kan worden verwezen, is de typeof
expressie (§12.8.18).
8.4.5 Bevredigende beperkingen
Wanneer naar een samengesteld type of algemene methode wordt verwezen, worden de opgegeven typeargumenten gecontroleerd op basis van de typeparameterbeperkingen die zijn gedeclareerd voor het algemene type of de methode (§15.2.5). Voor elke where
component wordt het typeargument A
dat overeenkomt met de parameter met het benoemde type als volgt gecontroleerd op elke beperking:
- Als de beperking een
class
type, een interfacetype of een typeparameter is, kunt uC
die beperking voorstellen met de opgegeven typeargumenten die worden vervangen door eventuele typeparameters die in de beperking worden weergegeven. Om aan de beperking te voldoen, is dit het geval dat het typeA
converteerbaar is om op een van de volgende manieren te typenC
: - Als de beperking de beperking van het referentietype (
class
) is, moet het typeA
voldoen aan een van de volgende:A
is een interfacetype, klassetype, gemachtigde type, matrixtype of het dynamische type.
Opmerking:
System.ValueType
enSystem.Enum
zijn referentietypen die voldoen aan deze beperking. eindnotitieA
is een typeparameter die bekend staat als referentietype (§8.2).
- Als de beperking de waardetypebeperking (
struct
) is, moet het typeA
voldoen aan een van de volgende:A
is eenstruct
type ofenum
type, maar geen null-waardetype.
Opmerking:
System.ValueType
enSystem.Enum
zijn verwijzingstypen die niet voldoen aan deze beperking. eindnotitieA
is een typeparameter met de waardetypebeperking (§15.2.5).
- Als de beperking de constructorbeperking
new()
is, mag het typeA
niet zijnabstract
en moet deze een openbare constructor zonder parameters hebben. Dit wordt voldaan als een van de volgende waar is:A
is een waardetype, omdat alle waardetypen een openbare standaardconstructor hebben (§8.3.3).A
is een typeparameter met de constructorbeperking (§15.2.5).A
is een typeparameter met de waardetypebeperking (§15.2.5).A
is eenclass
die niet abstract is en een expliciet gedeclareerde openbare constructor zonder parameters bevat.A
is nietabstract
en heeft een standaardconstructor (§15.11.5).
Er treedt een compilatiefout op als aan een of meer beperkingen van een typeparameter niet wordt voldaan door de opgegeven typeargumenten.
Omdat typeparameters niet worden overgenomen, worden beperkingen ook nooit overgenomen.
Voorbeeld: In het volgende
D
moet u de beperking voor de parameter van het typeT
opgeven, zodatT
deze voldoet aan de beperking die door de basisclass
B<T>
wordt opgelegd. Daarentegenclass
E
hoeft u geen beperking op te geven, omdatList<T>
dezeIEnumerable
wordt geïmplementeerd voor eenT
.class B<T> where T: IEnumerable {...} class D<T> : B<T> where T: IEnumerable {...} class E<T> : B<List<T>> {...}
eindvoorbeeld
8.5 Typeparameters
Een typeparameter is een id die een waardetype of verwijzingstype aangeeft waaraan de parameter tijdens runtime is gebonden.
type_parameter
: identifier
;
Omdat een typeparameter kan worden geïnstantieerd met veel verschillende typeargumenten, hebben typeparameters iets andere bewerkingen en beperkingen dan andere typen.
Opmerking: dit zijn onder andere:
- Een typeparameter kan niet rechtstreeks worden gebruikt om een basisklasse (§15.2.4.2) of interface (§18.2.4) te declareren.
- De regels voor het opzoeken van leden op typeparameters zijn afhankelijk van de beperkingen, indien van toepassing, op de typeparameter. Ze worden beschreven in §12.5.
- De beschikbare conversies voor een typeparameter zijn afhankelijk van de beperkingen, indien van toepassing op de typeparameter. Ze worden beschreven in §10.2.12 en §10.3.8.
- De letterlijke waarde
null
kan niet worden geconverteerd naar een typeparameter, behalve als de parameter van het type een verwijzingstype is (§10.2.12). In plaats daarvan kan echter een standaardexpressie (§12.8.21) worden gebruikt. Bovendien kan een waarde met een typeparameter worden vergeleken met null met behulp==
van en!=
(§12.12.7), tenzij de typeparameter de waardetypebeperking heeft.- Een
new
expressie (§12.8.17.2) kan alleen worden gebruikt met een typeparameter als de typeparameter wordt beperkt door een constructor_constraint of de waardetypebeperking (§15.2.5).- Een typeparameter kan nergens binnen een kenmerk worden gebruikt.
- Een typeparameter kan niet worden gebruikt in een lidtoegang (§12.8.7) of typenaam (§7.8) om een statisch lid of geneste type te identificeren.
- Een typeparameter kan niet worden gebruikt als een unmanaged_type (§8.8).
eindnotitie
Als type zijn typeparameters uitsluitend een compileertijdconstructie. Bij runtime is elke typeparameter gebonden aan een runtimetype dat is opgegeven door een typeargument op te geven aan de algemene typedeclaratie. Het type variabele dat is gedeclareerd met een typeparameter, is dus tijdens runtime een gesloten constructed type §8.4.3. De uitvoering van alle instructies en expressies die betrekking hebben op typeparameters, gebruikt het type dat is opgegeven als het typeargument voor die parameter.
8.6 Expressiestructuurtypen
Met expressiestructuren kunnen lambda-expressies worden weergegeven als gegevensstructuren in plaats van uitvoerbare code. Expressiestructuren zijn waarden van expressiestructuurtypen van het formulierSystem.Linq.Expressions.Expression<TDelegate>
, waarbij TDelegate
elk gemachtigde type is. Voor de rest van deze specificatie worden deze typen aangeduid met de afkorting Expression<TDelegate>
.
Als er een conversie bestaat van een lambda-expressie naar een gemachtigde, D
bestaat er ook een conversie naar het type expressiestructuur Expression<TDelegate>
. Terwijl de conversie van een lambda-expressie naar een gemachtigde een gemachtigde genereert die verwijst naar uitvoerbare code voor de lambda-expressie, wordt door conversie naar een expressiestructuurtype een expressiestructuurweergave van de lambda-expressie gemaakt. Meer informatie over deze conversie vindt u in §10.7.3.
Voorbeeld: Het volgende programma vertegenwoordigt een lambda-expressie, zowel als uitvoerbare code als als een expressiestructuur. Omdat er een conversie bestaat naar
Func<int,int>
, bestaat er ook een conversie naarExpression<Func<int,int>>
:Func<int,int> del = x => x + 1; // Code Expression<Func<int,int>> exp = x => x + 1; // Data
Na deze toewijzingen verwijst de gedelegeerde
del
naar een methode die wordt geretourneerdx + 1
en de expressiestructuur verwijst naar een gegevensstructuur die de expressiex => x + 1
beschrijft.eindvoorbeeld
Expression<TDelegate>
biedt een instantiemethode Compile
die een gemachtigde van het type TDelegate
produceert:
Func<int,int> del2 = exp.Compile();
Als u deze gemachtigde aanroept, wordt de code die wordt vertegenwoordigd door de expressiestructuur, uitgevoerd. Gezien de bovenstaande definities en del
del2
gelijkwaardig zijn, hebben de volgende twee instructies hetzelfde effect:
int i1 = del(1);
int i2 = del2(1);
Na het uitvoeren van deze code, i1
en i2
beide hebben de waarde 2
.
Het API-oppervlak dat wordt geleverd door Expression<TDelegate>
implementatie is gedefinieerd buiten de vereiste voor een Compile
methode die hierboven wordt beschreven.
Opmerking: Hoewel de details van de API voor expressiestructuren zijn gedefinieerd door de implementatie, wordt verwacht dat een implementatie het volgende doet:
- Code inschakelen om de structuur van een expressiestructuur te inspecteren en erop te reageren die is gemaakt als gevolg van een conversie van een lambda-expressie
- Expressiestructuren inschakelen om programmatisch te worden gemaakt in gebruikerscode
eindnotitie
8.7 Het dynamische type
Het type dynamic
maakt gebruik van dynamische binding, zoals beschreven in §12.3.2, in plaats van statische binding die door alle andere typen wordt gebruikt.
Het type dynamic
wordt als identiek beschouwd, object
behalve in de volgende opzichten:
- Bewerkingen voor expressies van het type
dynamic
kunnen dynamisch worden gebonden (§12.3.3). - Typedeductie (§12.6.3) geeft de voorkeur
dynamic
object
aan als beide kandidaten zijn. dynamic
kan niet worden gebruikt als- het type in een object_creation_expression (§12.8.17.2)
- een class_base (§15.2.4)
- een predefined_type in een member_access (§12.8.7.1)
- de operand van de
typeof
operator - een kenmerkargument
- een beperking
- een type extensiemethode
- een deel van een typeargument in struct_interfaces (§16.2.5) of interface_type_list (§15.2.4.1).
Vanwege deze gelijkwaardigheid geldt het volgende:
- Er is een impliciete identiteitsconversie
- tussen
object
endynamic
- tussen samengestelde typen die hetzelfde zijn bij het vervangen
dynamic
doorobject
- tussen tupeltypen die hetzelfde zijn bij het vervangen door
dynamic
object
- tussen
- Impliciete en expliciete conversies naar en van
object
toepassing op en vandynamic
. - Handtekeningen die hetzelfde zijn bij het vervangen
dynamic
doorobject
, worden beschouwd als dezelfde handtekening. - Het type
dynamic
is niet te onderscheiden van het typeobject
tijdens runtime. - Een expressie van het type
dynamic
wordt een dynamische expressie genoemd.
8.8 Niet-beheerde typen
unmanaged_type
: value_type
| pointer_type // unsafe code support
;
Een unmanaged_type is een type dat geen reference_type, een type_parameter of een samengesteld type is en geen exemplaarvelden bevat waarvan het type geen unmanaged_type is. Met andere woorden, een unmanaged_type is een van de volgende:
sbyte
, ,short
byte
,ushort
, ,int
, , ,uint
,long
,ulong
char
,float
,double
, ,decimal
ofbool
.- Elke enum_type.
- Door de gebruiker gedefinieerde struct_type die geen geconstrueerd type is en alleen exemplaarvelden van unmanaged_type bevat.
- In onveilige code (§23.2), alle pointer_type (§23.3).
8.9 Referentietypen en null-functionaliteit
8.9.1 Algemeen
Een null-verwijzingstype wordt aangeduid door een nullable_type_annotation (?
) toe te voegen aan een niet-null-verwijzingstype. Er is geen semantisch verschil tussen een niet-null-verwijzingstype en het bijbehorende null-type. Beide kunnen een verwijzing naar een object zijn of null
. De aanwezigheid of afwezigheid van de nullable_type_annotation geeft aan of een expressie is bedoeld om null-waarden toe te laten of niet. Een compiler kan diagnostische gegevens bieden wanneer een expressie niet wordt gebruikt volgens die intentie. De null-status van een expressie wordt gedefinieerd in §8.9.5. Er bestaat een identiteitsconversie tussen een null-referentietype en het bijbehorende niet-null-referentietype (§10.2.2).
Er zijn twee soorten null-mogelijkheden voor referentietypen:
- nullable: er kan een nullable-reference-type worden toegewezen
null
. De standaard null-status is misschien null. - niet-nullable: er mag geen waarde worden toegewezen aan een
null
niet-null-verwijzing. De standaard null-status is not-null.
Opmerking: De typen
R
enR?
worden vertegenwoordigd door hetzelfde onderliggende type,R
. Een variabele van dat onderliggende type kan een verwijzing naar een object bevatten of de waardenull
zijn, die 'geen verwijzing' aangeeft. eindnotitie
Het syntactische onderscheid tussen een null-verwijzingstype en het bijbehorende niet-null-referentietype stelt een compiler in staat om diagnostische gegevens te genereren. Een compiler moet de nullable_type_annotation toestaan zoals gedefinieerd in §8.2.1. De diagnostische gegevens moeten worden beperkt tot waarschuwingen. Noch de aanwezigheid of afwezigheid van null-aantekeningen, noch de status van de null-context kan de compilatietijd of runtime van een programma wijzigen, met uitzondering van wijzigingen in diagnostische berichten die tijdens het compileren worden gegenereerd.
8.9.2 Niet-nullable verwijzingstypen
Een niet-null-verwijzingstype is een verwijzingstype van het formulier T
, waarbij T
de naam van het type is. De standaard null-status van een variabele die niet null kan worden gebruikt, is niet null. Er kunnen waarschuwingen worden gegenereerd wanneer een expressie die misschien null is, wordt gebruikt waarbij een niet-null-waarde vereist is.
8.9.3 Nullable reference types
Een verwijzingstype van het formulier T?
(zoals string?
) is een null-verwijzingstype. De standaard null-status van een variabele die null kan worden gebruikt, is mogelijk null. De aantekening ?
geeft de intentie aan dat variabelen van dit type nullable zijn. De compiler kan deze intenties herkennen om waarschuwingen te geven. Wanneer de context voor null-aantekening is uitgeschakeld, kan met deze aantekening een waarschuwing worden gegenereerd.
8.9.4 Nullable context
8.9.4.1 Algemeen
Elke regel broncode heeft een context die null kan worden gebruikt. De aantekeningen en waarschuwingen voor de nullable context control nullable annotaations (§8.9.4.3) en nullable waarschuwingen (§8.9.4.4), respectievelijk. Elke vlag kan worden ingeschakeld of uitgeschakeld. De compiler kan statische stroomanalyse gebruiken om de null-status van een referentievariabele te bepalen. De null-status van een verwijzingsvariabele (§8.9.5) is niet null, misschien null of misschien standaard.
De context waarvoor null kan worden gebruikt, kan worden opgegeven in broncode via null-instructies (§6.5.9) en/of via een implementatiespecifiek mechanisme buiten de broncode. Als beide benaderingen worden gebruikt, vervangen null-instructies de instellingen die zijn gemaakt via een extern mechanisme.
De standaardstatus van de null-context is gedefinieerd door de implementatie.
In deze specificatie wordt ervan uitgegaan dat alle C#-code die geen null-instructies bevat, of waarvan geen instructie wordt gemaakt met betrekking tot de huidige status van de null-bare context, is gecompileerd met behulp van een nullable context waarbij zowel aantekeningen als waarschuwingen zijn ingeschakeld.
Opmerking: Een null-context waarbij beide vlaggen zijn uitgeschakeld, komt overeen met het vorige standaardgedrag voor referentietypen. eindnotitie
8.9.4.2 Nullable uitschakelen
Wanneer zowel de waarschuwingsvlag als de aantekeningen zijn uitgeschakeld, wordt de null-context uitgeschakeld.
Wanneer de null-context is uitgeschakeld:
- Er wordt geen waarschuwing gegenereerd wanneer een variabele van een niet-geannoteerd verwijzingstype wordt geïnitialiseerd met of een waarde van,
null
. - Er wordt geen waarschuwing gegenereerd wanneer een variabele van een verwijzingstype die mogelijk de null-waarde heeft.
- Voor elk verwijzingstype
T
genereert de aantekening?
inT?
een bericht en is het typeT?
hetzelfde alsT
. - Voor elke typeparameterbeperking
where T : C?
genereert de aantekening?
inC?
een bericht en is het typeC?
hetzelfde alsC
. - Voor elke typeparameterbeperking
where T : U?
genereert de aantekening?
inU?
een bericht en is het typeU?
hetzelfde alsU
. - Met de algemene beperking
class?
wordt een waarschuwingsbericht gegenereerd. De typeparameter moet een verwijzingstype zijn.Opmerking: dit bericht wordt gekenmerkt als 'informatief' in plaats van 'waarschuwing', zodat het niet wordt verward met de status van de null-waarschuwingsinstelling, die niet gerelateerd is. eindnotitie
- De operator
!
null-forgiving (§12.8.9) heeft geen effect.
Voorbeeld:
#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
eindvoorbeeld
8.9.4.3 Nullable annotaties
Wanneer de waarschuwingsvlag is uitgeschakeld en de vlag aantekeningen is ingeschakeld, is de null-context aantekeningen.
Wanneer de null-context aantekeningen bevat:
- Voor elk verwijzingstype
T
geeft de aantekening?
aanT?
datT?
een null-type, terwijl de niet-geannoteerdeT
niet-null-waarde is. - Er worden geen diagnostische waarschuwingen gegenereerd die betrekking hebben op null-baarheid.
- De operator
!
null-forgiving (§12.8.9) kan de geanalyseerde null-status van de operand wijzigen en welke diagnostische waarschuwingen voor compileertijd worden geproduceerd.
Voorbeeld:
#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
eindvoorbeeld
8.9.4.4 Nullable waarschuwingen
Wanneer de waarschuwingsvlag is ingeschakeld en de vlag aantekeningen is uitgeschakeld, is de null-context waarschuwingen.
Wanneer de null-context waarschuwingen bevat, kan een compiler in de volgende gevallen diagnostische gegevens genereren:
- Een verwijzingsvariabele die is vastgesteld om null te zijn, wordt gededucteerd.
- Een verwijzingsvariabele van een niet-null-type wordt toegewezen aan een expressie die mogelijk null is.
- Het
?
wordt gebruikt om een nullable verwijzingstype te noteren. - De operator
!
null-forgiving (§12.8.9) wordt gebruikt om de null-status van de operand in te stellen op niet null.
Voorbeeld:
#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
eindvoorbeeld
8.9.4.5 Nullable inschakelen
Wanneer zowel de waarschuwingsvlag als de vlag voor aantekeningen zijn ingeschakeld, is de context waarop null kan worden uitgevoerd ingeschakeld.
Wanneer de null-context is ingeschakeld:
- Voor elk verwijzingstype
T
maaktT?
de aantekeningT?
?
een null-type, terwijl de niet-geannoteerdeT
niet-null-waarde is. - De compiler kan statische stroomanalyse gebruiken om de null-status van een referentievariabele te bepalen. Wanneer null-waarschuwingen zijn ingeschakeld, is de null-status van een verwijzingsvariabele (§8.9.5) niet null, misschien null of misschien standaard en
- De operator
!
null-forgiving (§12.8.9) stelt de null-status van de operand in op niet null. - De compiler kan een waarschuwing geven als de null-waarde van een typeparameter niet overeenkomt met de null-waarde van het bijbehorende typeargument.
8.9.5 Nullabiliteiten en null-statussen
Een compiler is niet vereist om statische analyses uit te voeren en is ook niet vereist voor het genereren van diagnostische waarschuwingen met betrekking tot null-baarheid.
De rest van deze subclause is voorwaardelijk normatief.
Een compiler die diagnostische waarschuwingen genereert, voldoet aan deze regels.
Elke expressie heeft een van drie null-statussen:
- misschien null: de waarde van de expressie kan null opleveren.
- misschien standaard: de waarde van de expressie kan de standaardwaarde voor dat type evalueren.
- niet null: de waarde van de expressie is niet null.
De standaard null-status van een expressie wordt bepaald door het type en de status van de aantekeningenvlag wanneer deze wordt gedeclareerd:
- De standaard null-status van een null-verwijzingstype is:
- Misschien null wanneer de declaratie tekst bevat waarin de vlag aantekeningen is ingeschakeld.
- Niet null wanneer de declaratie tekst bevat waarin de vlag aantekeningen is uitgeschakeld.
- De standaard null-status van een niet-null-verwijzingstype is niet null.
Opmerking: De standaardstatus wordt mogelijk gebruikt met niet-gekoppelde typeparameters wanneer het type een niet-null-type is, zoals
string
en de expressiedefault(T)
de null-waarde is. Omdat null zich niet in het domein bevindt voor het niet-null-type, is de status mogelijk standaard. eindnotitie
Er kan een diagnose worden gemaakt wanneer een variabele (§9.2.1) van een niet-null-verwijzingstype wordt geïnitialiseerd of toegewezen aan een expressie die mogelijk null is wanneer die variabele wordt gedeclareerd in tekst waarin de vlag voor aantekeningen is ingeschakeld.
Voorbeeld: Houd rekening met de volgende methode waarbij een parameter nullbaar is en die waarde is toegewezen aan een niet-null-type:
#nullable enable public class C { public void M(string? p) { // Assignment of maybe null value to non-nullable variable string s = p; } }
De compiler kan een waarschuwing geven waarbij de parameter die null kan zijn toegewezen aan een variabele die niet null mag zijn. Als de parameter null-gecontroleerd is vóór de toewijzing, kan de compiler deze gebruiken in de null-statusanalyse en geen waarschuwing geven:
#nullable enable public class C { public void M(string? p) { if (p != null) { string s = p; // Use s } } }
eindvoorbeeld
De compiler kan de null-status van een variabele bijwerken als onderdeel van de analyse.
Voorbeeld: De compiler kan ervoor kiezen om de status bij te werken op basis van eventuele instructies in uw programma:
#nullable enable public void M(string? p) { // p is maybe-null int length = p.Length; // p is not null. string s = p; if (s != null) { int l2 = s.Length; } // s is maybe null int l3 = s.Length; }
In het vorige voorbeeld kan de compiler besluiten dat na de instructie
int length = p.Length;
de null-statusp
niet null is. Als het null was, zou die instructie eenNullReferenceException
. Dit is vergelijkbaar met het gedrag als de code is voorafgegaan doorif (p == null) throw NullReferenceException();
, behalve dat de code zoals geschreven een waarschuwing kan opleveren. Dit is bedoeld om te waarschuwen dat een uitzondering impliciet kan worden gegenereerd.
Verderop in de methode controleert de code die s
geen null-verwijzing is. De null-status kan s
worden gewijzigd in mogelijk null nadat het blok met null-controle is gesloten. De compiler kan afleiden dat s
dit null is omdat de code is geschreven om ervan uit te gaan dat deze mogelijk null is. Wanneer de code een null-controle bevat, kan de compiler in het algemeen afleiden dat de waarde null is geweest.eindvoorbeeld
Voorbeeld: De compiler kan een eigenschap (§15.7) behandelen als een variabele met de status of als onafhankelijke get- en set accessors (§15.7.3). Met andere woorden, een compiler kan kiezen of schrijven naar een eigenschap de null-status van het lezen van de eigenschap wijzigt of als het lezen van een eigenschap de null-status van die eigenschap wijzigt.
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; } } }
In het vorige voorbeeld is het backing-veld voor het
DisappearingProperty
veld ingesteld op null wanneer het wordt gelezen. Een compiler kan er echter van uitgaan dat het lezen van een eigenschap de null-status van die expressie niet wijzigt. eindvoorbeeld
Einde van voorwaardelijk normatieve tekst
ECMA C# draft specification