Delen via


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 objectte 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 nullaangeeft.

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, en long, ulongde standaardwaarde is 0.
    • Voor charis de standaardwaarde '\x0000'.
    • Voor floatis de standaardwaarde 0.0f.
    • Voor doubleis de standaardwaarde 0.0d.
    • Voor decimal, de standaardwaarde is (dat wil 0m gezegd, waarde nul met schaal 0).
    • Voor boolis de standaardwaarde false.
    • Voor een enum_type Ewordt 0de standaardwaarde geconverteerd naar het type E.
  • 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 de Value eigenschap van een dergelijke waarde probeert te lezen, wordt een uitzondering van het type System.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 ien j 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 en System.Int32 de leden overgenomen van System.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 type int en 'a' is een letterlijke van het type char. 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, , shortbyte, ushort, int, uint, , longen ulongchar. De integrale typen hebben de volgende grootten en bereiken van waarden:

  • Het sbyte type vertegenwoordigt ondertekende 8-bits gehele getallen met waarden van -128 tot 127en met.
  • Het byte type vertegenwoordigt niet-ondertekende 8-bits gehele getallen met waarden van 0 tot 255en met.
  • Het short type vertegenwoordigt ondertekende 16-bits gehele getallen met waarden van -32768 tot 32767en met.
  • Het ushort type vertegenwoordigt niet-ondertekende 16-bits gehele getallen met waarden van 0 tot 65535en met.
  • Het int type vertegenwoordigt ondertekende 32-bits gehele getallen met waarden van -2147483648 tot 2147483647en met.
  • Het uint type vertegenwoordigt niet-ondertekende 32-bits gehele getallen met waarden van 0 tot 4294967295en met.
  • Het long type vertegenwoordigt ondertekende 64-bits gehele getallen met waarden van -9223372036854775808 tot 9223372036854775807en met.
  • Het ulong type vertegenwoordigt niet-ondertekende 64-bits gehele getallen met waarden van 0 tot 18446744073709551615en met.
  • Het char type vertegenwoordigt niet-ondertekende 16-bits gehele getallen met waarden van 0 tot 65535en met. De set mogelijke waarden voor het char type komt overeen met de Unicode-tekenset.

    Opmerking: Hoewel char deze dezelfde weergave heeft als ushort, 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 de byte en ushort typen bereiken met waarden die volledig kunnen worden vertegenwoordigd met behulp van het char type, impliciete conversies van sbyte, byte of ushort om niet te char 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 voor double0 <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 formulier x * y / z, waarbij de vermenigvuldiging echter een resultaat produceert dat zich buiten het double bereik bevindt, maar de volgende deling het tijdelijke resultaat weer in het double 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 ≤ eEmax, 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 decimals met een absolute waarde kleiner dan 1.0m, is de waarde exact tot ten minste de 28e decimale plaats. Voor decimals 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 waarde true. In C# worden dergelijke conversies uitgevoerd door expliciet een integrale of drijvende-kommawaarde te vergelijken met nul of door een objectverwijzing expliciet te nullvergelijken 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, ushortshort, , int, , uintof 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, Item2enzovoort, 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 de Item... 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, pair2en pair3 zijn allemaal geldig, met namen voor nee, sommige of alle tuple-typeelementen.

Het tuple-type pair4 is geldig omdat de namen Item1 en Item2 hun posities overeenkomen, terwijl het tuple-type voor pair5 niet is toegestaan, omdat de namen Item2 en Item123 niet.

De declaraties voor pair6 en pair7 demonstreren dat tuple-typen uitwisselbaar zijn met samengestelde typen van het formulier ValueTuple<...>en dat de new 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 Twaarde), 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 type bool
  • Een Value eigenschap van het type T

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 u C 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 type A converteerbaar is om op een van de volgende manieren te typen C :
    • Een identiteitsconversie (§10.2.2)
    • Een impliciete verwijzingsconversie (§10.2.8)
    • Een boksconversie (§10.2.9), mits dat type A een niet-null-waardetype is.
    • Een impliciete verwijzing, boksen of type parameterconversie van een typeparameter A naar C.
  • Als de beperking de beperking van het referentietype (class) is, moet het type A voldoen aan een van de volgende:
    • A is een interfacetype, klassetype, gemachtigde type, matrixtype of het dynamische type.

    Opmerking: System.ValueType en System.Enum zijn referentietypen die voldoen aan deze beperking. eindnotitie

    • A is een typeparameter die bekend staat als referentietype (§8.2).
  • Als de beperking de waardetypebeperking (struct) is, moet het type A voldoen aan een van de volgende:
    • A is een struct type of enum type, maar geen null-waardetype.

    Opmerking: System.ValueType en System.Enum zijn verwijzingstypen die niet voldoen aan deze beperking. eindnotitie

    • A is een typeparameter met de waardetypebeperking (§15.2.5).
  • Als de beperking de constructorbeperking new()is, mag het type A niet zijn abstract en moet deze een openbare constructor zonder parameters hebben. Dit wordt voldaan als een van de volgende waar is:
    • Ais 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 een class die niet abstract is en een expliciet gedeclareerde openbare constructor zonder parameters bevat.
    • A is niet abstract 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 type T opgeven, zodat T deze voldoet aan de beperking die door de basis class B<T>wordt opgelegd. Daarentegen class E hoeft u geen beperking op te geven, omdat List<T> deze IEnumerable wordt geïmplementeerd voor een T.

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, Dbestaat 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 naar Expression<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 geretourneerd x + 1en de expressiestructuur verwijst naar een gegevensstructuur die de expressie x => x + 1beschrijft.

eindvoorbeeld

Expression<TDelegate> biedt een instantiemethode Compile die een gemachtigde van het type TDelegateproduceert:

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 en dynamic
    • tussen samengestelde typen die hetzelfde zijn bij het vervangen dynamic door object
    • tussen tupeltypen die hetzelfde zijn bij het vervangen door dynamicobject
  • Impliciete en expliciete conversies naar en van object toepassing op en van dynamic.
  • Handtekeningen die hetzelfde zijn bij het vervangen dynamic door object , worden beschouwd als dezelfde handtekening.
  • Het type dynamic is niet te onderscheiden van het type object 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, , shortbyte, ushort, , int, , , uint, long, ulongchar, float, double, , decimalof bool.
  • 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 en R? worden vertegenwoordigd door hetzelfde onderliggende type, R. Een variabele van dat onderliggende type kan een verwijzing naar een object bevatten of de waarde nullzijn, 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 Tgenereert de aantekening ? in T? een bericht en is het type T? hetzelfde als T.
  • Voor elke typeparameterbeperking where T : C?genereert de aantekening ? in C? een bericht en is het type C? hetzelfde als C.
  • Voor elke typeparameterbeperking where T : U?genereert de aantekening ? in U? een bericht en is het type U? hetzelfde als U.
  • 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 Tgeeft de aantekening ? aan T? dat T? een null-type, terwijl de niet-geannoteerde T 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 Tmaakt T? de aantekening T? ? een null-type, terwijl de niet-geannoteerde T 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 expressie default(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-status p niet null is. Als het null was, zou die instructie een NullReferenceException. Dit is vergelijkbaar met het gedrag als de code is voorafgegaan door if (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