Delen via


15 klassen

15.1 Algemeen

Een klasse is een gegevensstructuur die gegevensleden (constanten en velden) kan bevatten, functieleden (methoden, eigenschappen, gebeurtenissen, indexeerfuncties, operators, instantieconstructors, finalizers en statische constructors) en geneste typen. Klassetypen ondersteunen overname, een mechanisme waarbij een afgeleide klasse een basisklasse kan uitbreiden en specialiseren.

15.2 Klassedeclaraties

15.2.1 Algemeen

Een class_declaration is een type_declaration (§14.7) die een nieuwe klasse declareert.

class_declaration
    : attributes? class_modifier* 'partial'? 'class' identifier
        type_parameter_list? class_base? type_parameter_constraints_clause*
        class_body ';'?
    ;

Een class_declaration bestaat uit een optionele set kenmerken (§22), gevolgd door een optionele set class_modifiers (§15.2.2), gevolgd door een optionele partial modifier (§15.2.7), gevolgd door het trefwoord class en een id die de klasse een naam geeft, gevolgd door een optionele type_parameter_list (§15.2.3), gevolgd door een optionele class_base specificatie (§15.2.4), gevolgd door een optionele reeks type_parameter_constraints_clause(§15.2.5), gevolgd door een class_body (§15.2.6), eventueel gevolgd door een puntkomma.

Een klasseverklaring levert geen type_parameter_constraints_clauses, tenzij zij ook een type_parameter_list levert.

Een klassedeclaratie die een type_parameter_list levert, is een algemene klassedeclaratie. Bovendien is elke klasse die is genest in een algemene klassedeclaratie of een algemene struct-declaratie zelf een algemene klassedeclaratie, omdat typeargumenten voor het betreffende type worden opgegeven om een samengesteld type te maken (§8.4).

15.2.2 Classificaties

15.2.2.1 Algemeen

Een class_declaration kan eventueel een reeks classificaties bevatten:

class_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'abstract'
    | 'sealed'
    | 'static'
    | unsafe_modifier   // unsafe code support
    ;

unsafe_modifier (§23.2) is alleen beschikbaar in onveilige code (§23).

Het is een compilatiefout voor dezelfde wijziging die meerdere keren in een klassedeclaratie wordt weergegeven.

De new wijzigingsfunctie is toegestaan voor geneste klassen. Hiermee wordt aangegeven dat de klasse een overgenomen lid met dezelfde naam verbergt, zoals beschreven in §15.3.5. Het is een compilatiefout voor de new wijziging die wordt weergegeven in een klassedeclaratie die geen geneste klassedeclaratie is.

De public, protecteden internalprivate modifiers bepalen de toegankelijkheid van de klasse. Afhankelijk van de context waarin de klassedeclaratie plaatsvindt, zijn sommige van deze modifiers mogelijk niet toegestaan (§7.5.2).

Wanneer een gedeeltelijke typedeclaratie (§15.2.7) een toegankelijkheidsspecificatie (via de public, protected, internalen private modifiers) bevat, moet die specificatie overeenkomen met alle andere onderdelen die een toegankelijkheidsspecificatie bevatten. Als geen deel van een gedeeltelijk type een toegankelijkheidsspecificatie bevat, krijgt het type de juiste standaardtoegankelijkheid (§7.5.2).

De abstract, sealeden static modifiers worden besproken in de volgende subclauses.

15.2.2.2 Abstracte klassen

De abstract wijzigingsfunctie wordt gebruikt om aan te geven dat een klasse onvolledig is en dat deze alleen als basisklasse moet worden gebruikt. Een abstracte klasse verschilt van een niet-abstracte klasse op de volgende manieren:

  • Een abstracte klasse kan niet rechtstreeks worden geïnstantieerd en het is een compilatiefout voor het gebruik van de new operator voor een abstracte klasse. Hoewel het mogelijk is om variabelen en waarden te hebben waarvan compileertijdtypen abstract zijn, zijn dergelijke variabelen en waarden noodzakelijkerwijs null verwijzingen naar exemplaren van niet-abstracte klassen die zijn afgeleid van de abstracte typen.
  • Een abstracte klasse is toegestaan (maar niet vereist) om abstracte leden te bevatten.
  • Een abstracte klasse kan niet worden verzegeld.

Wanneer een niet-abstracte klasse wordt afgeleid van een abstracte klasse, omvat de niet-abstracte klasse werkelijke implementaties van alle overgenomen abstracte leden, waardoor deze abstracte leden worden overschreven.

Voorbeeld: In de volgende code

abstract class A
{
    public abstract void F();
}

abstract class B : A
{
    public void G() {}
}

class C : B
{
    public override void F()
    {
        // Actual implementation of F
    }
}

de abstracte klasse A introduceert een abstracte methode F. Klasse B introduceert een extra methode G, maar omdat deze geen implementatie van Fbiedt, B wordt ook abstract gedeclareerd. Klasse C overschrijft F en biedt een werkelijke implementatie. Aangezien er geen abstracte leden Czijn, C is toegestaan (maar niet vereist) niet-abstract te zijn.

eindvoorbeeld

Als een of meer delen van een gedeeltelijke typedeclaratie (§15.2.7) van een klasse de abstract wijzigingsfunctie bevatten, is de klasse abstract. Anders is de klasse niet-abstract.

15.2.2.3 Verzegelde klassen

De sealed modifier wordt gebruikt om afleiding van een klasse te voorkomen. Er treedt een compilatietijdfout op als een verzegelde klasse is opgegeven als de basisklasse van een andere klasse.

Een verzegelde klasse kan ook geen abstracte klasse zijn.

Opmerking: De sealed modifier wordt voornamelijk gebruikt om onbedoelde afleiding te voorkomen, maar het maakt ook bepaalde runtime-optimalisaties mogelijk. Met name omdat een verzegelde klasse nooit afgeleide klassen heeft, is het mogelijk om aanroepen van leden van virtuele functies op verzegelde klasse-exemplaren te transformeren in niet-virtuele aanroepen. eindnotitie

Als een of meer delen van een gedeeltelijke typedeclaratie (§15.2.7) van een klasse de sealed wijzigingsfunctie bevatten, wordt de klasse verzegeld. Anders is de klasse niet verzegeld.

15.2.2.4 Statische klassen

15.2.2.4.1 Algemeen

De static wijzigingsfunctie wordt gebruikt om de klasse te markeren die wordt gedeclareerd als een statische klasse. Een statische klasse wordt niet geïnstantieerd, mag niet worden gebruikt als type en mag alleen statische leden bevatten. Alleen een statische klasse kan declaraties van extensiemethoden bevatten (§15.6.10).

Een statische klassedeclaratie is onderhevig aan de volgende beperkingen:

  • Een statische klasse mag geen wijzigingsfunctie sealed bevattenabstract. (Aangezien een statische klasse echter niet kan worden geïnstantieerd of afgeleid, gedraagt deze zich alsof deze zowel verzegeld als abstract is.)
  • Een statische klasse bevat geen class_base specificatie (§15.2.4) en kan niet expliciet een basisklasse of een lijst met geïmplementeerde interfaces opgeven. Een statische klasse neemt impliciet over van het type object.
  • Een statische klasse mag alleen statische leden bevatten (§15.3.8).

    Opmerking: Alle constanten en geneste typen worden geclassificeerd als statische leden. eindnotitie

  • Een statische klasse mag geen leden hebben met protectedof private protectedprotected internal toegankelijkheid hebben gedeclareerd.

Het is een compilatiefout om een van deze beperkingen te schenden.

Een statische klasse heeft geen exemplaarconstructors. Het is niet mogelijk om een instantieconstructor in een statische klasse te declareren en er is geen standaardexemplarenconstructor (§15.11.5) beschikbaar voor een statische klasse.

De leden van een statische klasse zijn niet automatisch statisch en de liddeclaraties bevatten expliciet een static wijzigingsfunctie (met uitzondering van constanten en geneste typen). Wanneer een klasse is genest binnen een statische buitenste klasse, is de geneste klasse geen statische klasse, tenzij deze expliciet een static wijzigingsfunctie bevat.

Als een of meer delen van een gedeeltelijke typedeclaratie (§15.2.7) van een klasse de static wijzigingsfunctie bevatten, is de klasse statisch. Anders is de klasse niet statisch.

15.2.2.4.2 Referencing static class types

Een namespace_or_type_name (§7.8) mag verwijzen naar een statische klasse als

  • De namespace_or_type_name is het T in een namespace_or_type_name van de vorm T.I, of
  • De namespace_or_type-naam is de T in een typeof_expression (§12.8.18) van het formulier typeof(T).

Een primary_expression (§12.8) mag verwijzen naar een statische klasse indien

  • De primary_expression staat in een member_access (E) van het formulier.E.I

In een andere context is het een compilatiefout om te verwijzen naar een statische klasse.

Opmerking: het is bijvoorbeeld een fout voor een statische klasse die moet worden gebruikt als basisklasse, een samenstellend type (§15.3.7) van een lid, een algemeen typeargument of een typeparameterbeperking. Op dezelfde manier kan een statische klasse niet worden gebruikt in een matrixtype, een nieuwe expressie, een cast-expressie, een expressie, een als-expressie, een sizeof expressie of een standaardwaarde-expressie. eindnotitie

15.2.3 Typeparameters

Een typeparameter is een eenvoudige id die een tijdelijke aanduiding aangeeft voor een typeargument dat is opgegeven om een samengesteld type te maken. Volgens constrast is een typeargument (§8.4.2) het type dat wordt vervangen door de typeparameter wanneer een samengesteld type wordt gemaakt.

type_parameter_list
    : '<' type_parameters '>'
    ;

type_parameters
    : attributes? type_parameter
    | type_parameters ',' attributes? type_parameter
    ;

type_parameter wordt gedefinieerd in §8,5.

Elke typeparameter in een klassedeclaratie definieert een naam in de declaratieruimte (§7.3) van die klasse. Het mag dus niet dezelfde naam hebben als een andere typeparameter van die klasse of een lid dat in die klasse is gedeclareerd. Een typeparameter mag niet dezelfde naam hebben als het type zelf.

Twee gedeeltelijke algemene typedeclaraties (in hetzelfde programma) dragen bij aan hetzelfde niet-afhankelijke algemene type als ze dezelfde volledig gekwalificeerde naam hebben (met een generic_dimension_specifier (§12.8.18) voor het aantal typeparameters) (§7.8.3). Twee dergelijke gedeeltelijke typedeclaraties geven dezelfde naam op voor elke typeparameter, in volgorde.

15.2.4 Klassebasisspecificatie

15.2.4.1 Algemeen

Een klassedeclaratie kan een class_base specificatie bevatten, waarmee de directe basisklasse van de klasse en de interfaces (§18) rechtstreeks door de klasse worden geïmplementeerd.

class_base
    : ':' class_type
    | ':' interface_type_list
    | ':' class_type ',' interface_type_list
    ;

interface_type_list
    : interface_type (',' interface_type)*
    ;

15.2.4.2 Basisklassen

Wanneer een class_type is opgenomen in de class_base, wordt de directe basisklasse van de klasse opgegeven die wordt gedeclareerd. Als een niet-gedeeltelijke klassedeclaratie geen class_base heeft of als de class_base alleen interfacetypen bevat, wordt ervan uitgegaan dat de directe basisklasse .object Wanneer een gedeeltelijke klassedeclaratie een basisklassespecificatie bevat, verwijst die basisklassespecificatie naar hetzelfde type als alle andere onderdelen van dat gedeeltelijke type met een basisklassespecificatie. Als geen deel van een gedeeltelijke klasse een basisklassespecificatie bevat, is objectde basisklasse . Een klasse neemt leden over van de directe basisklasse, zoals beschreven in §15.3.4.

Voorbeeld: In de volgende code

class A {}
class B : A {}

Klasse A wordt geacht de directe basisklasse van Bte zijn en B wordt geacht te worden afgeleid van A. Omdat A er niet expliciet een directe basisklasse wordt opgegeven, is de directe basisklasse impliciet object.

eindvoorbeeld

Voor een samengesteld klassetype, met inbegrip van een geneste type dat is gedeclareerd in een algemene typedeclaratie (§15.3.9.7), wordt, als een basisklasse is opgegeven in de algemene klassedeclaratie, de basisklasse van het samengestelde type verkregen door voor elke type_parameter in de basisklassedeclaratie de bijbehorende type_argument van het samengestelde type te vervangen.

Voorbeeld: Gegeven de algemene klassedeclaraties

class B<U,V> {...}
class G<T> : B<string,T[]> {...}

de basisklasse van het samengestelde type G<int> zou zijn B<string,int[]>.

eindvoorbeeld

De basisklasse die is opgegeven in een klassedeclaratie kan een geconstrueerd klassetype zijn (§8.4). Een basisklasse kan geen eigen typeparameter zijn (§8.5), maar kan wel betrekking hebben op de typeparameters die binnen het bereik vallen.

Voorbeeld:

class Base<T> {}

// Valid, non-constructed class with constructed base class
class Extend1 : Base<int> {}

// Error, type parameter used as base class
class Extend2<V> : V {}

// Valid, type parameter used as type argument for base class
class Extend3<V> : Base<V> {}

eindvoorbeeld

De directe basisklasse van een klassetype moet ten minste zo toegankelijk zijn als het klassetype zelf (§7.5.5). Het is bijvoorbeeld een compilatiefout voor een openbare klasse die moet worden afgeleid van een persoonlijke of interne klasse.

De directe basisklasse van een klassetype mag geen van de volgende typen zijn: System.Array, System.Delegate, System.Enumof System.ValueType het dynamic type. Bovendien mag een algemene klassedeclaratie niet worden gebruikt System.Attribute als een directe of indirecte basisklasse (§22.2.1).

Bij het bepalen van de betekenis van de directe basisklassespecificatie A van een klasse Bwordt tijdelijk uitgegaan Bvan de directe basisklasseobject, die ervoor zorgt dat de betekenis van een basisklassespecificatie niet recursief afhankelijk is van zichzelf.

Voorbeeld: Het volgende

class X<T>
{
    public class Y{}
}

class Z : X<Z.Y> {}

is in de basisklassespecificatie X<Z.Y> de directe basisklasse van Z wordt beschouwd objectals , en daarom (volgens de regels van §7.8) Z wordt niet als lid Ybeschouwd .

eindvoorbeeld

De basisklassen van een klasse zijn de directe basisklasse en de bijbehorende basisklassen. Met andere woorden, de set basisklassen is de transitieve sluiting van de directe basisklasserelatie.

Voorbeeld: In het volgende:

class A {...}
class B<T> : A {...}
class C<T> : B<IComparable<T>> {...}
class D<T> : C<T[]> {...}

de basisklassen D<int> zijn C<int[]>, B<IComparable<int[]>>, Aen object.

eindvoorbeeld

Met uitzondering van klasse objectheeft elke klasse precies één directe basisklasse. De object klasse heeft geen directe basisklasse en is de ultieme basisklasse van alle andere klassen.

Het is een compilatietijdfout voor een klasse die afhankelijk is van zichzelf. Voor deze regel is een klasse rechtstreeks afhankelijk van de directe basisklasse (indien van toepassing) en rechtstreeks afhankelijk van de dichtstbijzijnde klasse waarin deze is genest (indien van toepassing). Gezien deze definitie is de volledige set klassen waarop een klasse afhankelijk is van de transitieve sluiting van de klasse, rechtstreeks afhankelijk van de relatie.

Voorbeeld: Het voorbeeld

class A : A {}

is onjuist omdat de klasse afhankelijk is van zichzelf. Op dezelfde manier is het voorbeeld

class A : B {}
class B : C {}
class C : A {}

is in de fout omdat de klassen circulair afhankelijk zijn van zichzelf. Ten slotte het voorbeeld

class A : B.C {}
class B : A
{
    public class C {}
}

resulteert in een compilatietijdfout omdat A afhankelijk is van B.C (de directe basisklasse), die afhankelijk is B van (de onmiddellijk omsluitende klasse), die circulair afhankelijk is Avan .

eindvoorbeeld

Een klasse is niet afhankelijk van de klassen die erin zijn genest.

Voorbeeld: In de volgende code

class A
{
    class B : A {}
}

B A is afhankelijk van (omdat A zowel de directe basisklasse als de bijbehorende klasse onmiddellijk insluiten), maar A niet afhankelijk is B van (omdat B het geen basisklasse of een insluitklasse van A). Het voorbeeld is dus geldig.

eindvoorbeeld

Het is niet mogelijk om af te leiden van een verzegelde klasse.

Voorbeeld: In de volgende code

sealed class A {}
class B : A {} // Error, cannot derive from a sealed class

Klasse B is in fout omdat deze probeert af te leiden van de verzegelde klasse A.

eindvoorbeeld

15.2.4.3 Interface-implementaties

Een class_base specificatie kan een lijst met interfacetypen bevatten. In dat geval wordt gezegd dat de klasse de opgegeven interfacetypen implementeert. Voor een samengesteld klassetype, met inbegrip van een geneste type dat is gedeclareerd in een algemene typedeclaratie (§15.3.9.7), wordt elk geïmplementeerd interfacetype verkregen door voor elke type_parameter in de opgegeven interface de bijbehorende type_argument van het samengestelde type te vervangen.

De set interfaces voor een type dat in meerdere delen is gedeclareerd (§15.2.7) is de samenvoeging van de interfaces die voor elk deel zijn opgegeven. Een bepaalde interface kan slechts eenmaal op elk onderdeel worden genoemd, maar meerdere onderdelen kunnen dezelfde basisinterface(s) een naam geven. Er is slechts één implementatie van elk lid van een bepaalde interface.

Voorbeeld: In het volgende:

partial class C : IA, IB {...}
partial class C : IC {...}
partial class C : IA, IB {...}

de set basisinterfaces voor klasse C is IA, IBen IC.

eindvoorbeeld

Normaal gesproken biedt elk onderdeel een implementatie van de interface(s) die op dat onderdeel zijn gedeclareerd; dit is echter geen vereiste. Een onderdeel kan de implementatie bieden voor een interface die op een ander onderdeel is gedeclareerd.

Voorbeeld:

partial class X
{
    int IComparable.CompareTo(object o) {...}
}

partial class X : IComparable
{
    ...
}

eindvoorbeeld

De basisinterfaces die zijn opgegeven in een klassedeclaratie kunnen worden samengesteld uit interfacetypen (§8.4, §18.2). Een basisinterface kan geen eigen typeparameter zijn, maar kan wel betrekking hebben op de typeparameters die binnen het bereik vallen.

Voorbeeld: De volgende code illustreert hoe een klasse samengestelde typen kan implementeren en uitbreiden:

class C<U, V> {}
interface I1<V> {}
class D : C<string, int>, I1<string> {}
class E<T> : C<int, T>, I1<T> {}

eindvoorbeeld

Interface-implementaties worden verder besproken in §18.6.

15.2.5 Type parameterbeperkingen

Algemene type- en methodedeclaraties kunnen desgewenst typeparameterbeperkingen opgeven door type_parameter_constraints_clauses op te geven.

type_parameter_constraints_clauses
    : type_parameter_constraints_clause
    | type_parameter_constraints_clauses type_parameter_constraints_clause
    ;

type_parameter_constraints_clause
    : 'where' type_parameter ':' type_parameter_constraints
    ;

type_parameter_constraints
    : primary_constraint (',' secondary_constraints)? (',' constructor_constraint)?
    | secondary_constraints (',' constructor_constraint)?
    | constructor_constraint
    ;

primary_constraint
    : class_type nullable_type_annotation?
    | 'class' nullable_type_annotation?
    | 'struct'
    | 'notnull'
    | 'unmanaged'
    ;

secondary_constraint
    : interface_type nullable_type_annotation?
    | type_parameter nullable_type_annotation?
    ;

secondary_constraints
    : secondary_constraint (',' secondary_constraint)*
    ;

constructor_constraint
    : 'new' '(' ')'
    ;

Elke type_parameter_constraints_clause bestaat uit het token where, gevolgd door de naam van een typeparameter, gevolgd door een dubbele punt en de lijst met beperkingen voor die typeparameter. Er kan maximaal één where component voor elke typeparameter zijn en de where componenten kunnen in elke volgorde worden vermeld. Net als de get tokens set in een eigenschapstoegangsor is het where token geen trefwoord.

De lijst met beperkingen in een where component kan een van de volgende onderdelen bevatten, in deze volgorde: één primaire beperking, een of meer secundaire beperkingen en de constructorbeperking, new().

Een primaire beperking kan een klassetype zijn, de beperking van het verwijzingstypeclass Het klassetype en de beperking van het verwijzingstype kunnen de nullable_type_annotation bevatten.

Een secundaire beperking kan een interface_type of type_parameter zijn, eventueel gevolgd door een nullable_type_annotation. De aanwezigheid van de nullable_type_annotatione* geeft aan dat het typeargument het null-referentietype mag zijn dat overeenkomt met een niet-null-verwijzingstype dat voldoet aan de beperking.

De beperking voor het verwijzingstype geeft aan dat een typeargument dat wordt gebruikt voor de typeparameter een verwijzingstype is. Alle klassetypen, interfacetypen, gedelegeerdentypen, matrixtypen en typeparameters die bekend staan als referentietype (zoals hieronder gedefinieerd) voldoen aan deze beperking.

Het klassetype, de beperking van het verwijzingstype en de secundaire beperkingen kunnen de aantekening van het type null bevatten. De aanwezigheid of afwezigheid van deze aantekening voor de typeparameter geeft de verwachtingen voor null-betrouwbaarheid voor het typeargument aan:

  • Als de beperking geen aantekening van het type null bevat, is het typeargument naar verwachting een verwijzingstype dat niet null kan worden gebruikt. Een compiler kan een waarschuwing geven als het typeargument een null-verwijzingstype is.
  • Als de beperking de aantekening van het type null bevat, wordt aan de beperking voldaan door zowel een niet-null-verwijzingstype als een null-verwijzingstype.

De null-waarde van het typeargument hoeft niet overeen te komen met de null-waarde van de typeparameter. Een compiler kan een waarschuwing geven als de null-waarde van de typeparameter niet overeenkomt met de null-waarde van het typeargument.

Opmerking: Als u wilt opgeven dat een typeargument een null-verwijzingstype is, voegt u de aantekening van het type null-type niet toe als een beperking (gebruik T : class of T : BaseClass), maar gebruikt T? u in de algemene declaratie om het bijbehorende null-verwijzingstype voor het typeargument aan te geven. eindnotitie

De aantekening ?van het type null kan niet worden gebruikt voor een niet-gekoppeld typeargument.

Voor een typeparameter wanneer het typeargument T een nullable verwijzingstype C?is, worden exemplaren T? geïnterpreteerd als C?, niet C??.

Voorbeeld: In de volgende voorbeelden ziet u hoe de null-waarde van een typeargument van invloed is op de null-waarde van een declaratie van de parameter van het type:

public class C
{
}

public static class  Extensions
{
    public static void M<T>(this T? arg) where T : notnull
    {

    }
}

public class Test
{
    public void M()
    {
        C? mightBeNull = new C();
        C notNull = new C();

        int number = 5;
        int? missing = null;

        mightBeNull.M(); // arg is C?
        notNull.M(); //  arg is C?
        number.M(); // arg is int?
        missing.M(); // arg is int?
    }
}

Wanneer het typeargument een niet-null-type is, geeft de ? typeaantekening aan dat de parameter het bijbehorende null-type is. Wanneer het typeargument al een nullable verwijzingstype is, is de parameter hetzelfde null-type.

eindvoorbeeld

De niet-null-beperking geeft aan dat een typeargument dat wordt gebruikt voor de typeparameter een niet-nullable waardetype of een niet-nullable verwijzingstype moet zijn. Een typeargument dat geen niet-nullable waarde- of verwijzingstype is, is toegestaan, maar een compiler kan een diagnostische waarschuwing genereren.

De waardetypebeperking geeft aan dat een typeargument dat wordt gebruikt voor de typeparameter een niet-null-waardetype is. Alle niet-null-structtypen, enumtypen en typeparameters met de beperking van het waardetype voldoen aan deze beperking. Houd er rekening mee dat hoewel een waardetype is geclassificeerd, een type null-waarde (§8.3.12) niet voldoet aan de beperking van het waardetype. Een typeparameter met de waardetypebeperking mag niet ook de constructor_constraint hebben, hoewel deze kan worden gebruikt als een typeargument voor een andere typeparameter met een constructor_constraint.

Opmerking: Het System.Nullable<T> type geeft de niet-null-waardetypebeperking voor T. Zo zijn recursief samengestelde typen van de formulieren T?? verboden Nullable<Nullable<T>> . eindnotitie

Omdat unmanaged dit geen trefwoord is, is in primary_constraint de niet-beheerde beperking altijd syntactisch dubbelzinnig is met class_type. Als een naamzoekactie (§12.8.4) van de naam unmanaged slaagt, wordt deze om compatibiliteitsredenen behandeld als een class_type. Anders wordt deze behandeld als de niet-beheerde beperking.

De niet-beheerde typebeperking geeft aan dat een typeargument dat wordt gebruikt voor de typeparameter een niet-null-niet-beheerd type is (§8.8).

Aanwijzertypen mogen nooit typeargumenten zijn en voldoen niet aan een typebeperkingen, zelfs niet-beheerd, ondanks dat ze niet-beheerde typen zijn.

Als een beperking een klassetype, een interfacetype of een typeparameter is, geeft dat type een minimaal 'basistype' op dat elk typeargument dat voor die typeparameter wordt gebruikt, wordt ondersteund. Wanneer een samengesteld type of algemene methode wordt gebruikt, wordt het typeargument gecontroleerd op basis van de beperkingen voor de typeparameter tijdens het compileren. Het opgegeven typeargument voldoet aan de in §8.4.5 beschreven voorwaarden.

Een class_type beperking voldoet aan de volgende regels:

  • Het type moet een klassetype zijn.
  • Het type mag niet zijn sealed.
  • Het type mag niet een van de volgende typen zijn: System.Array of System.ValueType.
  • Het type mag niet zijn object.
  • Maximaal één beperking voor een bepaalde typeparameter kan een klassetype zijn.

Een type dat is opgegeven als een interface_type beperking voldoet aan de volgende regels:

  • Het type moet een interfacetype zijn.
  • Een type mag niet meer dan één keer worden opgegeven in een bepaalde where component.

In beide gevallen kan de beperking betrekking hebben op een van de typeparameters van het bijbehorende type of de declaratie van de methode als onderdeel van een samengesteld type en kan het type worden gedeclareerd.

Elk klasse- of interfacetype dat is opgegeven als een typeparameterbeperking, moet ten minste zo toegankelijk zijn (§7.5.5) als het algemene type of de methode die wordt gedeclareerd.

Een type dat is opgegeven als een type_parameter beperking voldoet aan de volgende regels:

  • Het type moet een typeparameter zijn.
  • Een type mag niet meer dan één keer worden opgegeven in een bepaalde where component.

Daarnaast zijn er geen cycli in de afhankelijkheidsgrafiek van typeparameters, waarbij afhankelijkheid een transitieve relatie is gedefinieerd door:

  • Als een typeparameter T wordt gebruikt als een beperking voor de typeparameter S , Sis dit afhankelijkT van.
  • Als een typeparameter S afhankelijk is van een typeparameter T en T afhankelijk is van een typeparameter U , Sis dat afhankelijkU van.

Gezien deze relatie is het een compilatiefout voor een typeparameter die direct of indirect afhankelijk is van zichzelf.

Eventuele beperkingen moeten consistent zijn tussen afhankelijke typeparameters. Als de typeparameter S afhankelijk is van de typeparameter T , gaat u als volgende te werk:

  • T mag niet de waardetypebeperking hebben. Anders wordt dit effectief verzegeld, T dus S wordt gedwongen om hetzelfde type te zijn als T, waardoor de noodzaak van twee typeparameters wordt geëlimineerd.
  • Als S de waardetypebeperking is, heeft deze T geen class_type beperking.
  • Als een class_type beperking heeft en S een class_type beperking A heeft, moet er een identiteitsconversie of impliciete verwijzingsconversie van T naar of een impliciete verwijzingsconversie van B naar A.BBA
  • Als S dit ook afhankelijk is van de typeparameter U en U een class_type beperking A heeft en T een class_type beperking B heeft, moet er een identiteitsconversie of impliciete verwijzingsconversie van A naar B of een impliciete verwijzingsconversie van B naar A.

Het is geldig om S de waardetypebeperking te hebben en T om de beperking van het verwijzingstype te hebben. Dit beperkt T zich in feite tot de typen System.Object, System.ValueTypeen System.Enumelk interfacetype.

Als de where component voor een typeparameter een constructorbeperking (met het formulier new()) bevat, is het mogelijk om de new operator te gebruiken om exemplaren van het type te maken (§12.8.17.2). Elk typeargument dat wordt gebruikt voor een typeparameter met een constructorbeperking, moet een waardetype, een niet-abstracte klasse met een openbare parameterloze constructor of een typeparameter met de beperking van het waardetype of de constructorbeperking zijn.

Het is een compilatiefout voor type_parameter_constraints met een primary_constraint of structunmanaged om ook een constructor_constraint te hebben.

Voorbeeld: Hier volgen enkele voorbeelden van beperkingen:

interface IPrintable
{
    void Print();
}

interface IComparable<T>
{
    int CompareTo(T value);
}

interface IKeyProvider<T>
{
    T GetKey();
}

class Printer<T> where T : IPrintable {...}
class SortedList<T> where T : IComparable<T> {...}

class Dictionary<K,V>
    where K : IComparable<K>
    where V : IPrintable, IKeyProvider<K>, new()
{
    ...
}

Het volgende voorbeeld is in fout omdat het een circulariteit veroorzaakt in de afhankelijkheidsgrafiek van de typeparameters:

class Circular<S,T>
    where S: T
    where T: S // Error, circularity in dependency graph
{
    ...
}

In de volgende voorbeelden ziet u aanvullende ongeldige situaties:

class Sealed<S,T>
    where S : T
    where T : struct // Error, `T` is sealed
{
    ...
}

class A {...}
class B {...}

class Incompat<S,T>
    where S : A, T
    where T : B // Error, incompatible class-type constraints
{
    ...
}

class StructWithClass<S,T,U>
    where S : struct, T
    where T : U
    where U : A // Error, A incompatible with struct
{
    ...
}

eindvoorbeeld

De dynamische wissing van een type C wordt Cₓ als volgt samengesteld:

  • Als C een genest type Outer.Inner is, is dit Cₓ een genest type Outerₓ.Innerₓ.
  • Als CCₓeen samengesteld type G<A¹, ..., Aⁿ> met typeargumenten A¹, ..., Aⁿ is, is het Cₓ samengestelde type G<A¹ₓ, ..., Aⁿₓ>.
  • Als C dit een matrixtype E[] is, is dit Cₓ het matrixtype Eₓ[].
  • Als C dynamisch is, is Cₓdat object .
  • Anders is Cₓhet C .

De effectieve basisklasse van een typeparameter T wordt als volgt gedefinieerd:

Laten we R een set typen zijn, zoals:

  • Voor elke beperking van T dat is een typeparameter, R bevat de bijbehorende effectieve basisklasse.
  • Voor elke beperking van T dat is een struct-type, R bevat System.ValueType.
  • Voor elke beperking van T dat is een opsommingstype, R bevat System.Enum.
  • Voor elke beperking van T dat is een gemachtigde type, R bevat de dynamische verwijdering.
  • Voor elke beperking van T dat is een matrixtype, R bevat System.Array.
  • Voor elke beperking van T dat is een klassetype, R bevat de dynamische verwijdering.

Dan

  • Als T de waardetypebeperking is, is de effectieve basisklasse.System.ValueType
  • Als deze niet leeg is, R is objectde effectieve basisklasse.
  • Anders is de effectieve basisklasse van T het meest omvattende type (§10.5.3) van set R. Als de set geen omvattend type heeft, is de effectieve basisklasse van T .object De consistentieregels zorgen ervoor dat het meestbevattende type bestaat.

Als de typeparameter een methodetypeparameter is waarvan de beperkingen worden overgenomen van de basismethode, wordt de effectieve basisklasse berekend na het vervangen van het type.

Deze regels zorgen ervoor dat de effectieve basisklasse altijd een class_type is.

De effectieve interfaceset van een typeparameter T wordt als volgt gedefinieerd:

  • Als T er geen secondary_constraints is, is de effectieve interfaceset leeg.
  • Als T er interface_type beperkingen zijn, maar geen type_parameter beperkingen, is de effectieve interfaceset de set dynamische verwijderingen van de interface_type beperkingen.
  • Als T er geen interface_type beperkingen zijn maar type_parameter beperkingen heeft, is de effectieve interfaceset de samenvoeging van de effectieve interfacesets van de type_parameter beperkingen.
  • Als zowel interface_type beperkingen als type_parameter beperkingen heeft, is de effectieve interfaceset de samenvoeging van de reeks dynamische verwijderingen van de T beperkingen en de effectieve interfacesets van de type_parameter beperkingen.

Een typeparameter is een verwijzingstype als deze de beperking van het verwijzingstype heeft of de effectieve basisklasse niet object of System.ValueType. Een typeparameter is bekend als een niet-null-verwijzingstype als het een verwijzingstype is en de beperking voor het niet-null-verwijzingstype heeft.

Waarden van een parametertype voor een beperkt type kunnen worden gebruikt voor toegang tot de exemplaarleden die worden geïmpliceerd door de beperkingen.

Voorbeeld: In het volgende:

interface IPrintable
{
    void Print();
}

class Printer<T> where T : IPrintable
{
    void PrintOne(T x) => x.Print();
}

de methoden van IPrintable kunnen rechtstreeks x worden aangeroepen omdat T is beperkt om altijd te implementeren IPrintable.

eindvoorbeeld

Wanneer een gedeeltelijke algemene typeverklaring beperkingen bevat, zijn de beperkingen het eens met alle andere onderdelen die beperkingen omvatten. Elk onderdeel dat beperkingen omvat, moet beperkingen hebben voor dezelfde set van typeparameters, en voor elke typeparameter moeten de sets primaire, secundaire en constructorbeperkingen gelijkwaardig zijn. Twee sets beperkingen zijn gelijkwaardig als ze dezelfde leden bevatten. Als geen deel van een gedeeltelijk algemeen type beperkingen voor typeparameters aangeeft, worden de typeparameters beschouwd als niet-getraind.

Voorbeeld:

partial class Map<K,V>
    where K : IComparable<K>
    where V : IKeyProvider<K>, new()
{
    ...
}

partial class Map<K,V>
    where V : IKeyProvider<K>, new()
    where K : IComparable<K>
{
    ...
}

partial class Map<K,V>
{
    ...
}

is juist omdat deze onderdelen met beperkingen (de eerste twee) in feite dezelfde set primaire, secundaire en constructorbeperkingen opgeven voor respectievelijk dezelfde set parameters van het type.

eindvoorbeeld

15.2.6 Klassetekst

De class_body van een klasse definieert de leden van die klasse.

class_body
    : '{' class_member_declaration* '}'
    ;

15.2.7 Gedeeltelijke declaraties

De wijzigingsfunctie partial wordt gebruikt bij het definiëren van een klasse, struct of interfacetype in meerdere onderdelen. De partial modifier is een contextueel trefwoord (§6.4.4) en heeft alleen speciale betekenis direct voor een van de trefwoorden class, structof interface.

Elk deel van een gedeeltelijke typedeclaratie omvat een partial wijzigingsfunctie en moet worden gedeclareerd in dezelfde naamruimte of met het type als de andere onderdelen. De partial wijzigingsfunctie geeft aan dat er ergens anders extra onderdelen van de typedeclaratie kunnen bestaan, maar dat het bestaan van dergelijke aanvullende onderdelen geen vereiste is; het is geldig voor de enige declaratie van een type om de partial wijzigingsfunctie op te nemen. Het is geldig voor slechts één declaratie van een gedeeltelijk type om de basisklasse of geïmplementeerde interfaces op te nemen. Alle declaraties van een basisklasse of geïmplementeerde interfaces moeten echter overeenkomen, inclusief de null-waarde van opgegeven typeargumenten.

Alle delen van een gedeeltelijk type worden samengecompileerd, zodat de onderdelen tijdens het compileren kunnen worden samengevoegd. Gedeeltelijke typen staan specifiek niet toe dat reeds gecompileerde typen worden uitgebreid.

Geneste typen kunnen in meerdere onderdelen worden gedeclareerd met behulp van de partial wijzigingsfunctie. Normaal gesproken wordt het inhoudstype ook gedeclareerd en wordt elk deel van het geneste type gedeclareerd partial in een ander deel van het type dat het bevat.

Voorbeeld: De volgende gedeeltelijke klasse wordt geïmplementeerd in twee delen, die zich in verschillende compilatie-eenheden bevinden. Het eerste deel wordt gegenereerd door een hulpprogramma voor databasetoewijzing, terwijl het tweede deel handmatig is geschreven:

public partial class Customer
{
    private int id;
    private string name;
    private string address;
    private List<Order> orders;

    public Customer()
    {
        ...
    }
}

// File: Customer2.cs
public partial class Customer
{
    public void SubmitOrder(Order orderSubmitted) => orders.Add(orderSubmitted);

    public bool HasOutstandingOrders() => orders.Count > 0;
}

Wanneer de twee bovenstaande delen samen worden gecompileerd, gedraagt de resulterende code zich alsof de klasse als één eenheid is geschreven, als volgt:

public class Customer
{
    private int id;
    private string name;
    private string address;
    private List<Order> orders;

    public Customer()
    {
        ...
    }

    public void SubmitOrder(Order orderSubmitted) => orders.Add(orderSubmitted);

    public bool HasOutstandingOrders() => orders.Count > 0;
}

eindvoorbeeld

De verwerking van kenmerken die zijn opgegeven voor het type of type parameters van verschillende onderdelen van een gedeeltelijke declaratie wordt besproken in §22.3.

15.3 Klasleden

15.3.1 Algemeen

De leden van een klasse bestaan uit de leden die zijn geïntroduceerd door de class_member_declarations en de leden die zijn overgenomen van de directe basisklasse.

class_member_declaration
    : constant_declaration
    | field_declaration
    | method_declaration
    | property_declaration
    | event_declaration
    | indexer_declaration
    | operator_declaration
    | constructor_declaration
    | finalizer_declaration
    | static_constructor_declaration
    | type_declaration
    ;

De leden van een klas zijn onderverdeeld in de volgende categorieën:

  • Constanten, die constante waarden vertegenwoordigen die zijn gekoppeld aan de klasse (§15.4).
  • Velden, die de variabelen van de klasse zijn (§15,5).
  • Methoden die de berekeningen en acties implementeren die door de klasse kunnen worden uitgevoerd (§15.6).
  • Eigenschappen, die benoemde kenmerken definiëren en de acties die zijn gekoppeld aan het lezen en schrijven van deze kenmerken (§15.7).
  • Gebeurtenissen, waarmee meldingen worden gedefinieerd die kunnen worden gegenereerd door de klasse (§15.8).
  • Indexeerfuncties, waarmee exemplaren van de klasse op dezelfde manier (syntactisch) kunnen worden geïndexeerd als matrices (§15.9).
  • Operators, die de expressieoperators definiëren die kunnen worden toegepast op exemplaren van de klasse (§15.10).
  • Instantieconstructors, waarmee de acties worden geïmplementeerd die nodig zijn om exemplaren van de klasse te initialiseren (§15.11)
  • Finalizers, waarmee de acties worden geïmplementeerd die moeten worden uitgevoerd voordat exemplaren van de klasse definitief worden verwijderd (§15.13).
  • Statische constructors, waarmee de acties worden geïmplementeerd die nodig zijn om de klasse zelf te initialiseren (§15.12).
  • Typen, die de typen vertegenwoordigen die lokaal zijn voor de klasse (§14.7).

Een class_declaration maakt een nieuwe declaratieruimte (§7.3) en de type_parameters en de class_member_declarationdirect in de class_declaration nieuwe leden in deze declaratieruimte introduceren. De volgende regels zijn van toepassing op class_member_declaration s:

  • Instantieconstructors, finalizers en statische constructors hebben dezelfde naam als de onmiddellijk ingesloten klasse. Alle andere leden hebben namen die verschillen van de naam van de onmiddellijk ingesloten klasse.

  • De naam van een typeparameter in de type_parameter_list van een klassedeclaratie verschilt van de namen van alle andere typeparameters in hetzelfde type_parameter_list en verschilt van de naam van de klasse en de namen van alle leden van de klasse.

  • De naam van een type verschilt van de namen van alle niet-typeleden die in dezelfde klasse zijn aangegeven. Als twee of meer typedeclaraties dezelfde volledig gekwalificeerde naam hebben, hebben de declaraties de partial wijzigingsfunctie (§15.2.7) en worden deze declaraties gecombineerd om één type te definiëren.

Opmerking: Aangezien de volledig gekwalificeerde naam van een typedeclaratie het aantal typeparameters codeert, kunnen twee verschillende typen dezelfde naam hebben zolang ze een ander aantal typeparameters hebben. eindnotitie

  • De naam van een constante, veld, eigenschap of gebeurtenis verschilt van de namen van alle andere leden die in dezelfde klasse zijn gedeclareerd.

  • De naam van een methode verschilt van de namen van alle andere niet-methoden die in dezelfde klasse zijn gedeclareerd. Bovendien verschilt de handtekening (§7.6) van een methode van de handtekeningen van alle andere methoden die in dezelfde klasse zijn aangegeven, en twee methoden die in dezelfde klasse zijn gedeclareerd, mogen geen handtekeningen hebben die uitsluitend verschillen van in, outen ref.

  • De handtekening van een instantieconstructor wijkt af van de handtekeningen van alle andere instantieconstructors die in dezelfde klasse zijn gedeclareerd en twee constructors die in dezelfde klasse zijn gedeclareerd, hebben geen handtekeningen die alleen ref verschillen van en out.

  • De handtekening van een indexeerfunctie verschilt van de handtekeningen van alle andere indexeerfuncties die in dezelfde klasse zijn aangegeven.

  • De handtekening van een exploitant verschilt van de handtekeningen van alle andere exploitanten die in dezelfde klasse zijn aangegeven.

De overgenomen leden van een klasse (§15.3.4) maken geen deel uit van de declaratieruimte van een klasse.

Opmerking: een afgeleide klasse mag dus een lid met dezelfde naam of handtekening declareren als een overgenomen lid (waardoor het overgenomen lid wordt verborgen). eindnotitie

De set leden van een type die in meerdere delen is gedeclareerd (§15.2.7) is de vereniging van de leden die in elk deel zijn gedeclareerd. De lichamen van alle delen van de typedeclaratie delen dezelfde declaratieruimte (§7.3) en het bereik van elk lid (§7.7) strekt zich uit tot de lichamen van alle onderdelen. Het toegankelijkheidsdomein van een lid bevat altijd alle onderdelen van het insluittype; een privélid dat in het ene deel is gedeclareerd, is vrij toegankelijk vanaf een ander deel. Het is een compilatiefout om hetzelfde lid in meer dan één deel van het type te declareren, tenzij dat lid een type is dat de partial wijzigingsfunctie heeft.

Voorbeeld:

partial class A
{
    int x;                   // Error, cannot declare x more than once

    partial class Inner      // Ok, Inner is a partial type
    {
        int y;
    }
}

partial class A
{
    int x;                   // Error, cannot declare x more than once

    partial class Inner      // Ok, Inner is a partial type
    {
        int z;
    }
}

eindvoorbeeld

Veld initialisatievolgorde kan aanzienlijk zijn binnen C#-code en sommige garanties worden geboden, zoals gedefinieerd in §15.5.6.1. Anders is de volgorde van leden binnen een type zelden significant, maar kan dit van belang zijn bij communicatie met andere talen en omgevingen. In deze gevallen is de volgorde van leden binnen een type dat in meerdere delen is gedeclareerd, niet gedefinieerd.

15.3.2 Het exemplaartype

Elke klassedeclaratie heeft een gekoppeld exemplaartype. Voor een algemene klassedeclaratie wordt het exemplaartype gevormd door een samengesteld type (§8.4) te maken uit de typedeclaratie, waarbij elk van de opgegeven typeargumenten de bijbehorende typeparameter is. Omdat het exemplaartype gebruikmaakt van de typeparameters, kan deze alleen worden gebruikt wanneer de typeparameters binnen het bereik vallen; dat wil gezegd, binnen de klassedeclaratie. Het exemplaartype is het type voor this code die in de klassedeclaratie is geschreven. Voor niet-algemene klassen is het exemplaartype gewoon de gedeclareerde klasse.

Voorbeeld: Hieronder ziet u verschillende klassedeclaraties, samen met de instantietypen:

class A<T>             // instance type: A<T>
{
    class B {}         // instance type: A<T>.B
    class C<U> {}      // instance type: A<T>.C<U>
}
class D {}             // instance type: D

eindvoorbeeld

15.3.3 Leden van samengestelde typen

De niet-overgenomen leden van een geconstrueerd type worden verkregen door voor elke type_parameter in de liddeclaratie de bijbehorende type_argument van het samengestelde type te vervangen. Het vervangingsproces is gebaseerd op de semantische betekenis van typedeclaraties en is niet alleen tekstuele vervanging.

Voorbeeld: Gegeven de algemene klassedeclaratie

class Gen<T,U>
{
    public T[,] a;
    public void G(int i, T t, Gen<U,T> gt) {...}
    public U Prop { get {...} set {...} }
    public int H(double d) {...}
}

het samengestelde type Gen<int[],IComparable<string>> de volgende leden heeft:

public int[,][] a;
public void G(int i, int[] t, Gen<IComparable<string>,int[]> gt) {...}
public IComparable<string> Prop { get {...} set {...} }
public int H(double d) {...}

Het type van het lid a in de algemene klassedeclaratie Gen is 'tweedimensionale matrix van T', dus het type van het lid a in het hierboven samengestelde type is 'tweedimensionale matrix van eendimensionale matrix van int', of int[,][].

eindvoorbeeld

Binnen leden van this de instantiefunctie is het type het exemplaartype (§15.3.2) van de declaratie die de inhoud bevat.

Alle leden van een algemene klasse kunnen typeparameters van elke omsluitklasse gebruiken, rechtstreeks of als onderdeel van een samengesteld type. Wanneer een bepaald gesloten geconstrueerd type (§8.4.3) wordt gebruikt tijdens de uitvoering, wordt elk gebruik van een typeparameter vervangen door het typeargument dat aan het samengestelde type is opgegeven.

Voorbeeld:

class C<V>
{
    public V f1;
    public C<V> f2;

    public C(V x)
    {
        this.f1 = x;
        this.f2 = this;
    }
}

class Application
{
    static void Main()
    {
        C<int> x1 = new C<int>(1);
        Console.WriteLine(x1.f1);              // Prints 1

        C<double> x2 = new C<double>(3.1415);
        Console.WriteLine(x2.f1);              // Prints 3.1415
    }
}

eindvoorbeeld

15.3.4 Overname

Een klasse neemt de leden van de directe basisklasse over. Overname betekent dat een klasse impliciet alle leden van de directe basisklasse bevat, met uitzondering van de instantieconstructors, finalizers en statische constructors van de basisklasse. Enkele belangrijke aspecten van overname zijn:

  • Overname is transitief. Als C wordt afgeleid van B, en B is afgeleid van A, neemt u C de leden over die zijn gedeclareerd in B en de leden die zijn gedeclareerd in A.

  • Een afgeleide klasse breidt de directe basisklasse uit. Een afgeleide klasse kan nieuwe leden toevoegen aan leden die worden overgenomen, maar kan de definitie van een overgenomen lid niet verwijderen.

  • Exemplaarconstructors, finalizers en statische constructors worden niet overgenomen, maar alle andere leden zijn, ongeacht hun gedeclareerde toegankelijkheid (§7.5). Afhankelijk van hun gedeclareerde toegankelijkheid zijn overgenomen leden mogelijk niet toegankelijk in een afgeleide klasse.

  • Een afgeleide klasse kan (§7.7.2.3) overgenomen leden verbergen door nieuwe leden met dezelfde naam of handtekening te declareren. Als u een overgenomen lid verbergt, wordt dat lid echter niet verwijderd. Het maakt dat lid alleen niet rechtstreeks toegankelijk via de afgeleide klasse.

  • Een exemplaar van een klasse bevat een set van alle instantievelden die in de klasse en de basisklassen zijn gedeclareerd, en een impliciete conversie (§10.2.8) bestaat van een afgeleid klassetype naar een van de basisklassetypen. Een verwijzing naar een exemplaar van een afgeleide klasse kan dus worden beschouwd als een verwijzing naar een exemplaar van een van de basisklassen.

  • Een klasse kan virtuele methoden, eigenschappen, indexeerfuncties en gebeurtenissen declareren en afgeleide klassen kunnen de implementatie van deze functieleden overschrijven. Hierdoor kunnen klassen polymorf gedrag vertonen waarbij de acties die door een functielidaanroep worden uitgevoerd, variëren, afhankelijk van het uitvoeringstype van het exemplaar waarmee dat functielid wordt aangeroepen.

De overgenomen leden van een samengesteld klassetype zijn de leden van het directe basisklassetype (§15.2.4.2), die wordt gevonden door de typeargumenten van het samengestelde type te vervangen voor elk exemplaar van de bijbehorende typeparameters in de base_class_specification. Deze leden worden op hun beurt omgezet door de overeenkomstige type_argument van de base_class_specification voor elke type_parameter in de lidverklaring te vervangen.

Voorbeeld:

class B<U>
{
    public U F(long index) {...}
}

class D<T> : B<T[]>
{
    public T G(string s) {...}
}

In de bovenstaande code heeft het samengestelde type D<int> een niet-overgenomen lid openbaar intG(string s) verkregen door het typeargument int voor de typeparameter Tte vervangen. D<int> heeft ook een overgenomen lid van de klassedeclaratie B. Dit overgenomen lid wordt bepaald door eerst het type basisklasse te bepalen door in de specificatie B<int[]>van de basisklasse te vervangen D<int>int.TB<T[]> Vervolgens wordt, B als een typeargument, int[]vervangen door U in public U F(long index), wat het overgenomen lid public int[] F(long index)oplevert .

eindvoorbeeld

15.3.5 De nieuwe modifier

Een class_member_declaration mag een lid met dezelfde naam of handtekening declareren als een overgenomen lid. Wanneer dit gebeurt, wordt het afgeleide klasselid gezegd om het lid van de basisklasse te verbergen . Zie §7.7.2.3 voor een nauwkeurige specificatie van wanneer een lid een overgenomen lid verbergt.

Een overgenomen lid M wordt als beschikbaar beschouwd als M toegankelijk en er is geen ander overgenomen lid N dat al verborgen isM. Impliciet verbergen van een overgenomen lid wordt niet beschouwd als een fout, maar een compiler geeft een waarschuwing, tenzij de declaratie van het afgeleide klasselid een new modifier bevat om expliciet aan te geven dat het afgeleide lid bedoeld is om het basislid te verbergen. Als een of meer delen van een gedeeltelijke aangifte (§15.2.7) van een genest type de new wijzigingsfunctie bevatten, wordt er geen waarschuwing afgegeven als het geneste type een beschikbaar overgenomen lid verbergt.

Als een new wijzigingsfunctie is opgenomen in een declaratie die een beschikbaar overgenomen lid niet verbergt, wordt er een waarschuwing voor dat effect uitgegeven.

15.3.6 Toegangsmodifiers

Een Met uitzondering van de protected internal en private protected combinaties is het een compilatiefout om meer dan één wijzigingsfunctie voor toegang op te geven. Wanneer een class_member_declaration geen toegangsaanpassingen bevat, private wordt ervan uitgegaan.

15.3.7 Samenstellende typen

Typen die worden gebruikt in de declaratie van een lid, worden de samenstellende typen van dat lid genoemd. Mogelijke samenstellende typen zijn het type van een constante, veld, eigenschap, gebeurtenis of indexeerfunctie, het retourtype van een methode of operator en de parametertypen van een methode, indexeerfunctie, operator of instantieconstructor. De samenstellingstypen van een lid zijn ten minste zo toegankelijk als dat lid zelf (§7.5.5).

15.3.8 Statische leden en instantieleden

Leden van een klasse zijn statische leden of instantieleden.

Opmerking: Over het algemeen is het handig om statische leden te beschouwen als behorend tot klassen en exemplaarleden als behorend tot objecten (exemplaren van klassen). eindnotitie

Wanneer een veld, methode, eigenschap, gebeurtenis, operator of constructordeclaratie een static wijzigingsfunctie bevat, declareert het een statisch lid. Bovendien declareert een constante of typedeclaratie impliciet een statisch lid. Statische leden hebben de volgende kenmerken:

  • Wanneer in een member_access (M) van het formulier naar een statisch lid E.M wordt verwezen, E wordt een type aangeduid dat lid Mheeft . Het is een compilatietijdfout om E een exemplaar aan te geven.
  • Een statisch veld in een niet-algemene klasse identificeert precies één opslaglocatie. Ongeacht het aantal exemplaren van een niet-algemene klasse dat wordt gemaakt, is er maar één kopie van een statisch veld. Elk afzonderlijk gesloten samengesteld type (§8.4.3) heeft een eigen set statische velden, ongeacht het aantal exemplaren van het gesloten samengestelde type.
  • Een statisch functielid (methode, eigenschap, gebeurtenis, operator of constructor) werkt niet op een specifiek exemplaar en het is een compilatiefout om hiernaar te verwijzen in een dergelijk functielid.

Wanneer een veld, methode, eigenschap, gebeurtenis, indexeerfunctie, constructor of finalizer-declaratie geen statische wijzigingsfunctie bevat, declareert deze een exemplaarlid. (Een exemplaarlid wordt ook wel een niet-statisch lid genoemd.) Exemplaarleden hebben de volgende kenmerken:

  • Wanneer naar een exemplaarlid M wordt verwezen in een member_access (§12.8.7) van het formulier E.M, E wordt een exemplaar aangeduid van een type lid M. Het is een bindingstijdfout voor E om een type aan te geven.
  • Elk exemplaar van een klasse bevat een afzonderlijke set van alle exemplaarvelden van de klasse.
  • Een exemplaarfunctielid (methode, eigenschap, indexeerfunctie, exemplaarconstructor of finalizer) werkt op een bepaald exemplaar van de klasse en dit exemplaar kan worden geopend als this (§12.8.14).

Voorbeeld: In het volgende voorbeeld ziet u de regels voor toegang tot statische leden en exemplaren:

class Test
{
    int x;
    static int y;
    void F()
    {
        x = 1;               // Ok, same as this.x = 1
        y = 1;               // Ok, same as Test.y = 1
    }

    static void G()
    {
        x = 1;               // Error, cannot access this.x
        y = 1;               // Ok, same as Test.y = 1
    }

    static void Main()
    {
        Test t = new Test();
        t.x = 1;       // Ok
        t.y = 1;       // Error, cannot access static member through instance
        Test.x = 1;    // Error, cannot access instance member through type
        Test.y = 1;    // Ok
    }
}

De F methode laat zien dat in een exemplaarfunctielid een simple_name (§12.8.4) kan worden gebruikt voor toegang tot zowel instantieleden als statische leden. De G methode laat zien dat het in een statisch functielid een compilatiefout is voor toegang tot een exemplaarlid via een simple_name. De Main methode laat zien dat in een member_access (§12.8.7) exemplaarleden toegankelijk zijn via exemplaren en dat statische leden toegankelijk zijn via typen.

eindvoorbeeld

15.3.9 Geneste typen

15.3.9.1 Algemeen

Een type dat binnen een klasse of struct is gedeclareerd, wordt een genest type genoemd. Een type dat binnen een compilatie-eenheid of naamruimte wordt gedeclareerd, wordt een niet-genest type genoemd.

Voorbeeld: In het volgende voorbeeld:

class A
{
    class B
    {
        static void F()
        {
            Console.WriteLine("A.B.F");
        }
    }
}

klasse B is een genest type omdat het binnen klasse Awordt gedeclareerd en klasse A een niet-genest type is omdat deze wordt gedeclareerd binnen een compilatie-eenheid.

eindvoorbeeld

15.3.9.2 Volledig gekwalificeerde naam

De volledig gekwalificeerde naam (§7.8.3) voor een geneste typedeclaratie wordt S.N wanneer S de volledig gekwalificeerde naam is van de typedeclaratie waarin het type N wordt gedeclareerd en N is de niet-gekwalificeerde naam (§7.8.2) van de geneste typedeclaratie (inclusief generic_dimension_specifier (§12.8.18)).

15.3.9.3 Opgegeven toegankelijkheid

Niet-geneste typen kunnen toegankelijkheid hebben public of internal gedeclareerd en hebben internal standaard toegankelijkheid gedeclareerd. Geneste typen kunnen ook deze vormen van gedeclareerde toegankelijkheid hebben, plus een of meer extra vormen van gedeclareerde toegankelijkheid, afhankelijk van of het type een klasse of struct is:

  • Een genest type dat in een klasse wordt gedeclareerd, kan een van de toegestane soorten toegankelijkheid hebben en, net als andere klasseleden, standaard private toegankelijkheid gedeclareerd.
  • Een genest type dat in een struct wordt gedeclareerd, kan een van de drie vormen van gedeclareerde toegankelijkheid (public, internalof private) hebben en, net als andere structleden, standaard private toegankelijkheid gedeclareerd.

Voorbeeld: Het voorbeeld

public class List
{
    // Private data structure
    private class Node
    {
        public object Data;
        public Node? Next;

        public Node(object data, Node? next)
        {
            this.Data = data;
            this.Next = next;
        }
    }

    private Node? first = null;
    private Node? last = null;

    // Public interface
    public void AddToFront(object o) {...}
    public void AddToBack(object o) {...}
    public object RemoveFromFront() {...}
    public object RemoveFromBack() {...}
    public int Count { get {...} }
}

declareert een privé geneste klasse Node.

eindvoorbeeld

15.3.9.4 Verbergen

Een genest type kan een basislid verbergen (§7.7.2.2). De new wijzigingsfunctie (§15.3.5) is toegestaan op geneste typedeclaraties, zodat verbergen expliciet kan worden uitgedrukt.

Voorbeeld: Het voorbeeld

class Base
{
    public static void M()
    {
        Console.WriteLine("Base.M");
    }
}

class Derived: Base
{
    public new class M
    {
        public static void F()
        {
            Console.WriteLine("Derived.M.F");
        }
    }
}

class Test
{
    static void Main()
    {
        Derived.M.F();
    }
}

toont een geneste klasse M die de methode M verbergt die is gedefinieerd in Base.

eindvoorbeeld

15.3.9.5 deze toegang

Een genest type en het bijbehorende type hebben geen speciale relatie met betrekking tot this_access (§12.8.14). this In het bijzonder kan binnen een genest type niet worden gebruikt om te verwijzen naar exemplaarleden van het type dat het bevat. In gevallen waarin een geneste type toegang nodig heeft tot de exemplaarleden van het bijbehorende type, kan toegang worden geboden door het this exemplaar van het betreffende type op te geven als een constructorargument voor het geneste type.

Voorbeeld: Het volgende voorbeeld

class C
{
    int i = 123;
    public void F()
    {
        Nested n = new Nested(this);
        n.G();
    }

    public class Nested
    {
        C this_c;

        public Nested(C c)
        {
            this_c = c;
        }

        public void G()
        {
            Console.WriteLine(this_c.i);
        }
    }
}

class Test
{
    static void Main()
    {
        C c = new C();
        c.F();
    }
}

toont deze techniek. Een exemplaar van maakt een exemplaar van CNesteden geeft deze door aan Nestedde constructor van de constructor om volgende toegang te bieden tot Cde leden van het exemplaar.

eindvoorbeeld

15.3.9.6 Toegang tot privé- en beveiligde leden van het type met

Een genest type heeft toegang tot alle leden die toegankelijk zijn voor het bijbehorende type, inclusief leden van het type dat de toegankelijkheid heeft private en protected heeft gedeclareerd.

Voorbeeld: Het voorbeeld

class C
{
    private static void F() => Console.WriteLine("C.F");

    public class Nested
    {
        public static void G() => F();
    }
}

class Test
{
    static void Main() => C.Nested.G();
}

geeft een klasse C weer die een geneste klasse Nestedbevat. Binnen Nestedroept de methode G de statische methode F aan die is gedefinieerd in Cen F heeft privé toegankelijkheid gedeclareerd.

eindvoorbeeld

Een genest type heeft ook toegang tot beveiligde leden die zijn gedefinieerd in een basistype van het bijbehorende type.

Voorbeeld: In de volgende code

class Base
{
    protected void F() => Console.WriteLine("Base.F");
}

class Derived: Base
{
    public class Nested
    {
        public void G()
        {
            Derived d = new Derived();
            d.F(); // ok
        }
    }
}

class Test
{
    static void Main()
    {
        Derived.Nested n = new Derived.Nested();
        n.G();
    }
}

de geneste klasse Derived.Nested heeft toegang tot de beveiligde methode F die is gedefinieerd in Derivedde basisklasse, Basedoor een exemplaar van Derived.

eindvoorbeeld

15.3.9.7 Geneste typen in algemene klassen

Een algemene klassedeclaratie kan geneste typedeclaraties bevatten. De typeparameters van de insluitklasse kunnen worden gebruikt binnen de geneste typen. Een geneste typedeclaratie kan aanvullende typeparameters bevatten die alleen van toepassing zijn op het geneste type.

Elke typedeclaratie in een algemene klassedeclaratie is impliciet een algemene typedeclaratie. Bij het schrijven van een verwijzing naar een type dat is genest binnen een algemeen type, wordt het met het samengestelde type, met inbegrip van de bijbehorende typeargumenten, benoemd. Vanuit de buitenste klasse mag het geneste type echter zonder kwalificatie worden gebruikt; het exemplaartype van de buitenste klasse kan impliciet worden gebruikt bij het samenstellen van het geneste type.

Voorbeeld: Hieronder ziet u drie verschillende juiste manieren om te verwijzen naar een samengesteld type dat is gemaakt op basis van Inner; de eerste twee zijn equivalent:

class Outer<T>
{
    class Inner<U>
    {
        public static void F(T t, U u) {...}
    }

    static void F(T t)
    {
        Outer<T>.Inner<string>.F(t, "abc");    // These two statements have
        Inner<string>.F(t, "abc");             // the same effect
        Outer<int>.Inner<string>.F(3, "abc");  // This type is different
        Outer.Inner<string>.F(t, "abc");       // Error, Outer needs type arg
    }
}

eindvoorbeeld

Hoewel het een slechte programmeerstijl is, kan een typeparameter in een genest type een lid of typeparameter verbergen die in het buitenste type is gedeclareerd.

Voorbeeld:

class Outer<T>
{
    class Inner<T>                                  // Valid, hides Outer's T
    {
        public T t;                                 // Refers to Inner's T
    }
}

eindvoorbeeld

15.3.10 Gereserveerde ledennamen

15.3.10.1 Algemeen

Om de onderliggende C#-uitvoering te vergemakkelijken, behoudt de implementatie voor elke bronliddeclaratie die een eigenschap, gebeurtenis of indexeerfunctie is, twee methodehandtekeningen op basis van het soort liddeclaratie, de naam en het type ervan (§15.3.10.2, §15.3.10.3, §15.3.10.4). Het is een compilatiefout voor een programma om een lid te declareren waarvan de handtekening overeenkomt met een handtekening die door een lid in hetzelfde bereik is gedeclareerd, zelfs als de onderliggende runtime-implementatie geen gebruik maakt van deze reserveringen.

De gereserveerde namen voeren geen declaraties in, dus ze nemen niet deel aan het opzoeken van leden. De bijbehorende handtekeningen voor gereserveerde methoden van een verklaring nemen echter wel deel aan overname (§15.3.4) en kunnen worden verborgen met de new wijzigingsfunctie (§15.3.5).

Opmerking: De reservering van deze namen dient drie doeleinden:

  1. Als u wilt toestaan dat de onderliggende implementatie een gewone id als methodenaam gebruikt voor het ophalen of instellen van toegang tot de C#-taalfunctie.
  2. Als u wilt toestaan dat andere talen samenwerken met een gewone id als methodenaam voor het ophalen of instellen van toegang tot de C#-taalfunctie.
  3. Om ervoor te zorgen dat de bron die door de ene conforme compiler wordt geaccepteerd, wordt geaccepteerd door een andere, door de specifieke kenmerken van gereserveerde lidnamen consistent te maken voor alle C#-implementaties.

eindnotitie

De verklaring van een finalizer (§15.13) zorgt er ook voor dat een handtekening wordt gereserveerd (§15.3.10.5).

Bepaalde namen zijn gereserveerd voor gebruik als operatormethodenamen (§15.3.10.6).

15.3.10.2 Ledennamen gereserveerd voor eigenschappen

Voor een eigenschap P (§15.7) van het type Tzijn de volgende handtekeningen gereserveerd:

T get_P();
void set_P(T value);

Beide handtekeningen zijn gereserveerd, zelfs als de eigenschap alleen-lezen of alleen-schrijven is.

Voorbeeld: In de volgende code

class A
{
    public int P
    {
        get => 123;
    }
}

class B : A
{
    public new int get_P() => 456;

    public new void set_P(int value)
    {
    }
}

class Test
{
    static void Main()
    {
        B b = new B();
        A a = b;
        Console.WriteLine(a.P);
        Console.WriteLine(b.P);
        Console.WriteLine(b.get_P());
    }
}

Een klasse A definieert een alleen-lezen eigenschap P, waardoor handtekeningen voor get_P en set_P methoden worden reserveren. A de klasse B is afgeleid van A en verbergt beide gereserveerde handtekeningen. In het voorbeeld wordt de uitvoer geproduceerd:

123
123
456

eindvoorbeeld

15.3.10.3 Leden die zijn gereserveerd voor gebeurtenissen

Voor een gebeurtenis E (§15.8) van het type gemachtigde Tzijn de volgende handtekeningen gereserveerd:

void add_E(T handler);
void remove_E(T handler);

15.3.10.4 Leden die zijn gereserveerd voor indexeerfuncties

Voor een indexeerfunctie (§15.9) van het type T met parameterlijst Lzijn de volgende handtekeningen gereserveerd:

T get_Item(L);
void set_Item(L, T value);

Beide handtekeningen zijn gereserveerd, zelfs als de indexeerfunctie alleen-lezen of alleen-schrijven is.

Verder is de naam Item van het lid gereserveerd.

15.3.10.5 Ledennamen gereserveerd voor finalizers

Voor een klasse met een finalizer (§15.13) is de volgende handtekening gereserveerd:

void Finalize();

15.3.10.6 Methodenamen gereserveerd voor operators

De volgende methodenamen zijn gereserveerd. Hoewel velen overeenkomstige operators in deze specificatie hebben, zijn sommige gereserveerd voor gebruik door toekomstige versies, terwijl sommige zijn gereserveerd voor interop met andere talen.

Methodenaam C#-operator
op_Addition + (binair)
op_AdditionAssignment (gereserveerd)
op_AddressOf (gereserveerd)
op_Assign (gereserveerd)
op_BitwiseAnd & (binair)
op_BitwiseAndAssignment (gereserveerd)
op_BitwiseOr \|
op_BitwiseOrAssignment (gereserveerd)
op_CheckedAddition (gereserveerd voor toekomstig gebruik)
op_CheckedDecrement (gereserveerd voor toekomstig gebruik)
op_CheckedDivision (gereserveerd voor toekomstig gebruik)
op_CheckedExplicit (gereserveerd voor toekomstig gebruik)
op_CheckedIncrement (gereserveerd voor toekomstig gebruik)
op_CheckedMultiply (gereserveerd voor toekomstig gebruik)
op_CheckedSubtraction (gereserveerd voor toekomstig gebruik)
op_CheckedUnaryNegation (gereserveerd voor toekomstig gebruik)
op_Comma (gereserveerd)
op_Decrement -- (voorvoegsel en voorvoegsel)
op_Division /
op_DivisionAssignment (gereserveerd)
op_Equality ==
op_ExclusiveOr ^
op_ExclusiveOrAssignment (gereserveerd)
op_Explicit expliciete (vermaling) dwang
op_False false
op_GreaterThan >
op_GreaterThanOrEqual >=
op_Implicit impliciete (widening) dwang
op_Increment ++ (voorvoegsel en voorvoegsel)
op_Inequality !=
op_LeftShift <<
op_LeftShiftAssignment (gereserveerd)
op_LessThan <
op_LessThanOrEqual <=
op_LogicalAnd (gereserveerd)
op_LogicalNot !
op_LogicalOr (gereserveerd)
op_MemberSelection (gereserveerd)
op_Modulus %
op_ModulusAssignment (gereserveerd)
op_MultiplicationAssignment (gereserveerd)
op_Multiply * (binair)
op_OnesComplement ~
op_PointerDereference (gereserveerd)
op_PointerToMemberSelection (gereserveerd)
op_RightShift >>
op_RightShiftAssignment (gereserveerd)
op_SignedRightShift (gereserveerd)
op_Subtraction - (binair)
op_SubtractionAssignment (gereserveerd)
op_True true
op_UnaryNegation - (unaire)
op_UnaryPlus + (unaire)
op_UnsignedRightShift (gereserveerd voor toekomstig gebruik)
op_UnsignedRightShiftAssignment (gereserveerd)

15,4 Constanten

Een constante is een klasselid dat een constante waarde vertegenwoordigt: een waarde die kan worden berekend tijdens het compileren. Een constant_declaration introduceert een of meer constanten van een bepaald type.

constant_declaration
    : attributes? constant_modifier* 'const' type constant_declarators ';'
    ;

constant_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    ;

Een constant_declaration kan een set kenmerken (§22), een new modifier (§15.3.5) en een van de toegestane soorten toegankelijkheid bevatten (§15.3.6). De kenmerken en modifiers zijn van toepassing op alle leden die door de constant_declaration zijn gedeclareerd. Hoewel constanten als statische leden worden beschouwd, is een constant_declaration geen van beide vereist of is een static wijzigingsfunctie toegestaan. Het is een fout dat dezelfde wijziging meerdere keren wordt weergegeven in een constante declaratie.

Het type van een constant_declaration geeft het type van de leden aan die door de verklaring zijn ingevoerd. Het type wordt gevolgd door een lijst met constant_declarators (§13.6.3), die elk een nieuw lid introduceert. Een constant_declarator bestaat uit een id die het lid een naam geeft, gevolgd door een "=" token, gevolgd door een constant_expression (§12.23) die de waarde van het lid geeft.

Het type dat in een constante aangifte is opgegeven, is sbyte, , byteshort, ushort, , intuintlongulongcharfloatdoubledecimalbool, stringeen enum_type of een reference_type. Elke constant_expression levert een waarde op van het doeltype of van een type dat kan worden geconverteerd naar het doeltype door een impliciete conversie (§10.2).

Het type constante moet minstens zo toegankelijk zijn als de constante zelf (§7.5.5).

De waarde van een constante wordt verkregen in een expressie met behulp van een simple_name (§12.8.4) of een member_access (§12.8.7).

Een constante kan zelf deelnemen aan een constant_expression. Een constante kan dus worden gebruikt in elke constructie waarvoor een constant_expression is vereist.

Opmerking: voorbeelden van dergelijke constructies zijn case labels, goto case instructies, enum liddeclaraties, kenmerken en andere constante declaraties. eindnotitie

Opmerking: Zoals beschreven in §12.23, is een constant_expression een expressie die tijdens het compileren volledig kan worden geëvalueerd. Aangezien de enige manier om een niet-null-waarde van een andere reference_type toe te passen dan string de new operator, en omdat de new operator niet is toegestaan in een constant_expression, is de enige mogelijke waarde voor constanten van reference_types anders dan string is null. eindnotitie

Wanneer een symbolische naam voor een constante waarde gewenst is, maar wanneer het type van die waarde niet is toegestaan in een constante declaratie, of wanneer de waarde niet kan worden berekend tijdens het compileren door een constant_expression, kan in plaats daarvan een leesveld (§15.5.3) worden gebruikt.

Opmerking: De versiebeheersemantiek van const en readonly verschillen (§15.5.3.3). eindnotitie

Een constante declaratie die meerdere constanten declareert, is gelijk aan meerdere declaraties van enkelvoudige constanten met dezelfde kenmerken, modifiers en type.

Voorbeeld:

class A
{
    public const double X = 1.0, Y = 2.0, Z = 3.0;
}

is gelijk aan

class A
{
    public const double X = 1.0;
    public const double Y = 2.0;
    public const double Z = 3.0;
}

eindvoorbeeld

Constanten zijn toegestaan om afhankelijk te zijn van andere constanten binnen hetzelfde programma, zolang de afhankelijkheden niet van een circulaire aard zijn.

Voorbeeld: In de volgende code

class A
{
    public const int X = B.Z + 1;
    public const int Y = 10;
}

class B
{
    public const int Z = A.Y + 1;
}

een compiler moet eerst A.Yevalueren, vervolgens B.Zevalueren en ten slotte A.Xevalueren, de waarden 10, 11en 12produceren.

eindvoorbeeld

Constante declaraties kunnen afhankelijk zijn van constanten van andere programma's, maar dergelijke afhankelijkheden zijn slechts in één richting mogelijk.

Voorbeeld: Verwijzend naar het bovenstaande voorbeeld, als A en B zijn gedeclareerd in afzonderlijke programma's, zou het mogelijk A.X zijn om afhankelijk te zijn B.Zvan , maar B.Z kan dan niet tegelijkertijd afhankelijk A.Yzijn van . eindvoorbeeld

15,5 Velden

15.5.1 Algemeen

Een veld is een lid dat een variabele vertegenwoordigt die is gekoppeld aan een object of klasse. Een field_declaration introduceert een of meer velden van een bepaald type.

field_declaration
    : attributes? field_modifier* type variable_declarators ';'
    ;

field_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'static'
    | 'readonly'
    | 'volatile'
    | unsafe_modifier   // unsafe code support
    ;

variable_declarators
    : variable_declarator (',' variable_declarator)*
    ;

variable_declarator
    : identifier ('=' variable_initializer)?
    ;

unsafe_modifier (§23.2) is alleen beschikbaar in onveilige code (§23).

Een field_declaration kan een set kenmerken (§22), een new modifier (§15.3.5), een geldige combinatie van de vier toegangsmodifiers (§15.3.6) en een static modifier (§15.5.2) bevatten. Daarnaast kan een field_declaration een modifier (§15.5.3) of een readonly modifier (§15.5.4) bevatten, maar niet beide.volatile De kenmerken en modifiers zijn van toepassing op alle leden die door de field_declaration zijn gedeclareerd. Het is een fout dat dezelfde wijziging meerdere keren in een field_declaration wordt weergegeven.

Het type van een field_declaration geeft het type van de leden aan die door de verklaring zijn ingevoerd. Het type wordt gevolgd door een lijst met variable_declarators, die elk een nieuw lid introduceert. Een variable_declarator bestaat uit een id die het lid een naam geeft, eventueel gevolgd door een "="-token en een variable_initializer (§15.5.6) die de oorspronkelijke waarde van dat lid geeft.

Het type veld moet minstens zo toegankelijk zijn als het veld zelf (§7.5.5).

De waarde van een veld wordt verkregen in een expressie met behulp van een simple_name (§12.8.4), een member_access (§12.8.7) of een base_access (§12.8.15). De waarde van een niet-lezen veld wordt gewijzigd met behulp van een toewijzing (§12.21). De waarde van een niet-lezen veld kan zowel worden verkregen als gewijzigd met behulp van operatoren voor incrementele en degradatie van het voorvoegsel (§12.8.16) en operatoren voor voorvoegsels en voorvoegsels (§12.9.6).

Een velddeclaratie die meerdere velden declareert, is gelijk aan meerdere declaraties van enkele velden met dezelfde kenmerken, modifiers en type.

Voorbeeld:

class A
{
    public static int X = 1, Y, Z = 100;
}

is gelijk aan

class A
{
    public static int X = 1;
    public static int Y;
    public static int Z = 100;
}

eindvoorbeeld

15.5.2 Statische velden en instantievelden

Wanneer een velddeclaratie een static wijzigingsfunctie bevat, zijn de velden die door de declaratie worden geïntroduceerd statische velden. Als er geen static wijzigingsfunctie aanwezig is, zijn de velden die door de declaratie worden ingevoerd, exemplaarvelden. Statische velden en instantievelden zijn twee van de verschillende soorten variabelen (§9) die door C# worden ondersteund en soms worden ze respectievelijk statische variabelen en instantievariabelen genoemd.

Zoals uitgelegd in §15.3.8, bevat elk exemplaar van een klasse een volledige set exemplaarvelden van de klasse, terwijl er slechts één set statische velden is voor elk niet-algemeen of gesloten samengesteld type, ongeacht het aantal exemplaren van het klasse- of gesloten samengestelde type.

15.5.3 Leesvelden

15.5.3.1 Algemeen

Wanneer een field_declaration een wijzigingsfunctie bevat, zijn de velden die door de declaratie worden .readonly Directe toewijzingen voor alleen-lezen velden kunnen alleen voorkomen als onderdeel van die declaratie of in een instantieconstructor of statische constructor in dezelfde klasse. (Een alleen-lezen veld kan meerdere keren worden toegewezen in deze contexten.) Met name directe toewijzingen aan een alleen-lezenveld zijn alleen toegestaan in de volgende contexten:

  • In het variable_declarator dat het veld introduceert (door een variable_initializer op te geven in de declaratie).
  • Voor een exemplaarveld, in de instantieconstructors van de klasse die de velddeclaratie bevat; voor een statisch veld, in de statische constructor van de klasse die de velddeclaratie bevat. Dit zijn ook de enige contexten waarin het geldig is om een alleen-lezen veld door te geven als uitvoer- of verwijzingsparameter.

Een poging om een alleen-lezen veld toe te wijzen of door te geven als een uitvoer- of verwijzingsparameter in een andere context, is een compilatiefout.

15.5.3.2 Statische leesvelden gebruiken voor constanten

Een statisch alleen-lezen veld is handig wanneer een symbolische naam voor een constante waarde gewenst is, maar wanneer het type van de waarde niet is toegestaan in een const-declaratie of wanneer de waarde niet tijdens het compileren kan worden berekend.

Voorbeeld: In de volgende code

public class Color
{
    public static readonly Color Black = new Color(0, 0, 0);
    public static readonly Color White = new Color(255, 255, 255);
    public static readonly Color Red = new Color(255, 0, 0);
    public static readonly Color Green = new Color(0, 255, 0);
    public static readonly Color Blue = new Color(0, 0, 255);

    private byte red, green, blue;

    public Color(byte r, byte g, byte b)
    {
        red = r;
        green = g;
        blue = b;
    }
}

de Black, White, , Reden Greenleden Blue kunnen niet worden gedeclareerd als const-leden omdat hun waarden niet kunnen worden berekend tijdens het compileren. Het declareren static readonly ervan heeft echter veel hetzelfde effect.

eindvoorbeeld

15.5.3.3 Versiebeheer van constanten en statische leesvelden

Constanten en alleen-lezenvelden hebben verschillende binaire versiebeheersemantiek. Wanneer een expressie verwijst naar een constante, wordt de waarde van de constante verkregen tijdens het compileren, maar wanneer een expressie verwijst naar een alleen-lezen veld, wordt de waarde van het veld pas verkregen wanneer de runtime is uitgevoerd.

Voorbeeld: Overweeg een toepassing die bestaat uit twee afzonderlijke programma's:

namespace Program1
{
    public class Utils
    {
        public static readonly int x = 1;
    }
}

en

namespace Program2
{
    class Test
    {
        static void Main()
        {
            Console.WriteLine(Program1.Utils.X);
        }
    }
}

De Program1 en Program2 naamruimten geven twee programma's aan die afzonderlijk zijn gecompileerd. Omdat Program1.Utils.X deze wordt gedeclareerd als een static readonly veld, is de waardeuitvoer door de Console.WriteLine instructie niet bekend tijdens het compileren, maar wordt deze eerder verkregen tijdens runtime. Als de waarde dus X wordt gewijzigd en Program1 opnieuw wordt gecompileerd, levert de Console.WriteLine instructie de nieuwe waarde op, zelfs als Program2 deze niet opnieuw wordt gecompileerd. Als X het echter een constante was, zou de waarde X zijn verkregen op het moment Program2 dat deze werd gecompileerd en zou deze niet worden beïnvloed door wijzigingen totdat Program1Program2 deze opnieuw is gecompileerd.

eindvoorbeeld

15.5.4 Vluchtige velden

Wanneer een bevat, zijn de velden die door die verklaring worden volatile. Voor niet-vluchtige velden kunnen optimalisatietechnieken die instructies herschikken, leiden tot onverwachte en onvoorspelbare resultaten in programma's met meerdere threads die toegang hebben tot velden zonder synchronisatie, zoals die worden geleverd door de lock_statement (§13.13). Deze optimalisaties kunnen worden uitgevoerd door de compiler, door het runtimesysteem of door hardware. Voor vluchtige velden zijn dergelijke optimalisaties voor opnieuw ordenen beperkt:

  • Een leesbewerking van een vluchtig veld wordt een vluchtige leesbewerking genoemd. Een vluchtige leesbewerking heeft "semantiek verkrijgen"; Dat wil gezegd, het is gegarandeerd dat het vóór alle verwijzingen naar het geheugen die plaatsvinden na deze in de instructiereeks.
  • Een schrijfbewerking van een vluchtig veld wordt een vluchtige schrijfbewerking genoemd. Een vluchtige schrijfbewerking heeft 'release-semantiek'; Dat wil gezegd, het is gegarandeerd dat het gebeurt na eventuele geheugenverwijzingen voorafgaand aan de schrijfinstructie in de instructiereeks.

Deze beperkingen zorgen ervoor dat alle threads vluchtige schrijfbewerkingen observeren die worden uitgevoerd door een andere thread in de volgorde waarin ze zijn uitgevoerd. Een conforme implementatie is niet vereist om één totale volgorde van vluchtige schrijfbewerkingen te bieden, zoals te zien is vanuit alle threads van uitvoering. Het type vluchtig veld moet een van de volgende zijn:

  • Een reference_type.
  • Een type_parameter die bekend staat als referentietype (§15.2.5).
  • Het type byte, sbyte, , short, ushort, int, , uint, char, float, bool, , , System.IntPtrof System.UIntPtr.
  • Een

Voorbeeld: Het voorbeeld

class Test
{
    public static int result;
    public static volatile bool finished;

    static void Thread2()
    {
        result = 143;
        finished = true;
    }

    static void Main()
    {
        finished = false;

        // Run Thread2() in a new thread
        new Thread(new ThreadStart(Thread2)).Start();    

        // Wait for Thread2() to signal that it has a result
        // by setting finished to true.
        for (;;)
        {
            if (finished)
            {
                Console.WriteLine($"result = {result}");
                return;
            }
        }
    }
}

produceert de uitvoer:

result = 143

In dit voorbeeld start de methode Main een nieuwe thread waarmee de methode Thread2wordt uitgevoerd. Met deze methode wordt een waarde opgeslagen in een niet-vluchtig veld met de naam resulten slaat true deze vervolgens op in het vluchtige veld finished. De hoofdthread wacht totdat het veld finished is ingesteld trueop en leest vervolgens het veld result. Sindsdien finished is gedeclareerd volatile, leest de hoofdthread de waarde 143 uit het veld result. Als het veld finished niet is gedeclareerd volatile, is het toegestaan dat het archief result zichtbaar is voor de hoofdthread na het opslaan naar finished, en dus voor de hoofdthread om de waarde 0 uit het veld resultte lezen. Declaratie finished als een volatile veld voorkomt dergelijke inconsistentie.

eindvoorbeeld

15.5.5 Veld initialisatie

De oorspronkelijke waarde van een veld, of het nu een statisch veld of een exemplaarveld is, is de standaardwaarde (§9,3) van het veldtype. Het is niet mogelijk om de waarde van een veld te observeren voordat deze standaard initialisatie is opgetreden, en een veld is dus nooit 'niet geïnitialiseerd'.

Voorbeeld: Het voorbeeld

class Test
{
    static bool b;
    int i;

    static void Main()
    {
        Test t = new Test();
        Console.WriteLine($"b = {b}, i = {t.i}");
    }
}

produceert de uitvoer

b = False, i = 0

omdat b en i beide automatisch worden geïnitialiseerd op standaardwaarden.

eindvoorbeeld

15.5.6 Variabelen initializers

15.5.6.1 Algemeen

Velddeclaraties kunnen variable_initializer zijn. Voor statische velden komen variabele initialisaties overeen met toewijzingsinstructies die worden uitgevoerd tijdens de initialisatie van de klasse. Bijvoorbeeld velden, variabele initializers komen overeen met toewijzingsinstructies die worden uitgevoerd wanneer een exemplaar van de klasse wordt gemaakt.

Voorbeeld: Het voorbeeld

class Test
{
    static double x = Math.Sqrt(2.0);
    int i = 100;
    string s = "Hello";

    static void Main()
    {
        Test a = new Test();
        Console.WriteLine($"x = {x}, i = {a.i}, s = {a.s}");
    }
}

produceert de uitvoer

x = 1.4142135623730951, i = 100, s = Hello

omdat een toewijzing x plaatsvindt wanneer statische veld initialisaties worden uitgevoerd en toewijzingen uitvoeren i en s optreden wanneer de initialisatiefunctie van het exemplaarveld wordt uitgevoerd.

eindvoorbeeld

De standaardwaarde-initialisatie die wordt beschreven in §15.5.5 vindt plaats voor alle velden, inclusief velden met variabele initializers. Wanneer een klasse wordt geïnitialiseerd, worden alle statische velden in die klasse dus eerst geïnitialiseerd op hun standaardwaarden en worden de statische veldinitialiseerde initialisaties uitgevoerd in tekstvolgorde. Wanneer een exemplaar van een klasse wordt gemaakt, worden alle exemplaarvelden in dat exemplaar eerst geïnitialiseerd op de standaardwaarden en worden de initialisaties van het exemplaarveld uitgevoerd in tekstvolgorde. Wanneer er velddeclaraties in meerdere gedeeltelijke typedeclaraties voor hetzelfde type zijn, wordt de volgorde van de onderdelen niet opgegeven. Binnen elk deel worden de veld initialisaties echter op volgorde uitgevoerd.

Het is mogelijk dat statische velden met variabele initializers worden waargenomen in de standaardwaardestatus.

Voorbeeld: Dit wordt echter sterk afgeraden als stijl. Het voorbeeld

class Test
{
    static int a = b + 1;
    static int b = a + 1;

    static void Main()
    {
        Console.WriteLine($"a = {a}, b = {b}");
    }
}

vertoont dit gedrag. Ondanks de kringdefinities van a en bis het programma geldig. Dit resulteert in de uitvoer

a = 1, b = 2

omdat de statische velden a en b worden geïnitialiseerd naar 0 (de standaardwaarde voor int) voordat hun initializers worden uitgevoerd. Wanneer de initialisatiefunctie voor a uitvoeringen wordt uitgevoerd, is de waarde van b nul en dus a geïnitialiseerd op 1. Wanneer de initialisatiefunctie voor b uitvoeringen is, is de waarde van een al 1, en dus b geïnitialiseerd op 2.

eindvoorbeeld

15.5.6.2 Initialisatie van statisch veld

De initialisaties van statische veldvariabelen van een klasse komen overeen met een reeks toewijzingen die worden uitgevoerd in de tekstvolgorde waarin ze worden weergegeven in de klassedeclaratie (§15.5.6.1). Binnen een gedeeltelijke klasse wordt de betekenis van "tekstvolgorde" opgegeven door §15.5.6.1. Als er een statische constructor (§15.12) aanwezig is in de klasse, vindt de uitvoering van de statische veld initialisaties direct plaats voordat die statische constructor wordt uitgevoerd. Anders worden de initialisaties van statische velden uitgevoerd op een implementatieafhankelijke tijd vóór het eerste gebruik van een statisch veld van die klasse.

Voorbeeld: Het voorbeeld

class Test
{
    static void Main()
    {
        Console.WriteLine($"{B.Y} {A.X}");
    }

    public static int F(string s)
    {
        Console.WriteLine(s);
        return 1;
    }
}

class A
{
    public static int X = Test.F("Init A");
}

class B
{
    public static int Y = Test.F("Init B");
}

kan de uitvoer produceren:

Init A
Init B
1 1

of de uitvoer:

Init B
Init A
1 1

omdat de uitvoering van Xde initialisatiefunctie en Yinitialisatiefunctie in beide volgordes kunnen optreden; ze zijn alleen beperkt voordat de verwijzingen naar deze velden worden uitgevoerd. In het voorbeeld:

class Test
{
    static void Main()
    {
        Console.WriteLine($"{B.Y} {A.X}");
    }

    public static int F(string s)
    {
        Console.WriteLine(s);
        return 1;
    }
}

class A
{
    static A() {}
    public static int X = Test.F("Init A");
}

class B
{
    static B() {}
    public static int Y = Test.F("Init B");
}

de uitvoer is:

Init B
Init A
1 1

omdat de regels voor het uitvoeren van statische constructors (zoals gedefinieerd in §15.12

eindvoorbeeld

15.5.6.3 Instantieveld initialisatie

De initialisaties van de instantieveldvariabelen van een klasse komen overeen met een reeks toewijzingen die direct worden uitgevoerd bij de invoer van een van de exemplaarconstructors (§15.11.3) van die klasse. Binnen een gedeeltelijke klasse wordt de betekenis van "tekstvolgorde" opgegeven door §15.5.6.1. De variabele initializers worden uitgevoerd in de tekstvolgorde waarin ze worden weergegeven in de klassedeclaratie (§15.5.6.1). Het proces voor het maken en initialiseren van het klasse-exemplaar wordt verder beschreven in §15.11.

Een variabele initialisatiefunctie voor een exemplaarveld kan niet verwijzen naar het exemplaar dat wordt gemaakt. Het is dus een compilatiefout om te verwijzen in een variabele initialisatiefunctie, omdat het een compilatiefout is voor een variabele initializer om te verwijzen this naar een exemplaarlid via een simple_name.

Voorbeeld: In de volgende code

class A
{
    int x = 1;
    int y = x + 1;     // Error, reference to instance member of this
}

de variabele initialisatiefunctie voor y resultaten in een compileertijdfout omdat deze verwijst naar een lid van het exemplaar dat wordt gemaakt.

eindvoorbeeld

15.6 Methoden

15.6.1 Algemeen

Een methode is een lid dat een berekening of actie implementeert die kan worden uitgevoerd door een object of klasse. Methoden worden gedeclareerd met behulp van method_declaration s:

method_declaration
    : attributes? method_modifiers return_type method_header method_body
    | attributes? ref_method_modifiers ref_kind ref_return_type method_header
      ref_method_body
    ;

method_modifiers
    : method_modifier* 'partial'?
    ;

ref_kind
    : 'ref'
    | 'ref' 'readonly'
    ;

ref_method_modifiers
    : ref_method_modifier*
    ;

method_header
    : member_name '(' parameter_list? ')'
    | member_name type_parameter_list '(' parameter_list? ')'
      type_parameter_constraints_clause*
    ;

method_modifier
    : ref_method_modifier
    | 'async'
    ;

ref_method_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'static'
    | 'virtual'
    | 'sealed'
    | 'override'
    | 'abstract'
    | 'extern'
    | unsafe_modifier   // unsafe code support
    ;

return_type
    : ref_return_type
    | 'void'
    ;

ref_return_type
    : type
    ;

member_name
    : identifier
    | interface_type '.' identifier
    ;

method_body
    : block
    | '=>' null_conditional_invocation_expression ';'
    | '=>' expression ';'
    | ';'
    ;

ref_method_body
    : block
    | '=>' 'ref' variable_reference ';'
    | ';'
    ;

Grammaticanotities:

  • unsafe_modifier (§23.2) is alleen beschikbaar in onveilige code (§23).
  • bij de erkenning van een method_body indien zowel de alternatieven voor de null_conditional_invocation_expression als de expressie van toepassing zijn, wordt de vroegere gekozen.

Opmerking: de overlappende en prioriteit tussen de alternatieven hier is uitsluitend bedoeld voor beschrijvend gemak. De grammaticaregels kunnen worden uitgewerkt om de overlapping te verwijderen. ANTLR en andere grammaticasystemen gebruiken hetzelfde gemak, zodat method_body de opgegeven semantiek automatisch heeft. eindnotitie

Een method_declaration kan een set kenmerken (§22) en een van de toegestane soorten toegankelijkheid bevatten (§15.3.6), de new (§15.3.5), static (§15.6.3), (§15.6.3)virtual 6.4), override (§15.6.5), sealed (§15.6.6), abstract (§15.6.7), extern (§15.6.8) en async (§15.15) modifiers.

Een verklaring heeft een geldige combinatie van modifiers als aan alle volgende voorwaarden wordt voldaan:

  • De verklaring bevat een geldige combinatie van toegangsmodifiers (§15.3.6).
  • De declaratie bevat niet meerdere keren dezelfde wijzigingsfunctie.
  • De verklaring bevat ten hoogste een van de volgende modifiers: static, virtualen override.
  • De verklaring bevat ten hoogste een van de volgende modifiers: new en override.
  • Als de declaratie de abstract wijzigingsfunctie bevat, bevat de declaratie geen van de volgende modifiers: static, virtual, sealedof extern.
  • Als de declaratie de private wijzigingsfunctie bevat, bevat de declaratie geen van de volgende modifiers: virtual, overrideof abstract.
  • Als de declaratie de sealed wijzigingsfunctie bevat, bevat de declaratie ook de override wijzigingsfunctie.
  • Als de declaratie de partial wijzigingsfunctie bevat, bevat deze geen van de volgende modifiers: new, , , publicprotected, internal, privatevirtual, sealed, , override, , of abstractextern.

Methoden worden geclassificeerd op basis van wat, indien van toepassing, ze retourneren:

  • Als ref deze aanwezig is, wordt de methode geretourneerd op basis van verw en wordt een variabeleverwijzing geretourneerd, die optioneel alleen-lezen is;
  • Als return_type is, retourneert de methode en retourneert deze geen voidwaarde;
  • Anders wordt de methode geretourneerd op waarde en wordt een waarde geretourneerd.

De return_type van een declaratie van een methode returns-by-value of returns-no-value geeft het type van het resultaat op, indien van toepassing, geretourneerd door de methode. Alleen een methode zonder waarde retourneert mag de partial wijzigingsfunctie (§15.6.9) bevatten. Als de declaratie de async wijzigingsfunctie bevat, moet return_type zijn void of de methode retourneert per waarde en is het retourtype een taaktype (§15.15.11).

De ref_return_type van een declaratie van de methode returns-by-ref geeft het type van de variabele aan waarnaar wordt verwezen door de variable_reference geretourneerd door de methode.

Een algemene methode is een methode waarvan de declaratie een type_parameter_list bevat. Hiermee geeft u de typeparameters voor de methode op. De optionele type_parameter_constraints_clause sgeven de beperkingen voor de typeparameters op.

Een algemeen method_declaration voor een expliciete implementatie van interfaceleden heeft geen type_parameter_constraints_clauses; de declaratie neemt eventuele beperkingen over van de beperkingen op de interfacemethode.

Op dezelfde manier heeft een methodedeclaratie met de override modifier geen type_parameter_constraints_clauseen worden de beperkingen van de typeparameters van de methode overgenomen van de virtuele methode die wordt overschreven.

De member_name geeft de naam van de methode op. Tenzij de methode een expliciete implementatie van interfaceleden is (§18.6.2), is de member_name gewoon een id.

Voor een expliciete implementatie van interfaceleden bestaat de member_name uit een interface_type gevolgd door een '.' en een id. In dit geval omvat de verklaring geen andere modifiers dan (mogelijk) extern of async.

De optionele parameter_list geeft de parameters van de methode op (§15.6.2).

De return_type of ref_return_type en elk van de typen waarnaar in het parameter_list van een methode wordt verwezen, moet ten minste zo toegankelijk zijn als de methode zelf (§7.5.5).

De method_body van een methode returns-by-value of returns-no-value is ofwel een puntkomma, een bloktekst of een expressietekst. Een bloktekst bestaat uit een blok, waarmee de instructies worden opgegeven die moeten worden uitgevoerd wanneer de methode wordt aangeroepen. Een expressietekst bestaat uit =>, gevolgd door een null_conditional_invocation_expression of expressie, en een puntkomma, en geeft één expressie aan die moet worden uitgevoerd wanneer de methode wordt aangeroepen.

Voor abstracte en externe methoden bestaat de method_body uit een puntkomma. Voor gedeeltelijke methoden kan de method_body bestaan uit een puntkomma, een bloktekst of een expressietekst. Voor alle andere methoden is de method_body een bloktekst of een expressietekst.

Indien de method_body uit een puntkomma bestaat, mag de verklaring de async wijzigingsfunctie niet bevatten.

De ref_method_body van een methode returns-by-ref is ofwel een puntkomma, een bloktekst of een expressietekst. Een bloktekst bestaat uit een blok, waarmee de instructies worden opgegeven die moeten worden uitgevoerd wanneer de methode wordt aangeroepen. Een expressietekst bestaat uit =>, gevolgd door ref, een variable_reference en een puntkomma, en geeft één variable_reference aan om te evalueren wanneer de methode wordt aangeroepen.

Voor abstracte en externe methoden bestaat de ref_method_body uit een puntkomma; voor alle andere methoden is de ref_method_body ofwel een bloktekst of een expressietekst.

De naam, het aantal typeparameters en de parameterlijst van een methode definiëren de handtekening (§7.6) van de methode. Met name de handtekening van een methode bestaat uit de naam, het aantal parameters van het type en het aantal, parameter_mode_modifier s (§15.6.2.1) ende typen parameters. Het retourtype maakt geen deel uit van de handtekening van een methode, noch de namen van de parameters, de namen van de typeparameters of de beperkingen. Wanneer een parametertype verwijst naar een typeparameter van de methode, wordt de rangtelpositie van de typeparameter (niet de naam van de typeparameter) gebruikt voor gelijkwaardigheid van het type.

De naam van een methode verschilt van de namen van alle andere niet-methoden die in dezelfde klasse zijn gedeclareerd. Bovendien verschilt de handtekening van een methode van de handtekeningen van alle andere methoden die in dezelfde klasse zijn aangegeven, en twee methoden die in dezelfde klasse zijn gedeclareerd, mogen geen handtekeningen hebben die uitsluitend verschillen van in, outen ref.

De type_parameter van de methode vallen binnen het bereik van de method_declaration en kunnen worden gebruikt om typen te vormen in return_type of ref_return_type, method_body of ref_method_body, en type_parameter_constraints_clausemaar niet in kenmerken.

Alle parameters en typeparameters moeten verschillende namen hebben.

15.6.2 Methodeparameters

15.6.2.1 Algemeen

De parameters van een methode, indien aanwezig, worden gedeclareerd door de parameter_list van de methode.

parameter_list
    : fixed_parameters
    | fixed_parameters ',' parameter_array
    | parameter_array
    ;

fixed_parameters
    : fixed_parameter (',' fixed_parameter)*
    ;

fixed_parameter
    : attributes? parameter_modifier? type identifier default_argument?
    ;

default_argument
    : '=' expression
    ;

parameter_modifier
    : parameter_mode_modifier
    | 'this'
    ;

parameter_mode_modifier
    : 'ref'
    | 'out'
    | 'in'
    ;

parameter_array
    : attributes? 'params' array_type identifier
    ;

De parameterlijst bestaat uit een of meer door komma's gescheiden parameters waarvan alleen de laatste een parameter_array kan zijn.

Een fixed_parameter bestaat uit een optionele set kenmerken (§22); een optionele , inof outrefmodifier; een typethisid; en een optionele default_argument. Elke fixed_parameter declareert een parameter van het opgegeven type met de opgegeven naam. De this wijzigingsfunctie wijst de methode aan als een extensiemethode en is alleen toegestaan voor de eerste parameter van een statische methode in een niet-algemene, niet-geneste statische klasse. Als de parameter een struct type of een typeparameter is die is beperkt tot een struct, kan de this modifier worden gecombineerd met de ref of in modifier, maar niet met de out modifier. Extensiemethoden worden verder beschreven in §15.6.10. Een fixed_parameter met een default_argument wordt een optionele parameter genoemd, terwijl een fixed_parameter zonder default_argument een vereiste parameter is. Een vereiste parameter wordt niet weergegeven na een optionele parameter in een parameter_list.

Een parameter met een ref, out of this modifier kan geen default_argument hebben. Een invoerparameter kan een default_argument hebben. De expressie in een default_argument is een van de volgende:

  • een constant_expression
  • een expressie van het formulier new S() waarin S een waardetype is
  • een expressie van het formulier default(S) waarin S een waardetype is

De expressie moet impliciet worden omgezet door een identiteit of null-conversie naar het type van de parameter.

Als er optionele parameters optreden in een declaratie van gedeeltelijke methoden (§15.6.9), een expliciete implementatie van interfaceleden (§18.6.2), een declaratie van een indexeerfunctie met één parameter (§15.9), of in een operatordeclaratie (§15.10.1) moet een compiler een waarschuwing geven, omdat deze leden nooit kunnen worden aangeroepen op een manier die argumenten weglaat.

Een parameter_array bestaat uit een optionele set kenmerken (§22), een params modifier, een array_type en een id. Een parametermatrix declareert één parameter van het opgegeven matrixtype met de opgegeven naam. De array_type van een parametermatrix moet een enkeldimensionaal matrixtype zijn (§17.2). In een methode-aanroep staat een parametermatrix toe dat één argument van het opgegeven matrixtype wordt opgegeven, of dat nul of meer argumenten van het matrixelementtype worden opgegeven. Parametermatrices worden verder beschreven in §15.6.2.4.

Een parameter_array kan optreden na een optionele parameter, maar kan geen standaardwaarde hebben. Het weglaten van argumenten voor een parameter_array zou in plaats daarvan resulteren in het maken van een lege matrix.

Voorbeeld: Het volgende illustreert verschillende soorten parameters:

void M<T>(
    ref int i,
    decimal d,
    bool b = false,
    bool? n = false,
    string s = "Hello",
    object o = null,
    T t = default(T),
    params int[] a
) { }

In de parameter_list voor , is een vereiste parameter, M is een vereiste waardeparameter, irefen db zijn optionele waardeparameters en s is een parametermatrix. ota

eindvoorbeeld

Een methodedeclaratie maakt een afzonderlijke declaratieruimte (§7.3) voor parameters en typeparameters. Namen worden in deze declaratieruimte geïntroduceerd door de parameterlijst van het type en de parameterlijst van de methode. De hoofdtekst van de methode, indien aanwezig, wordt beschouwd als genest binnen deze declaratieruimte. Het is een fout dat twee leden van een methodedeclaratieruimte dezelfde naam hebben.

Een methode-aanroep (§12.8.10.2) maakt een kopie, specifiek voor die aanroep, van de parameters en lokale variabelen van de methode, en de argumentenlijst van de aanroep wijst waarden of variabele verwijzingen toe aan de zojuist gemaakte parameters. In het blok van een methode kunnen parameters worden verwezen door hun id's in simple_name expressies (§12.8.4).

De volgende soorten parameters bestaan:

Opmerking: Zoals beschreven in §7.6 maken de in, outen ref modifiers deel uit van de handtekening van een methode, maar de params wijzigingsfunctie niet. eindnotitie

15.6.2.2 Waardeparameters

Een parameter die zonder wijzigingsparameters is gedeclareerd, is een waardeparameter. Een waardeparameter is een lokale variabele die de oorspronkelijke waarde ophaalt uit het bijbehorende argument dat is opgegeven in de aanroep van de methode.

Zie §9.2.5 voor definitieve toewijzingsregels.

Het bijbehorende argument in een methodeaanroep moet een expressie zijn die impliciet converteerbaar is (§10.2) naar het parametertype.

Een methode is toegestaan om nieuwe waarden toe te wijzen aan een waardeparameter. Dergelijke toewijzingen zijn alleen van invloed op de lokale opslaglocatie die wordt vertegenwoordigd door de waardeparameter. Ze hebben geen invloed op het werkelijke argument dat is opgegeven in de aanroepmethode.

15.6.2.3 By-reference parameters

15.6.2.3.1 Algemeen

Invoer-, uitvoer- en verwijzingsparameters zijn by-reference-parameters. Een by-reference-parameter is een lokale referentievariabele (§9.7); de initiële referent wordt verkregen uit het bijbehorende argument dat is opgegeven in de methode-aanroep.

Opmerking: De verwijzing van een by-reference parameter kan worden gewijzigd met behulp van de verwtoewijzingsoperator (= ref).

Wanneer een parameter een by-reference-parameter is, bestaat het bijbehorende argument in een methode-aanroep uit het bijbehorende trefwoord, in, of refout, gevolgd door een variable_reference (§9,5) van hetzelfde type als de parameter. Wanneer de parameter echter een in parameter is, kan het argument een expressie zijn waarvoor een impliciete conversie (§10.2) van die argumentexpressie bestaat tot het type van de bijbehorende parameter.

By-reference parameters zijn niet toegestaan voor functies die zijn gedeclareerd als een iterator (§15.14) of asynchrone functie (§15.15).

In een methode die meerdere by-reference-parameters gebruikt, is het mogelijk dat meerdere namen dezelfde opslaglocatie vertegenwoordigen.

15.6.2.3.2 Invoerparameters

Een parameter die met een in wijzigingsfunctie is gedeclareerd, is een invoerparameter. Het argument dat overeenkomt met een invoerparameter is een variabele die bestaat op het punt van de aanroep van de methode of een variabele die is gemaakt door de implementatie (§12.6.2.3) in de methodeaanroep. Zie §9.2.8 voor definitieve toewijzingsregels.

Het is een compilatiefout om de waarde van een invoerparameter te wijzigen.

Opmerking: Het primaire doel van invoerparameters is voor efficiëntie. Wanneer het type methodeparameter een grote struct is (wat betreft geheugenvereisten), is het handig om te voorkomen dat de hele waarde van het argument wordt gekopieerd bij het aanroepen van de methode. Met invoerparameters kunnen methoden verwijzen naar bestaande waarden in het geheugen, terwijl ze bescherming bieden tegen ongewenste wijzigingen in deze waarden. eindnotitie

15.6.2.3.3 Referentieparameters

Een parameter die met een ref wijzigingsfunctie is gedeclareerd, is een referentieparameter. Zie §9.2.6 voor definitieve toewijzingsregels.

Voorbeeld: Het voorbeeld

class Test
{
    static void Swap(ref int x, ref int y)
    {
        int temp = x;
        x = y;
        y = temp;
    }

    static void Main()
    {
        int i = 1, j = 2;
        Swap(ref i, ref j);
        Console.WriteLine($"i = {i}, j = {j}");
    }
}

produceert de uitvoer

i = 2, j = 1

Voor de aanroep van Swap in Main, x vertegenwoordigt i en y vertegenwoordigt j. De aanroep heeft dus het effect van het wisselen van de waarden van i en j.

eindvoorbeeld

Voorbeeld: In de volgende code

class A
{
    string s;
    void F(ref string a, ref string b)
    {
        s = "One";
        a = "Two";
        b = "Three";
    }

    void G()
    {
        F(ref s, ref s);
    }
}

de aanroep van F in G de aanroep geeft een verwijzing door aan s voor beide a en b. Daarom verwijzen voor die aanroep, de namen s, aen b allemaal naar dezelfde opslaglocatie, en de drie toewijzingen allemaal het exemplaarveld swijzigen.

eindvoorbeeld

Voor een struct type, binnen een instantiemethode, instantietoegangsor (§12.2.1) of instantieconstructor met een initialisatiefunctie voor een constructor gedraagt het this trefwoord zich precies als referentieparameter van het structtype (§12.8.14).

15.6.2.3.4 Uitvoerparameters

Een parameter die is gedeclareerd met een out wijzigingsfunctie, is een uitvoerparameter. Zie §9.2.7 voor definitieve toewijzingsregels.

Een methode die is gedeclareerd als een gedeeltelijke methode (§15.6.9) mag geen uitvoerparameters hebben.

Opmerking: Uitvoerparameters worden doorgaans gebruikt in methoden die meerdere retourwaarden produceren. eindnotitie

Voorbeeld:

class Test
{
    static void SplitPath(string path, out string dir, out string name)
    {
        int i = path.Length;
        while (i > 0)
        {
            char ch = path[i - 1];
            if (ch == '\\' || ch == '/' || ch == ':')
            {
                break;
            }
            i--;
        }
        dir = path.Substring(0, i);
        name = path.Substring(i);
    }

    static void Main()
    {
        string dir, name;
        SplitPath(@"c:\Windows\System\hello.txt", out dir, out name);
        Console.WriteLine(dir);
        Console.WriteLine(name);
    }
}

In het voorbeeld wordt de uitvoer geproduceerd:

c:\Windows\System\
hello.txt

Houd er rekening mee dat de dir variabelen name niet kunnen worden toegewezen voordat ze worden doorgegeven aan SplitPathen dat ze worden beschouwd als definitief toegewezen na de aanroep.

eindvoorbeeld

15.6.2.4 Parametermatrices

Een parameter die is gedeclareerd met een params wijzigingsfunctie, is een parametermatrix. Als een parameterlijst een parametermatrix bevat, moet deze de laatste parameter in de lijst zijn en moet deze van een enkeldimensionaal matrixtype zijn.

Voorbeeld: De typen string[] en string[][] kunnen worden gebruikt als het type van een parametermatrix, maar het type string[,] kan niet. eindvoorbeeld

Opmerking: het is niet mogelijk om de params modifier te combineren met de modifiers in, outof ref. eindnotitie

Met een parametermatrix kunnen argumenten op twee manieren worden opgegeven in een methode-aanroep:

  • Het argument dat wordt opgegeven voor een parametermatrix kan één expressie zijn die impliciet wordt omgezet (§10.2) naar het parametermatrixtype. In dit geval fungeert de parametermatrix precies als een waardeparameter.
  • De aanroep kan ook nul of meer argumenten voor de parametermatrix opgeven, waarbij elk argument een expressie is die impliciet converteerbaar is (§10.2) naar het elementtype van de parametermatrix. In dit geval maakt de aanroep een exemplaar van het parametermatrixtype met een lengte die overeenkomt met het aantal argumenten, initialiseert de elementen van het matrixexemplaren met de opgegeven argumentwaarden en gebruikt u het zojuist gemaakte matrixexemplaren als het werkelijke argument.

Met uitzondering van het toestaan van een variabel aantal argumenten in een aanroep, is een parametermatrix precies gelijk aan een waardeparameter (§15.6.2.2) van hetzelfde type.

Voorbeeld: Het voorbeeld

class Test
{
    static void F(params int[] args)
    {
        Console.Write($"Array contains {args.Length} elements:");
        foreach (int i in args)
        {
            Console.Write($" {i}");
        }
        Console.WriteLine();
    }

    static void Main()
    {
        int[] arr = {1, 2, 3};
        F(arr);
        F(10, 20, 30, 40);
        F();
    }
}

produceert de uitvoer

Array contains 3 elements: 1 2 3
Array contains 4 elements: 10 20 30 40
Array contains 0 elements:

De eerste aanroep van F de matrix arr wordt gewoon doorgegeven als een waardeparameter. Met de tweede aanroep van F wordt automatisch een vier-element int[] gemaakt met de opgegeven elementwaarden en wordt dat matrixexemplaren doorgegeven als een waardeparameter. Op dezelfde manier maakt de derde aanroep van F een nul-element int[] en geeft dat exemplaar door als een waardeparameter. De tweede en derde aanroep zijn precies gelijk aan het schrijven:

F(new int[] {10, 20, 30, 40});
F(new int[] {});

eindvoorbeeld

Bij het uitvoeren van overbelastingsresolutie kan een methode met een parametermatrix van toepassing zijn, in de normale vorm of in de uitgevouwen vorm (§12.6.4.2). De uitgevouwen vorm van een methode is alleen beschikbaar als de normale vorm van de methode niet van toepassing is en alleen als een toepasselijke methode met dezelfde handtekening als het uitgevouwen formulier nog niet is gedeclareerd in hetzelfde type.

Voorbeeld: Het voorbeeld

class Test
{
    static void F(params object[] a) =>
        Console.WriteLine("F(object[])");

    static void F() =>
        Console.WriteLine("F()");

    static void F(object a0, object a1) =>
        Console.WriteLine("F(object,object)");

    static void Main()
    {
        F();
        F(1);
        F(1, 2);
        F(1, 2, 3);
        F(1, 2, 3, 4);
    }
}

produceert de uitvoer

F()
F(object[])
F(object,object)
F(object[])
F(object[])

In het voorbeeld zijn twee van de mogelijke uitgebreide vormen van de methode met een parametermatrix al opgenomen in de klasse als reguliere methoden. Deze uitgebreide formulieren worden daarom niet overwogen bij het uitvoeren van overbelastingsresolutie, en de eerste en derde methode aanroepen selecteren dus de reguliere methoden. Wanneer een klasse een methode met een parametermatrix declareert, is het niet ongebruikelijk om ook enkele van de uitgebreide formulieren op te nemen als reguliere methoden. Hierdoor is het mogelijk om te voorkomen dat een matrixexemplaren worden toegewezen wanneer een uitgebreide vorm van een methode met een parametermatrix wordt aangeroepen.

eindvoorbeeld

Een matrix is een verwijzingstype, dus de waarde die wordt doorgegeven voor een parametermatrix kan zijn null.

Voorbeeld: Het voorbeeld:

class Test
{
    static void F(params string[] array) =>
        Console.WriteLine(array == null);

    static void Main()
    {
        F(null);
        F((string) null);
    }
}

produceert de uitvoer:

True
False

De tweede aanroep produceert False omdat deze gelijk is aan F(new string[] { null }) en geeft een matrix door die één null-verwijzing bevat.

eindvoorbeeld

Wanneer het type van een parametermatrix is object[], ontstaat er mogelijk dubbelzinnigheid tussen de normale vorm van de methode en het uitgevouwen formulier voor één object parameter. De reden voor de dubbelzinnigheid is dat een object[] zelf impliciet converteerbaar is om te typen object. De dubbelzinnigheid vormt echter geen probleem, omdat het kan worden opgelost door indien nodig een cast in te voegen.

Voorbeeld: Het voorbeeld

class Test
{
    static void F(params object[] args)
    {
        foreach (object o in args)
        {
            Console.Write(o.GetType().FullName);
            Console.Write(" ");
        }
        Console.WriteLine();
    }

    static void Main()
    {
        object[] a = {1, "Hello", 123.456};
        object o = a;
        F(a);
        F((object)a);
        F(o);
        F((object[])o);
    }
}

produceert de uitvoer

System.Int32 System.String System.Double
System.Object[]
System.Object[]
System.Int32 System.String System.Double

In de eerste en laatste aanroepen van Fis de normale vorm van van F toepassing omdat er een impliciete conversie bestaat van het argumenttype naar het parametertype (beide van het type object[]). Overbelastingsresolutie selecteert dus de normale vorm van Fen het argument wordt doorgegeven als een normale waardeparameter. In de tweede en derde aanroepen is de normale vorm van F niet van toepassing omdat er geen impliciete conversie bestaat van het argumenttype naar het parametertype (type object kan niet impliciet worden geconverteerd naar type object[]). De uitgevouwen vorm is F echter van toepassing, dus wordt deze geselecteerd door overbelastingsresolutie. Als gevolg hiervan wordt een enkel element object[] gemaakt door de aanroep en wordt het enige element van de matrix geïnitialiseerd met de opgegeven argumentwaarde (die zelf een verwijzing naar een object[]is).

eindvoorbeeld

15.6.3 Statische methoden en instantiemethoden

Wanneer een methodedeclaratie een static wijzigingsfunctie bevat, wordt deze methode beschouwd als een statische methode. Wanneer er geen static wijzigingsfunctie aanwezig is, wordt gezegd dat de methode een instantiemethode is.

Een statische methode werkt niet op een specifiek exemplaar en het is een compilatiefout waarnaar wordt verwezen this in een statische methode.

Een instantiemethode werkt op een bepaald exemplaar van een klasse en dat exemplaar kan worden geopend als this (§12.8.14).

De verschillen tussen statisch en exemplaarleden worden verder besproken in §15.3.8.

15.6.4 Virtuele methoden

Wanneer een declaratie van een instantiemethode een virtuele modifier bevat, wordt deze methode als een virtuele methode beschouwd. Wanneer er geen virtuele wijzigingsfunctie aanwezig is, wordt gezegd dat de methode een niet-virtuele methode is.

De implementatie van een niet-virtuele methode is invariant: de implementatie is hetzelfde of de methode wordt aangeroepen op een exemplaar van de klasse waarin deze wordt gedeclareerd of een exemplaar van een afgeleide klasse. De implementatie van een virtuele methode kan daarentegen worden vervangen door afgeleide klassen. Het proces van het vervangen van de implementatie van een overgenomen virtuele methode wordt deze methode genoemd (§15.6.5).

In een aanroep van een virtuele methode bepaalt het runtimetype van het exemplaar waarvoor die aanroep plaatsvindt, de daadwerkelijke methode-implementatie die moet worden aangeroepen. In een niet-virtuele methode-aanroep is het type compileertijd van het exemplaar de bepalende factor. Wanneer een methode met de naam N wordt aangeroepen met een argumentenlijst A op een exemplaar met een type compileertijd C en een runtimetype R (waarbij R een C of een klasse is afgeleid van C), wordt de aanroep als volgt verwerkt:

  • Bij bindingstijd wordt overbelastingsresolutie toegepast op , en C, om een specifieke methode N te selecteren uit de set methoden die zijn gedeclareerd in en overgenomen door A. MC Dit wordt beschreven in §12.8.10.2.
  • Vervolgens tijdens runtime:
    • Als M dit een niet-virtuele methode is, M wordt aangeroepen.
    • M Anders is dit een virtuele methode en wordt de meest afgeleide implementatie M met betrekking tot deze R methode aangeroepen.

Voor elke virtuele methode die is gedeclareerd in of overgenomen door een klasse, bestaat er een meest afgeleide implementatie van de methode met betrekking tot die klasse. De meest afgeleide implementatie van een virtuele methode M met betrekking tot een klasse R wordt als volgt bepaald:

  • Als R dit de introductie van de virtuele declaratie bevat M, is dit de meest afgeleide implementatie M met betrekking tot R.
  • Als dit niet R een onderdrukking Mbevat, is dit de meest afgeleide implementatie M met betrekking tot R.
  • Anders is de meest afgeleide implementatie M met betrekking tot R de meest afgeleide implementatie M van met betrekking tot de directe basisklasse van R.

Voorbeeld: In het volgende voorbeeld ziet u de verschillen tussen virtuele en niet-virtuele methoden:

class A
{
    public void F() => Console.WriteLine("A.F");
    public virtual void G() => Console.WriteLine("A.G");
}

class B : A
{
    public new void F() => Console.WriteLine("B.F");
    public override void G() => Console.WriteLine("B.G");
}

class Test
{
    static void Main()
    {
        B b = new B();
        A a = b;
        a.F();
        b.F();
        a.G();
        b.G();
    }
}

In het voorbeeld A wordt een niet-virtuele methode F en een virtuele methode Ggeïntroduceerd. De klasse B introduceert een nieuwe niet-virtuele methode, waardoor F en ook . In het voorbeeld wordt de uitvoer geproduceerd:

A.F
B.F
B.G
B.G

U ziet dat de instructie a.G() wordt aangeroepen B.G, niet A.G. Dit komt doordat het runtimetype van het exemplaar (dat wil Bzijn), niet het type compileertijd van het exemplaar (dat wil Azijn), bepaalt de werkelijke methode-implementatie die moet worden aangeroepen.

eindvoorbeeld

Omdat methoden overgenomen methoden mogen verbergen, is het mogelijk dat een klasse meerdere virtuele methoden met dezelfde handtekening bevat. Dit levert geen dubbelzinnigheidsprobleem op, omdat alle maar de meest afgeleide methode verborgen is.

Voorbeeld: In de volgende code

class A
{
    public virtual void F() => Console.WriteLine("A.F");
}

class B : A
{
    public override void F() => Console.WriteLine("B.F");
}

class C : B
{
    public new virtual void F() => Console.WriteLine("C.F");
}

class D : C
{
    public override void F() => Console.WriteLine("D.F");
}

class Test
{
    static void Main()
    {
        D d = new D();
        A a = d;
        B b = d;
        C c = d;
        a.F();
        b.F();
        c.F();
        d.F();
    }
}

de C en D klassen bevatten twee virtuele methoden met dezelfde handtekening: de methode die wordt geïntroduceerd door A en de methode die wordt geïntroduceerd door C. De methode die wordt geïntroduceerd door C verbergt de methode die is overgenomen van A. De onderdrukkingsdeclaratie in D overschrijft dus de methode die is ingevoerd door C, en het is niet mogelijk D om de methode die wordt ingevoerd door Ate overschrijven . In het voorbeeld wordt de uitvoer geproduceerd:

B.F
B.F
D.F
D.F

Houd er rekening mee dat het mogelijk is om de verborgen virtuele methode aan te roepen door toegang te krijgen tot een exemplaar van D een minder afgeleid type waarin de methode niet verborgen is.

eindvoorbeeld

15.6.5 Onderdrukkingsmethoden

Wanneer een declaratie van een instantiemethode een override wijzigingsfunctie bevat, wordt de methode beschouwd als een onderdrukkingsmethode. Een onderdrukkingsmethode overschrijft een overgenomen virtuele methode met dezelfde handtekening. Terwijl een declaratie van een virtuele methode een nieuwe methode introduceert, is een overschrijvingsmethodedeclaratie gespecialiseerd in een bestaande overgenomen virtuele methode door een nieuwe implementatie van die methode te bieden.

De methode die wordt overschreven door een overschrijvingsdeclaratie wordt de basismethode overschreven voor een onderdrukkingsmethode M die is gedeclareerd in een klasse C, de overschreven basismethode wordt bepaald door elke basisklasse te onderzoeken, Cte beginnen met de directe basisklasse van C en door te gaan met elke opeenvolgende directe basisklasse, totdat in een bepaald basisklassetype ten minste één toegankelijke methode zich bevindt die dezelfde handtekening heeft als M na vervanging van typeargumenten. Voor het vinden van de overschreven basismethode wordt een methode als toegankelijk beschouwd als deze publicis , als het het geval is , als het is protectedprotected internal, of als het een of en gedeclareerd is internalprivate protected in hetzelfde programma als C.

Er treedt een compilatiefout op, tenzij aan alle volgende voorwaarden wordt voldaan voor een onderdrukkingsdeclaratie:

  • Een overschreven basismethode kan worden gevonden zoals hierboven beschreven.
  • Er is precies één dergelijke overschreven basismethode. Deze beperking heeft alleen effect als het basisklassetype een samengesteld type is waarbij de vervanging van typeargumenten de handtekening van twee methoden hetzelfde maakt.
  • De overschreven basismethode is een virtuele, abstracte of onderdrukkingsmethode. Met andere woorden, de overschreven basismethode kan niet statisch of niet-virtueel zijn.
  • De overschreven basismethode is geen verzegelde methode.
  • Er is een identiteitsconversie tussen het retourtype van de overschreven basismethode en de onderdrukkingsmethode.
  • De onderdrukkingsdeclaratie en de overschreven basismethode hebben dezelfde gedeclareerde toegankelijkheid. Met andere woorden, een overschrijvingsdeclaratie kan de toegankelijkheid van de virtuele methode niet wijzigen. Als de overschreven basismethode echter intern is beveiligd en deze wordt gedeclareerd in een andere assembly dan de assembly die de onderdrukkingsdeclaratie bevat, wordt de gedeclareerde toegankelijkheid van de toegankelijkheid van de overschrijvingsdeclaratie beschermd.
  • In de declaratie overschrijven worden geen type_parameter_constraints_clauses opgegeven. In plaats daarvan worden de beperkingen overgenomen van de overschreven basismethode. Beperkingen die typeparameters in de overschreven methode zijn, kunnen worden vervangen door typeargumenten in de overgenomen beperking. Dit kan leiden tot beperkingen die niet geldig zijn wanneer deze expliciet worden opgegeven, zoals waardetypen of verzegelde typen.

Voorbeeld: Hieronder ziet u hoe de onderdrukkingsregels werken voor algemene klassen:

abstract class C<T>
{
    public virtual T F() {...}
    public virtual C<T> G() {...}
    public virtual void H(C<T> x) {...}
}

class D : C<string>
{
    public override string F() {...}            // Ok
    public override C<string> G() {...}         // Ok
    public override void H(C<T> x) {...}        // Error, should be C<string>
}

class E<T,U> : C<U>
{
    public override U F() {...}                 // Ok
    public override C<U> G() {...}              // Ok
    public override void H(C<T> x) {...}        // Error, should be C<U>
}

eindvoorbeeld

Een overschrijvingsdeclaratie heeft toegang tot de overschreven basismethode met behulp van een base_access (§12.8.15).

Voorbeeld: In de volgende code

class A
{
    int x;

    public virtual void PrintFields() => Console.WriteLine($"x = {x}");
}

class B : A
{
    int y;

    public override void PrintFields()
    {
        base.PrintFields();
        Console.WriteLine($"y = {y}");
    }
}

de base.PrintFields() aanroep in B roept de methode PrintFields aan die is gedeclareerd in A. Een base_access schakelt het virtuele aanroepmechanisme uit en behandelt de basismethode als een niet-methodevirtual . Als de aanroep B is geschreven ((A)this).PrintFields(), zou deze recursief de PrintFields methode aanroepen die is gedeclareerd in B, niet de methode die is gedeclareerd in A, omdat PrintFields het virtueel is en het runtime-type ((A)this) is B.

eindvoorbeeld

Alleen door een override wijzigingsfunctie op te halen, kan een methode een andere methode overschrijven. In alle andere gevallen verbergt een methode met dezelfde handtekening als een overgenomen methode de overgenomen methode.

Voorbeeld: In de volgende code

class A
{
    public virtual void F() {}
}

class B : A
{
    public virtual void F() {} // Warning, hiding inherited F()
}

de F methode bevat B geen override wijzigingsfunctie en overschrijft daarom de F methode niet in A. In plaats daarvan verbergt de F methode in BAen wordt er een waarschuwing gerapporteerd omdat de declaratie geen nieuwe wijzigingsfunctie bevat.

eindvoorbeeld

Voorbeeld: In de volgende code

class A
{
    public virtual void F() {}
}

class B : A
{
    private new void F() {} // Hides A.F within body of B
}

class C : B
{
    public override void F() {} // Ok, overrides A.F
}

de F methode in B verbergt de virtuele F methode die is overgenomen van A. Aangezien de nieuwe F toegang B privétoegang heeft, omvat het bereik alleen de hoofdtekst van de klasse van B en wordt niet uitgebreid naar C. Daarom mag de declaratie van F binnen C de F overgenomen Aoverschrijving van .

eindvoorbeeld

15.6.6 Verzegelde methoden

Wanneer een instantiemethodedeclaratie een sealed wijzigingsfunctie bevat, wordt deze methode beschouwd als een verzegelde methode. Een verzegelde methode overschrijft een overgenomen virtuele methode met dezelfde handtekening. Een verzegelde methode moet ook worden gemarkeerd met de override modifier. Het gebruik van de sealed modifier voorkomt dat een afgeleide klasse de methode verder overschrijft.

Voorbeeld: Het voorbeeld

class A
{
    public virtual void F() => Console.WriteLine("A.F");
    public virtual void G() => Console.WriteLine("A.G");
}

class B : A
{
    public sealed override void F() => Console.WriteLine("B.F");
    public override void G()        => Console.WriteLine("B.G");
}

class C : B
{
    public override void G() => Console.WriteLine("C.G");
}

de klasse B biedt twee onderdrukkingsmethoden: een F methode die de sealed modifier heeft en een G methode die dat niet doet. B's gebruik van de sealed modifier voorkomt verdere C onderdrukking F.

eindvoorbeeld

15.6.7 Abstracte methoden

Wanneer een declaratie van een instantiemethode een abstract wijzigingsfunctie bevat, wordt deze methode beschouwd als een abstracte methode. Hoewel een abstracte methode impliciet ook een virtuele methode is, kan deze niet de modifier virtualhebben.

Een abstracte methodedeclaratie introduceert een nieuwe virtuele methode, maar biedt geen implementatie van die methode. In plaats daarvan moeten niet-abstracte afgeleide klassen hun eigen implementatie bieden door die methode te overschrijven. Omdat een abstracte methode geen werkelijke implementatie biedt, bestaat de methodetekst van een abstracte methode gewoon uit een puntkomma.

Abstracte methodedeclaraties zijn alleen toegestaan in abstracte klassen (§15.2.2.2).

Voorbeeld: In de volgende code

public abstract class Shape
{
    public abstract void Paint(Graphics g, Rectangle r);
}

public class Ellipse : Shape
{
    public override void Paint(Graphics g, Rectangle r) => g.DrawEllipse(r);
}

public class Box : Shape
{
    public override void Paint(Graphics g, Rectangle r) => g.DrawRect(r);
}

de Shape klasse definieert het abstracte begrip van een geometrisch vormobject dat zichzelf kan schilderen. De Paint methode is abstract omdat er geen zinvolle standaard implementatie is. De Ellipse en Box klassen zijn concrete Shape implementaties. Omdat deze klassen niet-abstract zijn, moeten ze de Paint methode overschrijven en een werkelijke implementatie bieden.

eindvoorbeeld

Het is een compilatietijdfout voor een base_access (§12.8.15) om te verwijzen naar een abstracte methode.

Voorbeeld: In de volgende code

abstract class A
{
    public abstract void F();
}

class B : A
{
    // Error, base.F is abstract
    public override void F() => base.F();
}

er wordt een compilatiefout gerapporteerd voor de base.F() aanroep, omdat deze verwijst naar een abstracte methode.

eindvoorbeeld

Een abstracte methodedeclaratie is toegestaan om een virtuele methode te overschrijven. Hierdoor kan een abstracte klasse de her implementatie van de methode in afgeleide klassen afdwingen en de oorspronkelijke implementatie van de methode niet beschikbaar maken.

Voorbeeld: In de volgende code

class A
{
    public virtual void F() => Console.WriteLine("A.F");
}

abstract class B: A
{
    public abstract override void F();
}

class C : B
{
    public override void F() => Console.WriteLine("C.F");
}

klasse A declareert een virtuele methode, klasse B overschrijft deze methode met een abstracte methode en klasse C overschrijft de abstracte methode om een eigen implementatie te bieden.

eindvoorbeeld

15.6.8 Externe methoden

Wanneer een methodedeclaratie een extern wijzigingsfunctie bevat, wordt gezegd dat de methode een externe methode is. Externe methoden worden extern geïmplementeerd, meestal met een andere taal dan C#. Omdat een externe methodedeclaratie geen werkelijke implementatie biedt, bestaat de methodetekst van een externe methode gewoon uit een puntkomma. Een externe methode mag niet algemeen zijn.

Het mechanisme waarmee koppeling naar een externe methode wordt bereikt, wordt gedefinieerd door de implementatie.

Voorbeeld: In het volgende voorbeeld ziet u het gebruik van de extern modifier en het DllImport kenmerk:

class Path
{
    [DllImport("kernel32", SetLastError=true)]
    static extern bool CreateDirectory(string name, SecurityAttribute sa);

    [DllImport("kernel32", SetLastError=true)]
    static extern bool RemoveDirectory(string name);

    [DllImport("kernel32", SetLastError=true)]
    static extern int GetCurrentDirectory(int bufSize, StringBuilder buf);

    [DllImport("kernel32", SetLastError=true)]
    static extern bool SetCurrentDirectory(string name);
}

eindvoorbeeld

15.6.9 Gedeeltelijke methoden

Wanneer een methodedeclaratie een partial wijzigingsfunctie bevat, wordt deze methode beschouwd als een gedeeltelijke methode. Gedeeltelijke methoden mogen alleen worden gedeclareerd als leden van gedeeltelijke typen (§15.2.7) en zijn onderworpen aan een aantal beperkingen.

Gedeeltelijke methoden kunnen worden gedefinieerd in het ene deel van een typedeclaratie en in een ander deel worden geïmplementeerd. De implementatie is optioneel; als er geen deel de gedeeltelijke methode implementeert, worden de declaratie van de gedeeltelijke methode en alle aanroepen ervan verwijderd uit de typedeclaratie als gevolg van de combinatie van de onderdelen.

Gedeeltelijke methoden definiëren geen toegangsaanpassingen; ze zijn impliciet privé. Hun retourtype moet zijn voiden hun parameters mogen geen uitvoerparameters zijn. De id wordt slechts herkend als een contextueel trefwoord (§6.4.4) in een methodedeclaratie als deze direct voor het void trefwoord wordt weergegeven. Een gedeeltelijke methode kan interfacemethoden niet expliciet implementeren.

Er zijn twee soorten gedeeltelijke methodedeclaraties: Als de hoofdtekst van de methodedeclaratie een puntkomma is, wordt gezegd dat de declaratie een definiërende gedeeltelijke methodedeclaratie is. Als de hoofdtekst niet een puntkomma is, wordt gezegd dat de aangifte een gedeeltelijke methodedeclaratie is. In de delen van een typedeclaratie kan er slechts één gedeeltelijke methodedeclaratie met een bepaalde handtekening worden gedefinieerd en kan er slechts één gedeeltelijke methodedeclaratie met een bepaalde handtekening worden geïmplementeerd. Indien een gedeeltelijke methodedeclaratie wordt uitgevoerd, bestaat er een overeenkomstige declaratie van een gedeeltelijke methode en komen de declaraties overeen zoals aangegeven in het volgende:

  • De declaraties hebben dezelfde modifiers (hoewel niet noodzakelijkerwijs in dezelfde volgorde), de naam van de methode, het aantal typeparameters en het aantal parameters.
  • De overeenkomstige parameters in de declaraties hebben dezelfde modifiers (hoewel niet noodzakelijkerwijs in dezelfde volgorde) en dezelfde typen, of identiteits converteerbare typen (moduloverschillen in parameternamen van het type).
  • De overeenkomstige typeparameters in de declaraties hebben dezelfde beperkingen (moduloverschillen in parameternamen van het type).

Een gedeeltelijke methodedeclaratie kan worden weergegeven in hetzelfde deel als de bijbehorende declaratie van gedeeltelijke methoden.

Alleen een definiërende gedeeltelijke methode neemt deel aan overbelastingsresolutie. Dus, ongeacht of er al dan niet een uitvoeringsdeclaratie wordt gegeven, kunnen aanroepexpressies worden omgezet in aanroepen van de gedeeltelijke methode. Omdat een gedeeltelijke methode altijd retourneert void, zijn dergelijke aanroepexpressies altijd expressie-instructies. Bovendien, omdat een gedeeltelijke methode impliciet privateis, zullen dergelijke instructies altijd plaatsvinden binnen een van de onderdelen van de typedeclaratie waarin de gedeeltelijke methode wordt gedeclareerd.

Opmerking: De definitie van overeenkomende declaraties voor gedeeltelijke methoden en het implementeren van declaraties van gedeeltelijke methoden vereist geen overeenkomende parameternamen. Dit kan verrassend, zij het goed gedefinieerde gedrag opleveren wanneer benoemde argumenten (§12.6.2.1) worden gebruikt. Als u bijvoorbeeld de declaratie van een gedeeltelijke methode voor M het ene bestand definieert en de gedeeltelijke methodedeclaratie in een ander bestand implementeert:

// File P1.cs:
partial class P
{
    static partial void M(int x);
}

// File P2.cs:
partial class P
{
    static void Caller() => M(y: 0);
    static partial void M(int y) {}
}

is ongeldig omdat de aanroep gebruikmaakt van de argumentnaam van de implementatie en niet van de definiërende gedeeltelijke methodedeclaratie.

eindnotitie

Als geen deel van een declaratie van een gedeeltelijk type een uitvoeringsdeclaratie voor een bepaalde gedeeltelijke methode bevat, wordt een expressie-instructie die deze aanroept, gewoon verwijderd uit de gecombineerde typedeclaratie. De aanroepexpressie, inclusief subexpressies, heeft dus geen effect tijdens runtime. De gedeeltelijke methode zelf wordt ook verwijderd en is geen lid van de gecombineerde typedeclaratie.

Als er een uitvoeringsdeclaratie bestaat voor een bepaalde gedeeltelijke methode, blijven de aanroepen van de gedeeltelijke methoden behouden. De gedeeltelijke methode geeft aanleiding tot een methodedeclaratie die vergelijkbaar is met de declaratie van een gedeeltelijke methode, met uitzondering van het volgende:

  • De partial wijzigingsfunctie is niet inbegrepen.

  • De kenmerken in de resulterende methodedeclaratie zijn de gecombineerde kenmerken van het definiëren en uitvoeren van gedeeltelijke methodedeclaratie in niet-opgegeven volgorde. Duplicaten worden niet verwijderd.

  • De kenmerken van de parameters van de resulterende methodedeclaratie zijn de gecombineerde kenmerken van de bijbehorende parameters van de definitie en de implementatie gedeeltelijke methodedeclaratie in niet-gespecificeerde volgorde. Duplicaten worden niet verwijderd.

Als voor een gedeeltelijke methode Meen definiërende declaratie wordt opgegeven, zijn de volgende beperkingen van toepassing:

  • Het is een compilatiefout voor het maken van een gemachtigde van M (§12.8.17.6).

  • Het is een compilatiefout die verwijst naar M een anonieme functie die wordt geconverteerd naar een expressiestructuurtype (§8.6).

  • Expressies die optreden als onderdeel van een aanroep van M , hebben geen invloed op de definitieve toewijzingsstatus (§9.4), wat mogelijk kan leiden tot compilatiefouten.

  • M kan niet het toegangspunt voor een toepassing zijn (§7.1).

Gedeeltelijke methoden zijn handig om een deel van een typedeclaratie toe te staan het gedrag van een ander onderdeel aan te passen, bijvoorbeeld een methode die wordt gegenereerd door een hulpprogramma. Houd rekening met de volgende gedeeltelijke klassedeclaratie:

partial class Customer
{
    string name;

    public string Name
    {
        get => name;
        set
        {
            OnNameChanging(value);
            name = value;
            OnNameChanged();
        }
    }

    partial void OnNameChanging(string newName);
    partial void OnNameChanged();
}

Als deze klasse zonder andere onderdelen wordt gecompileerd, worden de declaraties van gedeeltelijke methoden en hun aanroepen verwijderd en is de resulterende gecombineerde klassedeclaratie gelijk aan het volgende:

class Customer
{
    string name;

    public string Name
    {
        get => name;
        set => name = value;
    }
}

Stel dat er een ander onderdeel wordt gegeven, dat echter declaraties van de gedeeltelijke methoden biedt:

partial class Customer
{
    partial void OnNameChanging(string newName) =>
        Console.WriteLine($"Changing {name} to {newName}");

    partial void OnNameChanged() =>
        Console.WriteLine($"Changed to {name}");
}

Vervolgens is de resulterende gecombineerde klassedeclaratie gelijk aan het volgende:

class Customer
{
    string name;

    public string Name
    {
        get => name;
        set
        {
            OnNameChanging(value);
            name = value;
            OnNameChanged();
        }
    }

    void OnNameChanging(string newName) =>
        Console.WriteLine($"Changing {name} to {newName}");

    void OnNameChanged() =>
        Console.WriteLine($"Changed to {name}");
}

15.6.10 Extensiemethoden

Wanneer de eerste parameter van een methode de this modifier bevat, wordt deze methode beschouwd als een extensiemethode. Uitbreidingsmethoden worden alleen gedeclareerd in niet-algemene, niet-geneste statische klassen. De eerste parameter van een extensiemethode is als volgt beperkt:

  • Dit kan alleen een invoerparameter zijn als deze een waardetype heeft
  • Dit kan alleen een verwijzingsparameter zijn als deze een waardetype heeft of een algemeen type heeft dat is beperkt tot de struct
  • Het mag geen aanwijzer zijn.

Voorbeeld: Hier volgt een voorbeeld van een statische klasse die twee extensiemethoden declareert:

public static class Extensions
{
    public static int ToInt32(this string s) => Int32.Parse(s);

    public static T[] Slice<T>(this T[] source, int index, int count)
    {
        if (index < 0 || count < 0 || source.Length - index < count)
        {
            throw new ArgumentException();
        }
        T[] result = new T[count];
        Array.Copy(source, index, result, 0, count);
        return result;
    }
}

eindvoorbeeld

Een extensiemethode is een reguliere statische methode. Bovendien kan een extensiemethode worden aangeroepen met behulp van de aanroepsyntaxis van de instantiemethode (§12.8.10.3) met behulp van de ontvangerexpressie als het eerste argument.

Voorbeeld: In het volgende programma worden de bovenstaande extensiemethoden gebruikt:

static class Program
{
    static void Main()
    {
        string[] strings = { "1", "22", "333", "4444" };
        foreach (string s in strings.Slice(1, 2))
        {
            Console.WriteLine(s.ToInt32());
        }
    }
}

De Slice methode is beschikbaar op de string[], en de ToInt32 methode is beschikbaar op string, omdat ze zijn gedeclareerd als uitbreidingsmethoden. De betekenis van het programma is hetzelfde als het volgende, met behulp van gewone statische methode aanroepen:

static class Program
{
    static void Main()
    {
        string[] strings = { "1", "22", "333", "4444" };
        foreach (string s in Extensions.Slice(strings, 1, 2))
        {
            Console.WriteLine(Extensions.ToInt32(s));
        }
    }
}

eindvoorbeeld

15.6.11 Methodetekst

De hoofdtekst van een methodedeclaratie bestaat uit een bloktekst, een expressietekst of een puntkomma.

Abstracte en externe methodedeclaraties bieden geen methode-implementatie, dus hun methodeteksten bestaan gewoon uit een puntkomma. Voor elke andere methode is de hoofdtekst van de methode een blok (§13.3) dat de instructies bevat die moeten worden uitgevoerd wanneer die methode wordt aangeroepen.

Het effectieve retourtype van een methode is void als het retourtype is void, of als de methode asynchroon is en het retourtype «TaskType» (§15.15.15.1). Anders is het effectieve retourtype van een niet-asynchrone methode het retourtype en het effectieve retourtype van een asynchrone methode met retourtype «TaskType»<T>(§15.15.11) is T.

Wanneer het effectieve retourtype van een methode is void en de methode een bloktekst heeft, return geven instructies (§13.10.5) in het blok geen expressie op. Als de uitvoering van het blok van een ongeldige methode normaal wordt voltooid (dat wil gezegd, de controle loopt van het einde van de hoofdtekst van de methode), keert die methode gewoon terug naar de aanroeper.

Wanneer het effectieve retourtype van een methode is void en de methode een expressietekst heeft, moet de expressie E een statement_expression zijn en is de hoofdtekst precies gelijk aan een bloktekst van het formulier { E; }.

Voor een methode voor retouren per waarde (§15.6.1) geeft elke retourinstructie in de hoofdtekst van die methode een expressie op die impliciet kan worden omgezet in het effectieve retourtype.

Voor een methode returns-by-ref (§15.6.1) geeft elke retourinstructie in de hoofdtekst van die methode een expressie op waarvan het type is dat van het effectieve retourtype en een ref-safe-context van de aanroepercontext (§9.7.2) heeft.

Voor retouren per waarde en retourneert het eindpunt van de hoofdtekst van de methode niet bereikbaar. Met andere woorden, de besturing mag niet aan het einde van de hoofdtekst van de methode stromen.

Voorbeeld: In de volgende code

class A
{
    public int F() {} // Error, return value required

    public int G()
    {
        return 1;
    }

    public int H(bool b)
    {
        if (b)
        {
            return 1;
        }
        else
        {
            return 0;
        }
    }

    public int I(bool b) => b ? 1 : 0;
}

de methode die de waarde retourneert F , resulteert in een compilatiefout omdat het besturingselement kan stromen aan het einde van de hoofdtekst van de methode. De G en H methoden zijn juist omdat alle mogelijke uitvoeringspaden eindigen op een retourinstructie waarmee een retourwaarde wordt opgegeven. De I methode is juist, omdat de hoofdtekst gelijk is aan een blok met slechts één retourinstructie erin.

eindvoorbeeld

15.7 Eigenschappen

15.7.1 Algemeen

Een eigenschap is een lid dat toegang biedt tot een kenmerk van een object of een klasse. Voorbeelden van eigenschappen zijn de lengte van een tekenreeks, de grootte van een lettertype, het bijschrift van een venster en de naam van een klant. Eigenschappen zijn een natuurlijke uitbreiding van velden, beide benoemde leden met gekoppelde typen en de syntaxis voor het openen van velden en eigenschappen is hetzelfde. In tegenstelling tot velden geven eigenschappen echter geen opslaglocaties aan. In plaats daarvan hebben eigenschappen accessors die de instructies opgeven die moeten worden uitgevoerd wanneer hun waarden worden gelezen of geschreven. Eigenschappen bieden dus een mechanisme voor het koppelen van acties aan het lezen en schrijven van de kenmerken van een object of klasse; bovendien kunnen dergelijke kenmerken worden berekend.

Eigenschappen worden gedeclareerd met property_declaration s:

property_declaration
    : attributes? property_modifier* type member_name property_body
    | attributes? property_modifier* ref_kind type member_name ref_property_body
    ;    

property_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'static'
    | 'virtual'
    | 'sealed'
    | 'override'
    | 'abstract'
    | 'extern'
    | unsafe_modifier   // unsafe code support
    ;
    
property_body
    : '{' accessor_declarations '}' property_initializer?
    | '=>' expression ';'
    ;

property_initializer
    : '=' variable_initializer ';'
    ;

ref_property_body
    : '{' ref_get_accessor_declaration '}'
    | '=>' 'ref' variable_reference ';'
    ;

unsafe_modifier (§23.2) is alleen beschikbaar in onveilige code (§23).

Er zijn twee soorten property_declaration:

  • De eerste declareert een niet-verw-waarde-eigenschap. De waarde heeft een typetype. Dit type eigenschap kan leesbaar en/of schrijfbaar zijn.
  • De tweede declareert een verw-waarde-eigenschap. De waarde is een variable_reference (§9,5 Dit type eigenschap kan alleen worden gelezen.

Een property_declaration kan een set kenmerken (§22) en een van de toegestane soorten toegankelijkheid (§15.3.6), de new (§15.3.5), (§15.3.5), static (§15.7.2), virtual (§15.6.4, §15.7.6), override (§15.6.5, §15.7.6), sealed (§15.6.6), abstract (§15.6.7, §15.7.6) en extern (§15.6.8) modifiers.

Eigenschappendeclaraties zijn onderworpen aan dezelfde regels als methodedeclaraties (§15.6) met betrekking tot geldige combinaties van modifiers.

De member_name (§15.6.1) geeft de naam van de eigenschap op. Tenzij de eigenschap een expliciete implementatie van een interfacelid is, is de member_name gewoon een id. Voor een expliciete implementatie van interfaceleden (§18.6.2) bestaat de member_name uit een interface_type gevolgd door een "." en een id.

Het type eigenschap moet minstens zo toegankelijk zijn als de eigenschap zelf (§7.5.5).

Een property_body kan bestaan uit een hoofdtekst van de instructie of een expressietekst. In een hoofdtekst van de verklaring, accessor_declarations, die tussen "{" en "}" tokens worden geplaatst, declareert u de toegangsrechten (§15.7.3) van de eigenschap. De accessors geven de uitvoerbare instructies op die zijn gekoppeld aan het lezen en schrijven van de eigenschap.

In een property_body een expressietekst die bestaat uit => gevolgd door een expressieE en een puntkomma exact gelijk is aan de hoofdtekst { get { return E; } }van de instructie, en kan daarom alleen worden gebruikt om alleen-lezeneigenschappen op te geven waarbij het resultaat van de get-accessor wordt gegeven door één expressie.

Een property_initializer mag alleen worden gegeven voor een automatisch geïmplementeerde eigenschap (§15.7.4) en veroorzaakt de initialisatie van het onderliggende veld van dergelijke eigenschappen met de waarde die door de expressie wordt opgegeven.

Een ref_property_body kan bestaan uit een hoofdtekst van de instructie of een expressietekst. In een instructietekst geeft een get_accessor_declaration de get accessor (§15.7.3) van de eigenschap aan. De accessor geeft de uitvoerbare instructies op die zijn gekoppeld aan het lezen van de eigenschap.

In een ref_property_body een expressietekst die bestaat uit => gevolgd door ref, is een variable_referenceV en een puntkomma exact gelijk aan de hoofdtekst { get { return ref V; } }van de instructie.

Opmerking: Hoewel de syntaxis voor het openen van een eigenschap hetzelfde is als voor een veld, wordt een eigenschap niet geclassificeerd als een variabele. Het is dus niet mogelijk om een eigenschap door te geven als een in, outof ref argument, tenzij de eigenschap een verw-waarde heeft en daarom een variabeleverwijzing retourneert (§9,7). eindnotitie

Wanneer een eigenschapsdeclaratie een extern wijzigingsfunctie bevat, wordt de eigenschap als een externe eigenschap beschouwd. Omdat een externe eigenschapsdeclaratie geen werkelijke uitvoering biedt, moet elk van de accessor_bodyin zijn accessor_declarations een puntkomma zijn.

15.7.2 Statische eigenschappen en exemplaareigenschappen

Wanneer een eigenschapsdeclaratie een static wijzigingsfunctie bevat, wordt de eigenschap als een statische eigenschap beschouwd. Als er geen static wijzigingsfunctie aanwezig is, wordt de eigenschap als een instantieeigenschap beschouwd.

Een statische eigenschap is niet gekoppeld aan een specifiek exemplaar en het is een compilatiefout waarnaar moet worden verwezen this in de toegangsfactoren van een statische eigenschap.

Een exemplaareigenschap is gekoppeld aan een bepaald exemplaar van een klasse en dat exemplaar kan worden geopend als this (§12.8.14) in de accessors van die eigenschap.

De verschillen tussen statisch en exemplaarleden worden verder besproken in §15.3.8.

15.7.3 Accessors

Opmerking: Deze component is van toepassing op zowel eigenschappen (§15.7) als indexeerfuncties (§15.9). De component wordt geschreven in termen van eigenschappen, bij het lezen voor indexeerfuncties vervangen indexeerfunctie/indexeerfuncties voor eigenschappen/eigenschappen en raadpleegt u de lijst met verschillen tussen eigenschappen en indexeerfuncties die zijn opgegeven in §15.9.2. eindnotitie

De accessor_declarations van een eigenschap geven de uitvoerbare instructies op die zijn gekoppeld aan het schrijven en/of lezen van die eigenschap.

accessor_declarations
    : get_accessor_declaration set_accessor_declaration?
    | set_accessor_declaration get_accessor_declaration?
    ;

get_accessor_declaration
    : attributes? accessor_modifier? 'get' accessor_body
    ;

set_accessor_declaration
    : attributes? accessor_modifier? 'set' accessor_body
    ;

accessor_modifier
    : 'protected'
    | 'internal'
    | 'private'
    | 'protected' 'internal'
    | 'internal' 'protected'
    | 'protected' 'private'
    | 'private' 'protected'
    ;

accessor_body
    : block
    | '=>' expression ';'
    | ';' 
    ;

ref_get_accessor_declaration
    : attributes? accessor_modifier? 'get' ref_accessor_body
    ;
    
ref_accessor_body
    : block
    | '=>' 'ref' variable_reference ';'
    | ';'
    ;

De accessor_declarations bestaat uit een get_accessor_declaration, een set_accessor_declaration of beide. Elke declaratie van accessor bestaat uit optionele kenmerken, een optionele accessor_modifier, het token get of set, gevolgd door een accessor_body.

Voor een eigenschap met verw-waarde bestaat de ref_get_accessor_declaration uit optionele kenmerken, een optionele accessor_modifier, het token get, gevolgd door een ref_accessor_body.

Het gebruik van accessor_modifier svalt onder de volgende beperkingen:

  • Een accessor_modifier mag niet worden gebruikt in een interface of in een expliciete implementatie van interfaceleden.
  • Voor een eigenschap of indexeerfunctie die geen override wijzigingsfunctie heeft, is een accessor_modifier alleen toegestaan als de eigenschap of indexeerfunctie zowel een get- als set-accessor heeft en vervolgens slechts is toegestaan op een van deze accessors.
  • Voor een eigenschap of indexeerfunctie die een override wijzigingsfunctie bevat, moet een toegangsfunctie overeenkomen met de accessor_modifier, indien van toepassing, van de toegangsfunctie die wordt overschreven.
  • De accessor_modifier declareert een toegankelijkheid die strikt beperkend is dan de gedeclareerde toegankelijkheid van de eigenschap of indexeerfunctie zelf. Om precies te zijn:
    • Als de eigenschap of indexeerfunctie een gedeclareerde toegankelijkheid publicheeft, kan de toegankelijkheid die door accessor_modifier is gedeclareerd, ofwel private protected, protected internalinternalof protectedprivate.
    • Als de eigenschap of indexeerfunctie een gedeclareerde toegankelijkheid protected internalheeft, kan de toegankelijkheid die door accessor_modifier is gedeclareerd, ofwel private protected, protected privateinternalof protectedprivate.
    • Indien de eigenschap of indexeerfunctie een gedeclareerde toegankelijkheid van internal of protectedheeft, is de toegankelijkheid die door accessor_modifier is gedeclareerd, private protectedofwel private .
    • Indien de eigenschap of indexeerfunctie een gedeclareerde toegankelijkheid private protectedheeft, is de door accessor_modifierprivatetoegankelijkheid .
    • Als de eigenschap of indexeerfunctie een gedeclareerde toegankelijkheid privateheeft, kan er geen accessor_modifier worden gebruikt.

Voor abstract en extern niet-verw-waarde-eigenschappen is elke accessor_body voor elke opgegeven accessor gewoon een puntkomma. Een niet-abstracte, niet-externe eigenschap, maar niet een indexeerfunctie, kan ook de accessor_body hebben voor alle accessors die zijn opgegeven een puntkomma, in welk geval het een automatisch geïmplementeerde eigenschap is (§15.7.4). Een automatisch geïmplementeerde eigenschap heeft ten minste een get accessor. Voor de toegangsrechten van een andere niet-abstracte, niet-externe eigenschap is de accessor_body :

  • een blok dat aangeeft welke instructies moeten worden uitgevoerd wanneer de bijbehorende accessor wordt aangeroepen; of
  • een expressietekst, die bestaat uit => gevolgd door een expressie en een puntkomma, en geeft een enkele expressie aan die moet worden uitgevoerd wanneer de bijbehorende accessor wordt aangeroepen.

Voor abstract en extern ref-valued eigenschappen is de ref_accessor_body gewoon een puntkomma. Voor de toegangsfunctie van een andere niet-abstracte, niet-externe eigenschap is het ref_accessor_body :

  • een blok dat aangeeft welke instructies moeten worden uitgevoerd wanneer de get accessor wordt aangeroepen; of
  • een expressietekst, die bestaat uit => gevolgd door ref, een variable_reference en een puntkomma. De variabelereferentie wordt geëvalueerd wanneer de get accessor wordt aangeroepen.

Een get accessor voor een eigenschap zonder verwijzingswaarde komt overeen met een parameterloze methode met een retourwaarde van het eigenschapstype. Behalve als het doel van een toewijzing, wordt wanneer naar een dergelijke eigenschap wordt verwezen in een expressie waarnaar wordt verwezen, de get accessor aangeroepen om de waarde van de eigenschap te berekenen (§12.2.2).

De hoofdtekst van een get accessor voor een niet-verw-waarde-eigenschap moet voldoen aan de regels voor methoden voor het retourneren van waarden die worden beschreven in §15.6.11. In het bijzonder geven alle return instructies in de hoofdtekst van een get accessor een expressie op die impliciet kan worden omgezet in het eigenschapstype. Bovendien is het eindpunt van een get accessor niet bereikbaar.

Een get accessor voor een verw-waarde-eigenschap komt overeen met een methode zonder parameters met een retourwaarde van een variable_reference aan een variabele van het eigenschapstype. Wanneer naar een dergelijke eigenschap wordt verwezen in een expressie wordt de get accessor aangeroepen om de variable_reference waarde van de eigenschap te berekenen. Deze variabeleverwijzing wordt, net als andere, gebruikt om te lezen of, voor niet-lezende variable_references, de variabele waarnaar wordt verwezen, te schrijven zoals vereist door de context.

Voorbeeld: In het volgende voorbeeld ziet u een eigenschap met een verwijzingswaarde als het doel van een toewijzing:

class Program
{
    static int field;
    static ref int Property => ref field;

    static void Main()
    {
        field = 10;
        Console.WriteLine(Property); // Prints 10
        Property = 20;               // This invokes the get accessor, then assigns
                                     // via the resulting variable reference
        Console.WriteLine(field);    // Prints 20
    }
}

eindvoorbeeld

De hoofdtekst van een get accessor voor een eigenschap met verw-waarde moet voldoen aan de regels voor methoden die in §15.6.11 worden beschreven.

Een set accessor komt overeen met een methode met één waardeparameter van het eigenschapstype en een void retourtype. De impliciete parameter van een set accessor heeft altijd de naam value. Wanneer naar een eigenschap wordt verwezen als het doel van een opdracht (§12.21), of als operand van ++ of –- (§12.8.16, §12.9.6), wordt de set accessor aangeroepen met een argument dat de nieuwe waarde biedt (§12.21.2). De hoofdtekst van een set accessor voldoet aan de regels voor void methoden die in §15.6.11 worden beschreven. Met name retourinstructies in de hoofdtekst van de settoegangsfunctie mogen geen expressie opgeven. Omdat een set accessor impliciet een parameter heeft met de naam value, is het een compilatietijdfout voor een lokale variabele of constante declaratie in een set accessor om die naam te hebben.

Op basis van de aanwezigheid of afwezigheid van de get- en set-accessors, wordt een eigenschap als volgt geclassificeerd:

  • Een eigenschap die zowel een get-accessor als een set-accessor bevat, is een eigenschap voor lezen/schrijven.
  • Een eigenschap die alleen een get accessor heeft, wordt gezegd een alleen-lezen eigenschap te zijn. Het is een compilatiefout voor een eigenschap met het kenmerk Alleen-lezen als het doel van een toewijzing.
  • Een eigenschap met slechts een set accessor wordt gezegd als een alleen-schrijven eigenschap. Behalve als het doel van een toewijzing, is het een compilatiefout om te verwijzen naar een eigenschap alleen-schrijven in een expressie.

Opmerking: de operatoren voor- en navoegsels ++ en -- operatoren voor samengestelde toewijzingen kunnen niet worden toegepast op eigenschappen met alleen-schrijven, omdat deze operators de oude waarde van hun operand lezen voordat ze de nieuwe schrijven. eindnotitie

Voorbeeld: In de volgende code

public class Button : Control
{
    private string caption;

    public string Caption
    {
        get => caption;
        set
        {
            if (caption != value)
            {
                caption = value;
                Repaint();
            }
        }
    }

    public override void Paint(Graphics g, Rectangle r)
    {
        // Painting code goes here
    }
}

het Button besturingselement declareert een openbare Caption eigenschap. De get accessor van de eigenschap Caption retourneert de string opgeslagen in het privéveld caption . De instellen-accessor controleert of de nieuwe waarde verschilt van de huidige waarde, en als dat het zo is, wordt de nieuwe waarde opgeslagen en wordt het besturingselement opnieuw weergegeven. Eigenschappen volgen vaak het bovenstaande patroon: De get-accessor retourneert gewoon een waarde die is opgeslagen in een private veld en de set accessor wijzigt dat private veld en voert vervolgens eventuele extra acties uit die nodig zijn om de status van het object volledig bij te werken. Gezien de Button bovenstaande klasse is het volgende een voorbeeld van het gebruik van de Caption eigenschap:

Button okButton = new Button();
okButton.Caption = "OK"; // Invokes set accessor
string s = okButton.Caption; // Invokes get accessor

Hier wordt de set accessor aangeroepen door een waarde toe te wijzen aan de eigenschap en wordt de get-accessor aangeroepen door te verwijzen naar de eigenschap in een expressie.

eindvoorbeeld

De get- en set-accessors van een eigenschap zijn geen afzonderlijke leden en het is niet mogelijk om de toegangsrechten van een eigenschap afzonderlijk te declareren.

Voorbeeld: Het voorbeeld

class A
{
    private string name;

    // Error, duplicate member name
    public string Name
    { 
        get => name;
    }

    // Error, duplicate member name
    public string Name
    { 
        set => name = value;
    }
}

declareert geen enkele eigenschap voor lezen/schrijven. In plaats daarvan declareert het twee eigenschappen met dezelfde naam, één alleen-lezen en één alleen-schrijven. Omdat twee leden die in dezelfde klasse zijn gedeclareerd, niet dezelfde naam kunnen hebben, veroorzaakt het voorbeeld een compilatiefout.

eindvoorbeeld

Wanneer een afgeleide klasse een eigenschap declareert met dezelfde naam als een overgenomen eigenschap, verbergt de afgeleide eigenschap de overgenomen eigenschap met betrekking tot zowel lezen als schrijven.

Voorbeeld: In de volgende code

class A
{
    public int P
    {
        set {...}
    }
}

class B : A
{
    public new int P
    {
        get {...}
    }
}

de P eigenschap in B verbergt de P eigenschap A met betrekking tot zowel lezen als schrijven. Dus in de verklaringen

B b = new B();
b.P = 1;       // Error, B.P is read-only
((A)b).P = 1;  // Ok, reference to A.P

de toewijzing zorgt b.P ervoor dat een compilatiefout wordt gerapporteerd, omdat de eigenschap alleen-lezen P in B de eigenschap Alleen-schrijven P verborgen blijft in A. Houd er echter rekening mee dat een cast kan worden gebruikt voor toegang tot de verborgen P eigenschap.

eindvoorbeeld

In tegenstelling tot openbare velden bieden eigenschappen een scheiding tussen de interne status van een object en de bijbehorende openbare interface.

Voorbeeld: Bekijk de volgende code, die een Point struct gebruikt om een locatie weer te geven:

class Label
{
    private int x, y;
    private string caption;

    public Label(int x, int y, string caption)
    {
        this.x = x;
        this.y = y;
        this.caption = caption;
    }

    public int X => x;
    public int Y => y;
    public Point Location => new Point(x, y);
    public string Caption => caption;
}

Hier gebruikt de Label klasse twee int velden x en y, om de locatie op te slaan. De locatie wordt openbaar weergegeven als een X eigenschap en Y als een Location eigenschap van het type Point. Als het in een toekomstige versie van Labelde klasse handiger wordt om de locatie intern op Point te slaan, kan de wijziging worden aangebracht zonder dat dit van invloed is op de openbare interface van de klasse:

class Label
{
    private Point location;
    private string caption;

    public Label(int x, int y, string caption)
    {
        this.location = new Point(x, y);
        this.caption = caption;
    }

    public int X => location.X;
    public int Y => location.Y;
    public Point Location => location;
    public string Caption => caption;
}

Als x en y in plaats daarvan public readonly velden waren, zou het onmogelijk zijn geweest om een dergelijke wijziging aan te brengen in de Label klasse.

eindvoorbeeld

Opmerking: het blootstellen van de status via eigenschappen is niet noodzakelijkerwijs minder efficiënt dan het rechtstreeks beschikbaar maken van velden. Met name wanneer een eigenschap niet-virtueel is en slechts een kleine hoeveelheid code bevat, kan de uitvoeringsomgeving aanroepen naar accessors vervangen door de werkelijke code van de accessors. Dit proces wordt inlining genoemd en maakt eigenschapstoegang zo efficiënt als veldtoegang, maar behoudt de verhoogde flexibiliteit van eigenschappen. eindnotitie

Voorbeeld: Omdat het aanroepen van een get accessor conceptueel gelijk is aan het lezen van de waarde van een veld, wordt het beschouwd als een slechte programmeerstijl voor get-accessors om waarneembare bijwerkingen te hebben. In het voorbeeld

class Counter
{
    private int next;

    public int Next => next++;
}

de waarde van de Next eigenschap is afhankelijk van het aantal keren dat de eigenschap eerder is geopend. Als u de eigenschap opent, ontstaat er dus een waarneembaar neveneffect en moet de eigenschap in plaats daarvan worden geïmplementeerd als een methode.

De conventie 'geen neveneffecten' voor get-accessors betekent niet dat accessors altijd moeten worden geschreven om waarden te retourneren die zijn opgeslagen in velden. Get accessors berekenen vaak de waarde van een eigenschap door toegang te krijgen tot meerdere velden of methoden aan te roepen. Een correct ontworpen get accessor voert echter geen acties uit die waarneembare wijzigingen veroorzaken in de status van het object.

eindvoorbeeld

Eigenschappen kunnen worden gebruikt om de initialisatie van een resource te vertragen tot het moment dat er eerst naar wordt verwezen.

Voorbeeld:

public class Console
{
    private static TextReader reader;
    private static TextWriter writer;
    private static TextWriter error;

    public static TextReader In
    {
        get
        {
            if (reader == null)
            {
                reader = new StreamReader(Console.OpenStandardInput());
            }
            return reader;
        }
    }

    public static TextWriter Out
    {
        get
        {
            if (writer == null)
            {
                writer = new StreamWriter(Console.OpenStandardOutput());
            }
            return writer;
        }
    }

    public static TextWriter Error
    {
        get
        {
            if (error == null)
            {
                error = new StreamWriter(Console.OpenStandardError());
            }
            return error;
        }
    }
...
}

De Console klasse bevat drie eigenschappen, In, Outen Error, die respectievelijk de standaardinvoer-, uitvoer- en foutapparaten vertegenwoordigen. Door deze leden beschikbaar te maken als eigenschappen, kan de klasse de Console initialisatie vertragen totdat ze daadwerkelijk worden gebruikt. Als u bijvoorbeeld eerst naar de Out eigenschap verwijst, zoals in

Console.Out.WriteLine("hello, world");

de onderliggende waarde TextWriter voor het uitvoerapparaat wordt gemaakt. Als de toepassing echter geen verwijzing naar de In en Error eigenschappen maakt, worden er geen objecten voor deze apparaten gemaakt.

eindvoorbeeld

15.7.4 Automatisch geïmplementeerde eigenschappen

Een automatisch geïmplementeerde eigenschap (of automatische eigenschap voor kort), is een niet-abstracte, niet-extern, niet-verw-waarded eigenschap met alleen puntkomma's accessor_bodys. Automatische eigenschappen hebben een get-accessor en kunnen eventueel een set accessor hebben.

Wanneer een eigenschap wordt opgegeven als een automatisch geïmplementeerde eigenschap, is er automatisch een verborgen back-upveld beschikbaar voor de eigenschap en worden de accessors geïmplementeerd om van dat back-upveld te lezen en te schrijven. Het verborgen back-upveld is niet toegankelijk, het kan alleen worden gelezen en geschreven via de automatisch geïmplementeerde eigenschapstoegangsors, zelfs binnen het type dat het bevat. Als de automatische eigenschap geen set accessor heeft, wordt het backing-veld beschouwd readonly (§15.5.3). Net als een readonly veld kan een alleen-lezen auto-eigenschap ook worden toegewezen aan de hoofdtekst van een constructor van de insluitklasse. Een dergelijke toewijzing wordt rechtstreeks toegewezen aan het alleen-lezen back-upveld van de eigenschap.

Een automatische eigenschap kan eventueel een property_initializer hebben, die rechtstreeks op het backingveld wordt toegepast als een variable_initializer (§17.7).

Voorbeeld:

public class Point
{
    public int X { get; set; } // Automatically implemented
    public int Y { get; set; } // Automatically implemented
}

is gelijk aan de volgende declaratie:

public class Point
{
    private int x;
    private int y;

    public int X { get { return x; } set { x = value; } }
    public int Y { get { return y; } set { y = value; } }
}

eindvoorbeeld

Voorbeeld: In het volgende

public class ReadOnlyPoint
{
    public int X { get; }
    public int Y { get; }

    public ReadOnlyPoint(int x, int y)
    {
        X = x;
        Y = y;
    }
}

is gelijk aan de volgende declaratie:

public class ReadOnlyPoint
{
    private readonly int __x;
    private readonly int __y;
    public int X { get { return __x; } }
    public int Y { get { return __y; } }

    public ReadOnlyPoint(int x, int y)
    {
        __x = x;
        __y = y;
    }
}

De toewijzingen aan het alleen-lezenveld zijn geldig, omdat deze zich in de constructor bevinden.

eindvoorbeeld

Hoewel het backing-veld verborgen is, zijn er mogelijk veldkenmerken rechtstreeks op het veld toegepast via de property_declaration van de automatisch geïmplementeerde eigenschap (§15.7.1).

Voorbeeld: De volgende code

[Serializable]
public class Foo
{
    [field: NonSerialized]
    public string MySecret { get; set; }
}

resulteert in het veld gerichte kenmerk NonSerialized dat wordt toegepast op het door de compiler gegenereerde backingveld, alsof de code als volgt is geschreven:

[Serializable]
public class Foo
{
    [NonSerialized]
    private string _mySecretBackingField;
    public string MySecret
    {
        get { return _mySecretBackingField; }
        set { _mySecretBackingField = value; }
    }
}

eindvoorbeeld

15.7.5 Toegankelijkheid

Als een accessor een accessor_modifier heeft, wordt het toegankelijkheidsdomein (§7.5.3) van de toegangsgebruiker bepaald met behulp van de gedeclareerde toegankelijkheid van de accessor_modifier. Als een accessor geen accessor_modifier heeft, wordt het toegankelijkheidsdomein van de toegangsfunctie bepaald op basis van de gedeclareerde toegankelijkheid van de eigenschap of indexeerfunctie.

De aanwezigheid van een accessor_modifier heeft nooit invloed op het opzoeken van leden (§12.5) of overbelastingsresolutie (§12.6.4). De modifiers op de eigenschap of indexeerfunctie bepalen altijd aan welke eigenschap of indexeerfunctie is gebonden, ongeacht de context van de toegang.

Zodra een bepaalde niet-verw-waarde-eigenschap of een niet-verw-indexeerfunctie is geselecteerd, worden de toegankelijkheidsdomeinen van de betrokken toegangsrechten gebruikt om te bepalen of dat gebruik geldig is:

  • Als het gebruik als een waarde (§12.2.2) is, moet de get accessor bestaan en toegankelijk zijn.
  • Als het gebruik het doel is van een eenvoudige toewijzing (§12.21.2), moet de set accessor bestaan en toegankelijk zijn.
  • Als het gebruik het doel is van samengestelde toewijzing (§12.21.4) of als het doel van de ++ operatoren -- (§12.8.16, §12.9.6), moeten zowel de get-accessors als de settoegangsor bestaan en toegankelijk zijn.

Voorbeeld: In het volgende voorbeeld wordt de eigenschap A.Text verborgen door de eigenschap B.Text, zelfs in contexten waarin alleen de settoegangsfunctie wordt aangeroepen. De eigenschap B.Count is daarentegen niet toegankelijk voor klasse M, dus de toegankelijke eigenschap A.Count wordt in plaats daarvan gebruikt.

class A
{
    public string Text
    {
        get => "hello";
        set { }
    }

    public int Count
    {
        get => 5;
        set { }
    }
}

class B : A
{
    private string text = "goodbye";
    private int count = 0;

    public new string Text
    {
        get => text;
        protected set => text = value;
    }

    protected new int Count
    {
        get => count;
        set => count = value;
    }
}

class M
{
    static void Main()
    {
        B b = new B();
        b.Count = 12;       // Calls A.Count set accessor
        int i = b.Count;    // Calls A.Count get accessor
        b.Text = "howdy";   // Error, B.Text set accessor not accessible
        string s = b.Text;  // Calls B.Text get accessor
    }
}

eindvoorbeeld

Zodra een bepaalde referentiewaarde-eigenschap of referentiewaarde-indexeerder is geselecteerd---of het gebruik als een waarde is, als doel van een eenvoudige toewijzing of als doel van een samengestelde toewijzing---wordt het toegankelijkheidsdomein van de betrokken get-accessor gebruikt om te bepalen of het gebruik geldig is.

Een toegangsbeheerprogramma dat wordt gebruikt om een interface te implementeren, heeft geen accessor_modifier. Als slechts één toegangsfunctie wordt gebruikt om een interface te implementeren, kan de andere toegangsfunctie worden gedeclareerd met een accessor_modifier:

Voorbeeld:

public interface I
{
    string Prop { get; }
}

public class C : I
{
    public string Prop
    {
        get => "April";     // Must not have a modifier here
        internal set {...}  // Ok, because I.Prop has no set accessor
    }
}

eindvoorbeeld

15.7.6 Virtuele, verzegelde, onderdrukkings- en abstracte accessors

Opmerking: Deze component is van toepassing op zowel eigenschappen (§15.7) als indexeerfuncties (§15.9). De component wordt geschreven in termen van eigenschappen, bij het lezen voor indexeerfuncties vervangen indexeerfunctie/indexeerfuncties voor eigenschappen/eigenschappen en raadpleegt u de lijst met verschillen tussen eigenschappen en indexeerfuncties die zijn opgegeven in §15.9.2. eindnotitie

Een declaratie van virtuele eigenschappen geeft aan dat de toegangsrechten van de eigenschap virtueel zijn. De virtual wijzigingsfunctie is van toepassing op alle niet-privétoegangsors van een eigenschap. Wanneer een accessor van een virtuele eigenschap de privateaccessor_modifier heeft, is de privétoegangsor impliciet niet virtueel.

Een abstracte eigenschapsdeclaratie geeft aan dat de accessors van de eigenschap virtueel zijn, maar geen daadwerkelijke implementatie van de accessors biedt. In plaats daarvan moeten niet-abstracte afgeleide klassen hun eigen implementatie voor de accessors bieden door de eigenschap te overschrijven. Omdat een accessor voor een abstracte eigenschapsdeclaratie geen werkelijke implementatie biedt, bestaat de accessor_body simpelweg uit een puntkomma. Een abstracte eigenschap mag geen toegangsrechten hebben private .

Een eigenschapsdeclaratie die zowel de abstract als override modifiers bevat, geeft aan dat de eigenschap abstract is en een basiseigenschap overschrijft. De toegangsrechten van een dergelijke eigenschap zijn ook abstract.

Abstracte eigenschapsdeclaraties zijn alleen toegestaan in abstracte klassen (§15.2.2.2). De accessors van een overgenomen virtuele eigenschap kunnen worden overschreven in een afgeleide klasse door een eigenschapsdeclaratie op te nemen die een override instructie aangeeft. Dit wordt een onderdrukkingsdeclaratie van eigenschappen genoemd. Een overschrijvende eigenschapsdeclaratie declareert geen nieuwe eigenschap. In plaats daarvan is het eenvoudigweg gespecialiseerd in de implementaties van de toegangsrechten van een bestaande virtuele eigenschap.

De declaratie van onderdrukking en de overschreven basiseigenschap zijn vereist om dezelfde gedeclareerde toegankelijkheid te hebben. Met andere woorden, een onderdrukkingsdeclaratie zal de toegankelijkheid van de basiseigenschap niet wijzigen. Als de overschreven basiseigenschap echter intern is beveiligd en deze wordt gedeclareerd in een andere assembly dan de assembly die de onderdrukkingsdeclaratie bevat, wordt de gedeclareerde toegankelijkheid van de toegankelijkheid van de overschrijvingsdeclaratie beschermd. Als de overgenomen eigenschap slechts één toegangsrechten heeft (bijvoorbeeld als de overgenomen eigenschap alleen-lezen of alleen-schrijven is), bevat de overschrijvende eigenschap alleen die accessor. Als de overgenomen eigenschap beide accessors bevat (bijvoorbeeld als de overgenomen eigenschap lezen/schrijven is), kan de overschrijvende eigenschap één accessor of beide accessors bevatten. Er moet een identiteitsconversie zijn tussen het type overschrijven en de overgenomen eigenschap.

Een overschrijvende eigenschapsdeclaratie kan de sealed wijzigingsfunctie bevatten. Het gebruik van deze wijzigingsfunctie voorkomt dat een afgeleide klasse de eigenschap verder overschrijft. De toegangsrechten van een verzegelde eigenschap worden ook verzegeld.

Met uitzondering van verschillen in declaratie- en aanroepsyntaxis, virtuele, verzegelde, onderdrukkings- en abstracte toegangsrechten gedragen zich precies zoals virtuele, verzegelde, onderdrukkings- en abstracte methoden. De regels die worden beschreven in §15.6.4, §15.6.5, §15.6.6 en §15.6.7 zijn van toepassing alsof accessors methoden van een overeenkomstig formulier zijn:

  • Een get accessor komt overeen met een parameterloze methode met een retourwaarde van het eigenschapstype en dezelfde modifiers als de bijbehorende eigenschap.
  • Een set accessor komt overeen met een methode met één waardeparameter van het eigenschapstype, een ongeldig retourtype en dezelfde modifiers als de bijbehorende eigenschap.

Voorbeeld: In de volgende code

abstract class A
{
    int y;

    public virtual int X
    {
        get => 0;
    }

    public virtual int Y
    {
        get => y;
        set => y = value;
    }

    public abstract int Z { get; set; }
}

X is een virtuele eigenschap alleen-lezen, Y is een virtuele eigenschap voor lezen/schrijven en Z is een abstracte eigenschap voor lezen/schrijven. Omdat Z abstract is, wordt de met klasse A ook abstract gedeclareerd.

Hieronder ziet u een klasse die is afgeleid van A :

class B : A
{
    int z;

    public override int X
    {
        get => base.X + 1;
    }

    public override int Y
    {
        set => base.Y = value < 0 ? 0: value;
    }

    public override int Z
    {
        get => z;
        set => z = value;
    }
}

Hier worden de declaraties van X, Yen Z worden eigenschappendeclaraties overschreven. Elke eigenschapsdeclaratie komt exact overeen met de toegankelijkheidsaanpassingen, het type en de naam van de bijbehorende overgenomen eigenschap. De get-accessor van X en de set-accessor van het gebruik van Y het basiswoord voor toegang tot de overgenomen toegangsrechten. De verklaring van Z onderdrukkingen overschrijft beide abstracte toegangsrechten, dus er zijn geen openstaande abstract functieleden in Ben B mogen een niet-abstracte klasse zijn.

eindvoorbeeld

Wanneer een eigenschap wordt gedeclareerd als een onderdrukking, zijn alle overschreven toegangsrechten toegankelijk voor de onderdrukkingscode. Bovendien komt de gedeclareerde toegankelijkheid van zowel de eigenschap als de indexeerfunctie zelf en van de toegangsrechten overeen met die van het overschreven lid en de toegangsrechten.

Voorbeeld:

public class B
{
    public virtual int P
    {
        get {...}
        protected set {...}
    }
}

public class D: B
{
    public override int P
    {
        get {...}            // Must not have a modifier here
        protected set {...}  // Must specify protected here
    }
}

eindvoorbeeld

15.8 Gebeurtenissen

15.8.1 Algemeen

Een gebeurtenis is een lid waarmee een object of klasse meldingen kan opgeven. Clients kunnen uitvoerbare code voor gebeurtenissen koppelen door gebeurtenis-handlers op te leveren.

Gebeurtenissen worden gedeclareerd met behulp van event_declarations:

event_declaration
    : attributes? event_modifier* 'event' type variable_declarators ';'
    | attributes? event_modifier* 'event' type member_name
        '{' event_accessor_declarations '}'
    ;

event_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'static'
    | 'virtual'
    | 'sealed'
    | 'override'
    | 'abstract'
    | 'extern'
    | unsafe_modifier   // unsafe code support
    ;

event_accessor_declarations
    : add_accessor_declaration remove_accessor_declaration
    | remove_accessor_declaration add_accessor_declaration
    ;

add_accessor_declaration
    : attributes? 'add' block
    ;

remove_accessor_declaration
    : attributes? 'remove' block
    ;

unsafe_modifier (§23.2) is alleen beschikbaar in onveilige code (§23).

Een event_declaration kan een set kenmerken (§22) en een van de toegestane soorten toegankelijkheid (§15.3.6), de new (§15.3.5), (§15.3.5), static (§15.6.3, §15.8.4), virtual (§15.6.4, §15.8.5), override (§15.6.5, §15.8.5), sealed (§15.6.6), abstract (§15.6.7, §15.8.5) en extern (§15.6.8) modifiers.

Gebeurtenisdeclaraties zijn onderworpen aan dezelfde regels als methodedeclaraties (§15.6) met betrekking tot geldige combinaties van modifiers.

Het type gebeurtenisdeclaratie is een delegate_type (§8.2.8) en dat delegate_type minstens zo toegankelijk is als de gebeurtenis zelf (§7.5.5).

Een gebeurtenisdeclaratie kan event_accessor_declarations bevatten. Indien dit echter niet gebeurt voor niet-externe, niet-abstracte gebeurtenissen, verstrekt een compiler deze automatisch (§15.8.2); voor extern gebeurtenissen worden de accessors extern verstrekt.

Een gebeurtenisdeclaratie die event_accessor_declaration s weglaat, definieert een of meer gebeurtenissen, één voor elk van de variable_declarators. De kenmerken en modifiers zijn van toepassing op alle leden die door een dergelijke event_declaration zijn gedeclareerd.

Het is een compilatiefout voor een event_declaration om zowel de abstract modifier als event_accessor_declarations op te nemen.

Wanneer een gebeurtenisdeclaratie een extern wijzigingsfunctie bevat, wordt de gebeurtenis beschouwd als een externe gebeurtenis. Omdat een externe gebeurtenisdeclaratie geen werkelijke implementatie biedt, is het een fout dat deze zowel de extern modifier als event_accessor_declarations bevat.

Het is een compilatiefout voor een variable_declarator van een gebeurtenisdeclaratie met een abstract of external modifier om een variable_initializer op te nemen.

Een gebeurtenis kan worden gebruikt als de linkeroperand van de += en -= operators. Deze operators worden respectievelijk gebruikt om gebeurtenis-handlers toe te voegen aan of om gebeurtenis-handlers uit een gebeurtenis te verwijderen en de toegangsmodifiers van de gebeurtenis bepalen de contexten waarin dergelijke bewerkingen zijn toegestaan.

De enige bewerkingen die zijn toegestaan voor een gebeurtenis per code die zich buiten het type bevindt waarin die gebeurtenis wordt gedeclareerd, zijn += en -=. Hoewel dergelijke code handlers voor een gebeurtenis kan toevoegen en verwijderen, kan deze de onderliggende lijst met gebeurtenis-handlers niet rechtstreeks verkrijgen of wijzigen.

In een bewerking van het formulier x += y of x –= y, wanneer x is een gebeurtenis het resultaat van de bewerking heeft het type void (§12.21.5) (in tegenstelling tot het type x, met de waarde van x na de toewijzing, zoals voor andere de += operatoren -= die zijn gedefinieerd voor niet-gebeurtenistypen). Dit voorkomt dat externe code indirect de onderliggende gemachtigde van een gebeurtenis onderzoekt.

Voorbeeld: In het volgende voorbeeld ziet u hoe gebeurtenis-handlers zijn gekoppeld aan exemplaren van de Button klasse:

public delegate void EventHandler(object sender, EventArgs e);

public class Button : Control
{
    public event EventHandler Click;
}

public class LoginDialog : Form
{
    Button okButton;
    Button cancelButton;

    public LoginDialog()
    {
        okButton = new Button(...);
        okButton.Click += new EventHandler(OkButtonClick);
        cancelButton = new Button(...);
        cancelButton.Click += new EventHandler(CancelButtonClick);
    }

    void OkButtonClick(object sender, EventArgs e)
    {
        // Handle okButton.Click event
    }

    void CancelButtonClick(object sender, EventArgs e)
    {
        // Handle cancelButton.Click event
    }
}

Hier maakt de LoginDialog instantieconstructor twee Button exemplaren en koppelt de gebeurtenis-handlers aan de Click gebeurtenissen.

eindvoorbeeld

15.8.2 Veldachtige gebeurtenissen

In de programmatekst van de klasse of struct die de declaratie van een gebeurtenis bevat, kunnen bepaalde gebeurtenissen worden gebruikt als velden. Om op deze manier te worden gebruikt, mag een gebeurtenis niet abstract of extern zijn en mag deze niet expliciet event_accessor_declarations bevatten. Een dergelijke gebeurtenis kan worden gebruikt in elke context die een veld toestaat. Het veld bevat een gemachtigde (§20), die verwijst naar de lijst met gebeurtenis-handlers die aan de gebeurtenis zijn toegevoegd. Als er geen gebeurtenis-handlers zijn toegevoegd, bevat nullhet veld .

Voorbeeld: In de volgende code

public delegate void EventHandler(object sender, EventArgs e);

public class Button : Control
{
    public event EventHandler Click;

    protected void OnClick(EventArgs e)
    {
        EventHandler handler = Click;
        if (handler != null)
        {
            handler(this, e);
        }
    }

    public void Reset() => Click = null;
}

Click wordt gebruikt als een veld binnen de Button klasse. Zoals in het voorbeeld wordt getoond, kan het veld worden onderzocht, gewijzigd en gebruikt in expressies voor gemachtigde aanroepen. De OnClick methode in de Button klasse 'genereert' de Click gebeurtenis. Het idee van het genereren van een gebeurtenis komt precies overeen met het aanroepen van de gemachtigde die wordt vertegenwoordigd door de gebeurtenis. Er zijn dus geen speciale taalconstructies voor het genereren van gebeurtenissen. Houd er rekening mee dat de aanroep van de gemachtigde wordt voorafgegaan door een controle die ervoor zorgt dat de gemachtigde niet null is en dat de controle wordt uitgevoerd op een lokale kopie om de veiligheid van threads te waarborgen.

Buiten de verklaring van de Button klasse kan het Click lid alleen worden gebruikt aan de linkerkant van de += en –= operators, zoals in

b.Click += new EventHandler(...);

waarmee een gemachtigde wordt toegevoegd aan de aanroeplijst van de Click gebeurtenis en

Click –= new EventHandler(...);

waarmee een gemachtigde uit de aanroeplijst van de Click gebeurtenis wordt verwijderd.

eindvoorbeeld

Bij het compileren van een veldachtige gebeurtenis maakt een compiler automatisch geheugen aan voor de gedelegeerde en maakt hij toegangsmethoden voor de gebeurtenis die event handlers aan het gedelegeerde object toevoegt of verwijdert. De toevoegings- en verwijderingsbewerkingen zijn threadveilig en kunnen (maar niet verplicht) worden uitgevoerd tijdens het vasthouden van de vergrendeling (§13.13) op het betreffende object voor een instantie-gebeurtenis, of het System.Type object (§12.8.18) voor een statische gebeurtenis.

Opmerking: Een gebeurtenisdeclaratie van het exemplaar van het formulier:

class X
{
    public event D Ev;
}

moet worden gecompileerd tot iets dat gelijk is aan:

class X
{
    private D __Ev; // field to hold the delegate

    public event D Ev
    {
        add
        {
            /* Add the delegate in a thread safe way */
        }
        remove
        {
            /* Remove the delegate in a thread safe way */
        }
    }
}

In de klasse Xworden verwijzingen aan de linkerkant van de Ev en += operators veroorzaakt –= dat de toegangsrechten voor toevoegen en verwijderen worden aangeroepen. Alle andere verwijzingen Ev worden gecompileerd om in plaats daarvan te verwijzen naar het verborgen veld __Ev (§12.8.7). De naam '__Ev' is willekeurig. Het verborgen veld kan elke naam of helemaal geen naam hebben.

eindnotitie

15.8.3 Gebeurtenistoegangsors

Opmerking: gebeurtenisdeclaraties laten meestal event_accessor_declarations weg, zoals in het Button bovenstaande voorbeeld. Ze kunnen bijvoorbeeld worden opgenomen als de opslagkosten van één veld per gebeurtenis niet acceptabel zijn. In dergelijke gevallen kan een klasse event_accessor_declarations bevatten en een privémechanisme gebruiken voor het opslaan van de lijst met gebeurtenis-handlers. eindnotitie

De event_accessor_declarations van een gebeurtenis geven de uitvoerbare instructies op die zijn gekoppeld aan het toevoegen en verwijderen van gebeurtenis-handlers.

De declaraties van toegangsrechten bestaan uit een add_accessor_declaration en een remove_accessor_declaration. Elke declaratie van een accessor bestaat uit het toevoegen of verwijderen van het token, gevolgd door een blok. Het blok dat is gekoppeld aan een add_accessor_declaration geeft de instructies op die moeten worden uitgevoerd wanneer een gebeurtenis-handler wordt toegevoegd en het blok dat is gekoppeld aan een remove_accessor_declaration geeft de instructies op die moeten worden uitgevoerd wanneer een gebeurtenis-handler wordt verwijderd.

Elke add_accessor_declaration en remove_accessor_declaration komt overeen met een methode met één waardeparameter van het gebeurtenistype en een void retourtype. De impliciete parameter van een gebeurtenistoegangsor heeft de naam value. Wanneer een gebeurtenis wordt gebruikt in een gebeurtenistoewijzing, wordt de juiste gebeurtenistoegangsor gebruikt. Als de toewijzingsoperator is, wordt += de add accessor gebruikt en als de toewijzingsoperator is, wordt –= de verwijdertoegangsor gebruikt. In beide gevallen wordt de juiste operand van de toewijzingsoperator gebruikt als het argument voor de gebeurtenistoegangsor. Het blok van een add_accessor_declaration of een remove_accessor_declaration voldoet aan de regels voor void methoden die in §15.6.9 worden beschreven. In het bijzonder return zijn instructies in een dergelijk blok niet toegestaan om een expressie op te geven.

Omdat een gebeurtenistoegangsor impliciet een parameter heeft met de naam value, is het een compileertijdfout voor een lokale variabele of constante die is gedeclareerd in een gebeurtenistoegangspunt om die naam te hebben.

Voorbeeld: In de volgende code


class Control : Component
{
    // Unique keys for events
    static readonly object mouseDownEventKey = new object();
    static readonly object mouseUpEventKey = new object();

    // Return event handler associated with key
    protected Delegate GetEventHandler(object key) {...}

    // Add event handler associated with key
    protected void AddEventHandler(object key, Delegate handler) {...}

    // Remove event handler associated with key
    protected void RemoveEventHandler(object key, Delegate handler) {...}

    // MouseDown event
    public event MouseEventHandler MouseDown
    {
        add { AddEventHandler(mouseDownEventKey, value); }
        remove { RemoveEventHandler(mouseDownEventKey, value); }
    }

    // MouseUp event
    public event MouseEventHandler MouseUp
    {
        add { AddEventHandler(mouseUpEventKey, value); }
        remove { RemoveEventHandler(mouseUpEventKey, value); }
    }

    // Invoke the MouseUp event
    protected void OnMouseUp(MouseEventArgs args)
    {
        MouseEventHandler handler;
        handler = (MouseEventHandler)GetEventHandler(mouseUpEventKey);
        if (handler != null)
        {
            handler(this, args);
        }
    }
}

de Control klasse implementeert een intern opslagmechanisme voor gebeurtenissen. De AddEventHandler methode koppelt een gedelegeerde waarde aan een sleutel, de GetEventHandler methode retourneert de gemachtigde die momenteel aan een sleutel is gekoppeld en de RemoveEventHandler methode verwijdert een gemachtigde als gebeurtenis-handler voor de opgegeven gebeurtenis. Waarschijnlijk is het onderliggende opslagmechanisme zodanig ontworpen dat er geen kosten in rekening worden gebracht voor het koppelen van een null-gedelegeerde waarde aan een sleutel, waardoor niet-verwerkte gebeurtenissen geen opslag verbruiken.

eindvoorbeeld

15.8.4 Statische gebeurtenissen en instantie-gebeurtenissen

Wanneer een gebeurtenisdeclaratie een static wijzigingsfunctie bevat, wordt de gebeurtenis beschouwd als een statische gebeurtenis. Wanneer er geen static wijzigingsfunctie aanwezig is, wordt de gebeurtenis beschouwd als een instantie-gebeurtenis.

Een statische gebeurtenis is niet gekoppeld aan een specifiek exemplaar en het is een compilatiefout waarnaar wordt verwezen this in de accessors van een statische gebeurtenis.

Een exemplaargebeurtenis is gekoppeld aan een bepaald exemplaar van een klasse en dit exemplaar kan worden geopend als this (§12.8.14) in de accessors van die gebeurtenis.

De verschillen tussen statisch en exemplaarleden worden verder besproken in §15.3.8.

15.8.5 Virtuele, verzegelde, onderdrukkings- en abstracte accessors

Een declaratie van virtuele gebeurtenissen geeft aan dat de accessors van die gebeurtenis virtueel zijn. De virtual wijzigingsfunctie is van toepassing op beide toegangsrechten van een gebeurtenis.

Een abstracte gebeurtenisdeclaratie geeft aan dat de accessors van de gebeurtenis virtueel zijn, maar geen daadwerkelijke implementatie van de accessors bieden. In plaats daarvan zijn niet-abstracte afgeleide klassen vereist om hun eigen implementatie voor de accessors te bieden door de gebeurtenis te overschrijven. Omdat een toegangsverantwoordelijke voor een abstracte gebeurtenisdeclaratie geen daadwerkelijke uitvoering biedt, geeft deze geen event_accessor_declarations.

Een gebeurtenisdeclaratie die zowel de abstract als override modifiers bevat, geeft aan dat de gebeurtenis abstract is en een basisgebeurtenis overschrijft. De toegangsrechten van een dergelijke gebeurtenis zijn ook abstract.

Abstracte gebeurtenisdeclaraties zijn alleen toegestaan in abstracte klassen (§15.2.2.2).

De toegangsrechten van een overgenomen virtuele gebeurtenis kunnen worden overschreven in een afgeleide klasse door een gebeurtenisdeclaratie op te nemen die een override wijzigingsfunctie aangeeft. Dit staat bekend als een overschrijvende gebeurtenisdeclaratie. Een overschrijvende gebeurtenisdeclaratie declareert geen nieuwe gebeurtenis. In plaats daarvan zijn de implementaties van de accessors van een bestaande virtuele gebeurtenis gespecialiseerd.

In een declaratie van overschrijvende gebeurtenissen moeten exact dezelfde toegankelijkheidsaanpassingen en naam worden opgegeven als de overschreven gebeurtenis. Er moet een identiteitsconversie zijn tussen het type overschrijven en de overschreven gebeurtenis, en zowel de toegangsrechten voor toevoegen als verwijderen moeten worden opgegeven in de declaratie.

Een declaratie van overschrijvende gebeurtenissen kan de sealed wijzigingsfunctie bevatten. Het gebruik van this modifier voorkomt dat een afgeleide klasse de gebeurtenis verder overschrijft. De toegangsrechten van een verzegelde gebeurtenis worden ook verzegeld.

Het is een compilatiefout voor een overschrijvende gebeurtenisdeclaratie om een new wijzigingsfunctie op te nemen.

Met uitzondering van verschillen in declaratie- en aanroepsyntaxis, virtuele, verzegelde, onderdrukkings- en abstracte toegangsrechten gedragen zich precies zoals virtuele, verzegelde, onderdrukkings- en abstracte methoden. De regels die worden beschreven in §15.6.4, §15.6.5, §15.6.6 en §15.6.7 zijn van toepassing alsof accessors methoden van een corresponderend formulier zijn. Elke accessor komt overeen met een methode met één waardeparameter van het gebeurtenistype, een void retourtype en dezelfde modifiers als de bijbehorende gebeurtenis.

15.9 Indexeerfuncties

15.9.1 Algemeen

Een indexeerfunctie is een lid waarmee een object op dezelfde manier kan worden geïndexeerd als een matrix. Indexeerfuncties worden gedeclareerd met indexer_declaration s:

indexer_declaration
    : attributes? indexer_modifier* indexer_declarator indexer_body
    | attributes? indexer_modifier* ref_kind indexer_declarator ref_indexer_body
    ;

indexer_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'virtual'
    | 'sealed'
    | 'override'
    | 'abstract'
    | 'extern'
    | unsafe_modifier   // unsafe code support
    ;

indexer_declarator
    : type 'this' '[' parameter_list ']'
    | type interface_type '.' 'this' '[' parameter_list ']'
    ;

indexer_body
    : '{' accessor_declarations '}' 
    | '=>' expression ';'
    ;  

ref_indexer_body
    : '{' ref_get_accessor_declaration '}'
    | '=>' 'ref' variable_reference ';'
    ;

unsafe_modifier (§23.2) is alleen beschikbaar in onveilige code (§23).

Er zijn twee soorten indexer_declaration:

  • De eerste declareert een indexeerfunctie zonder verw-waarde. De waarde heeft een typetype. Dit type indexeerfunctie kan leesbaar en/of schrijfbaar zijn.
  • De tweede declareert een indexeerfunctie met verw-waarden. De waarde is een variable_reference (§9,5 Dit type indexeerfunctie kan alleen worden gelezen.

Een indexer_declaration kan een set kenmerken (§22) en een van de toegestane soorten toegankelijkheid (§15.3.6), de new (§15.3.5), (§15.3.5), virtual (§15.15. 6.4), override (§15.6.5), sealed (§15.6.6), abstract (§15.6.7) en extern (§15.6.8) modifiers.

Indexeerdeclaraties zijn onderworpen aan dezelfde regels als methodedeclaraties (§15.6) met betrekking tot geldige combinaties van modifiers, met uitzondering dat de static modifier niet is toegestaan voor een indexeerfunctiedeclaratie.

Het type indexeerfunctiedeclaratie specificeert het elementtype van de indexeerfunctie die door de declaratie is ingevoerd.

Opmerking: Aangezien indexeerfuncties zijn ontworpen om te worden gebruikt in matrixelementachtige contexten, wordt het type termelement zoals gedefinieerd voor een matrix ook gebruikt met een indexeerfunctie. eindnotitie

Tenzij de indexeerfunctie een expliciete implementatie van interfaceleden is, wordt het type gevolgd door het trefwoord this. Voor een expliciete implementatie van interfaceleden wordt het . In tegenstelling tot andere leden hebben indexeerfuncties geen door de gebruiker gedefinieerde namen.

De parameter_list geeft de parameters van de indexeerfunctie op. De parameterlijst van een indexeerfunctie komt overeen met die van een methode (§15.6.2), behalve dat ten minste één parameter moet worden opgegeven en dat de thisref, en out parametermodifiers niet zijn toegestaan.

Het type indexeerfunctie en elk van de typen waarnaar in de parameter_list wordt verwezen, moet ten minste zo toegankelijk zijn als de indexeerfunctie zelf (§7.5.5).

Een indexer_body kan bestaan uit een instructietekst (§15.7.1) of een expressietekst (§15.6.1). In een hoofdtekst van de instructie declareert accessor_declarations, die tussen "{" en "}" tokens worden geplaatst, de toegangsrechten (§15.7.3) van de indexeerfunctie. De accessors geven de uitvoerbare instructies op die zijn gekoppeld aan het lezen en schrijven van indexeerelementen.

In een indexer_body een expressietekst die bestaat uit '=>' gevolgd door een expressie E en een puntkomma exact gelijk is aan de hoofdtekst { get { return E; } }van de instructie, en kan daarom alleen worden gebruikt om indexeerfuncties met het kenmerk Alleen-lezen op te geven waarbij het resultaat van de get-accessor wordt gegeven door één expressie.

Een ref_indexer_body kan bestaan uit een hoofdtekst van de instructie of een hoofdtekst van een expressie. In een instructietekst geeft een get_accessor_declaration de get accessor (§15.7.3) van de indexeerfunctie aan. De accessor geeft de uitvoerbare instructies op die zijn gekoppeld aan het lezen van de indexeerfunctie.

In een ref_indexer_body een expressietekst die bestaat uit => gevolgd door ref, is een variable_referenceV en een puntkomma exact gelijk aan de hoofdtekst { get { return ref V; } }van de instructie.

Opmerking: Hoewel de syntaxis voor het openen van een indexeerelement hetzelfde is als voor een matrixelement, wordt een indexeerfunctieelement niet geclassificeerd als een variabele. Het is dus niet mogelijk om een indexeerelement door te geven als een , of in argument, tenzij de indexeerfunctie ref-valued is en daarom een verwijzing retourneert (out). ref eindnotitie

De parameter_list van een indexeerfunctie definieert de handtekening (§7.6) van de indexeerfunctie. Met name de handtekening van een indexeerfunctie bestaat uit het aantal en de typen parameters. Het elementtype en de namen van de parameters maken geen deel uit van de handtekening van een indexeerfunctie.

De handtekening van een indexeerfunctie verschilt van de handtekeningen van alle andere indexeerfuncties die in dezelfde klasse zijn aangegeven.

Wanneer een indexeerfunctiedeclaratie een extern wijzigingsfunctie bevat, wordt de indexeerfunctie als een externe indexeerfunctie beschouwd. Omdat een externe indexeerfunctiedeclaratie geen werkelijke uitvoering biedt, moet elk van de accessor_bodyin zijn accessor_declarations een puntkomma zijn.

Voorbeeld: In het onderstaande voorbeeld wordt een BitArray klasse declareert waarmee een indexeerfunctie wordt geïmplementeerd voor toegang tot de afzonderlijke bits in de bitmatrix.

class BitArray
{
    int[] bits;
    int length;

    public BitArray(int length)
    {
        if (length < 0)
        {
            throw new ArgumentException();
        }
        bits = new int[((length - 1) >> 5) + 1];
        this.length = length;
    }

    public int Length => length;

    public bool this[int index]
    {
        get
        {
            if (index < 0 || index >= length)
            {
                throw new IndexOutOfRangeException();
            }
            return (bits[index >> 5] & 1 << index) != 0;
        }
        set
        {
            if (index < 0 || index >= length)
            {
                throw new IndexOutOfRangeException();
            }
            if (value)
            {
                bits[index >> 5] |= 1 << index;
            }
            else
            {
                bits[index >> 5] &= ~(1 << index);
            }
        }
    }
}

Een exemplaar van de BitArray klasse verbruikt aanzienlijk minder geheugen dan een corresponderende bool[] waarde (aangezien elke waarde van het eerste deel slechts één bit in beslag neemt in plaats van de laatste), bytemaar dezelfde bewerkingen als een bool[].

De volgende CountPrimes klasse maakt gebruik van een BitArray en het klassieke 'zeef'-algoritme om het aantal priemgetallen tussen 2 en een gegeven maximum te berekenen:

class CountPrimes
{
    static int Count(int max)
    {
        BitArray flags = new BitArray(max + 1);
        int count = 0;
        for (int i = 2; i <= max; i++)
        {
            if (!flags[i])
            {
                for (int j = i * 2; j <= max; j += i)
                {
                    flags[j] = true;
                }
                count++;
            }
        }
        return count;
    }

    static void Main(string[] args)
    {
        int max = int.Parse(args[0]);
        int count = Count(max);
        Console.WriteLine($"Found {count} primes between 2 and {max}");
    }
}

Houd er rekening mee dat de syntaxis voor het openen van elementen van de elementen BitArray precies hetzelfde is als voor een bool[].

In het volgende voorbeeld ziet u een rasterklasse van 26×10 met een indexeerfunctie met twee parameters. De eerste parameter moet een hoofdletter of kleine letter in het bereik A-Z zijn en de tweede moet een geheel getal in het bereik 0-9 zijn.

class Grid
{
    const int NumRows = 26;
    const int NumCols = 10;
    int[,] cells = new int[NumRows, NumCols];

    public int this[char row, int col]
    {
        get
        {
            row = Char.ToUpper(row);
            if (row < 'A' || row > 'Z')
            {
                throw new ArgumentOutOfRangeException("row");
            }
            if (col < 0 || col >= NumCols)
            {
                throw new ArgumentOutOfRangeException ("col");
            }
            return cells[row - 'A', col];
        }
        set
        {
            row = Char.ToUpper(row);
            if (row < 'A' || row > 'Z')
            {
                throw new ArgumentOutOfRangeException ("row");
            }
            if (col < 0 || col >= NumCols)
            {
                throw new ArgumentOutOfRangeException ("col");
            }
            cells[row - 'A', col] = value;
        }
    }
}

eindvoorbeeld

15.9.2 Indexeerfunctie en eigenschapsverschillen

Indexeerfuncties en eigenschappen zijn in concept vergelijkbaar, maar verschillen op de volgende manieren:

  • Een eigenschap wordt geïdentificeerd met de naam, terwijl een indexeerfunctie wordt geïdentificeerd door de handtekening.
  • Een eigenschap wordt geopend via een simple_name (§12.8.4) of een member_access (§12.8.7), terwijl een indexeerelement toegankelijk is via een element_access (§12.8.12.3).
  • Een eigenschap kan een statisch lid zijn, terwijl een indexeerfunctie altijd een exemplaarlid is.
  • Een get accessor van een eigenschap komt overeen met een methode zonder parameters, terwijl een get accessor van een indexeerfunctie overeenkomt met een methode met dezelfde parameterlijst als de indexeerfunctie.
  • Een set accessor van een eigenschap komt overeen met een methode met één parameter genaamd value, terwijl een set accessor van een indexeerfunctie overeenkomt met een methode met dezelfde parameterlijst als de indexeerfunctie, plus een extra parameter met de naam value.
  • Het is een compilatiefout voor een indexeerfunctietoegangsfunctie om een lokale variabele of lokale constante met dezelfde naam als een indexeerparameter te declareren.
  • In een overschrijvende eigenschapsdeclaratie wordt de overgenomen eigenschap geopend met behulp van de syntaxis base.P, waar P is de naam van de eigenschap. In een overschrijvende indexeerfunctiedeclaratie wordt de overgenomen indexeerfunctie geopend met behulp van de syntaxis base[E], waarbij E een door komma's gescheiden lijst met expressies is.
  • Er is geen concept van een 'automatisch geïmplementeerde indexeerfunctie'. Het is een fout dat een niet-abstracte, niet-externe indexeerfunctie met puntkomma accessor_bodys.

Afgezien van deze verschillen zijn alle regels die zijn gedefinieerd in §15.7.3, §15.7.5 en §15.7.6 van toepassing op toegangsrechten voor indexeerfuncties en eigenschappentoegangsors.

Deze vervanging van eigenschappen/eigenschappen door indexeerfunctie/indexeerfuncties bij het lezen van §15.7.3, §15.7.5 en §15.7.6 is ook van toepassing op gedefinieerde termen. In het bijzonder wordt de eigenschap lezen/schrijven indexeerfunctie voor lezen/schrijven, de eigenschapAlleen-lezen wordt indexeerfunctie voor alleen-lezen en de eigenschap Alleen-schrijven wordt de indexeerfunctie alleen-schrijven.

15.10 Operators

15.10.1 Algemeen

Een operator is een lid dat de betekenis van een expressieoperator definieert die kan worden toegepast op exemplaren van de klasse. Operators worden gedeclareerd met behulp van operator_declarations:

operator_declaration
    : attributes? operator_modifier+ operator_declarator operator_body
    ;

operator_modifier
    : 'public'
    | 'static'
    | 'extern'
    | unsafe_modifier   // unsafe code support
    ;

operator_declarator
    : unary_operator_declarator
    | binary_operator_declarator
    | conversion_operator_declarator
    ;

unary_operator_declarator
    : type 'operator' overloadable_unary_operator '(' fixed_parameter ')'
    ;

logical_negation_operator
    : '!'
    ;

overloadable_unary_operator
    : '+' | '-' | logical_negation_operator | '~' | '++' | '--' | 'true' | 'false'
    ;

binary_operator_declarator
    : type 'operator' overloadable_binary_operator
        '(' fixed_parameter ',' fixed_parameter ')'
    ;

overloadable_binary_operator
    : '+'  | '-'  | '*'  | '/'  | '%'  | '&' | '|' | '^'  | '<<' 
    | right_shift | '==' | '!=' | '>' | '<' | '>=' | '<='
    ;

conversion_operator_declarator
    : 'implicit' 'operator' type '(' fixed_parameter ')'
    | 'explicit' 'operator' type '(' fixed_parameter ')'
    ;

operator_body
    : block
    | '=>' expression ';'
    | ';'
    ;

unsafe_modifier (§23.2) is alleen beschikbaar in onveilige code (§23).

Opmerking: De logische voorvoegselondertekening (§12.9.4) en postfix null-forgiving-operators (§12.8.9), terwijl deze worden vertegenwoordigd door hetzelfde lexicale token (!), zijn verschillend. De laatste is geen overbelastingsoperator. eindnotitie

Er zijn drie categorieën overbelaste operators: Unaire operators (§15.10.2), binaire operatoren (§15.10.3) en conversieoperators (§15.10.4).

Het operator_body is een puntkomma, een bloktekst (§15.6.1) of een expressietekst (§15.6.1). Een bloktekst bestaat uit een blok, waarmee de instructies worden opgegeven die moeten worden uitgevoerd wanneer de operator wordt aangeroepen. Het blok voldoet aan de regels voor methoden voor retourneert waarden die worden beschreven in §15.6.11. Een expressietekst bestaat uit => gevolgd door een expressie en een puntkomma en geeft één expressie aan die moet worden uitgevoerd wanneer de operator wordt aangeroepen.

Voor extern operators bestaat de operator_body uit een puntkomma. Voor alle andere operators is de operator_body een bloktekst of een expressietekst.

De volgende regels zijn van toepassing op alle operatordeclaraties:

  • Een operatordeclaratie omvat zowel een public als een static wijzigingsfunctie.
  • De parameters van een operator mogen geen andere modifiers hebben dan in.
  • De handtekening van een exploitant (§15.10.2, §15.10.3, §15.10.4) verschilt van de handtekeningen van alle andere exploitanten die in dezelfde klasse zijn aangegeven.
  • Alle typen waarnaar wordt verwezen in een operatordeclaratie moeten ten minste even toegankelijk zijn als de exploitant zelf (§7.5.5).
  • Het is een fout dat dezelfde wijziging meerdere keren wordt weergegeven in een operatordeclaratie.

Elke operatorcategorie legt aanvullende beperkingen op, zoals beschreven in de volgende subclauses.

Net als andere leden worden operators die in een basisklasse zijn gedeclareerd, overgenomen door afgeleide klassen. Omdat operatordeclaraties altijd de klasse of struct vereisen waarin de operator wordt gedeclareerd om deel te nemen aan de handtekening van de operator, is het niet mogelijk voor een operator die is gedeclareerd in een afgeleide klasse om een operator te verbergen die is gedeclareerd in een basisklasse. new De wijzigingsfunctie is dus nooit vereist en daarom nooit toegestaan in een operatordeclaratie.

Meer informatie over unaire en binaire operatoren vindt u in §12.4.

Meer informatie over conversieoperators vindt u in §10,5.

15.10.2 Unaire operators

De volgende regels zijn van toepassing op declaraties van unaire operatoren, waarbij T het exemplaartype van de klasse of struct wordt aangegeven die de operatordeclaratie bevat:

  • Een unaire +, ( -!alleen logische negatie) of ~ operator neemt één parameter van het type T of T? kan elk type retourneren.
  • Een unaire ++ of -- operator neemt één parameter van het type T of T? retourneert hetzelfde type of een type dat ermee is afgeleid.
  • Een unaire true of false exploitant neemt één parameter van het type T of T? en retourneert het type bool.

De handtekening van een unaire operator bestaat uit het operatortoken (+, , -!, ~++, , --of truefalse) en het type van de enkele parameter. Het retourtype maakt geen deel uit van de handtekening van een unaire operator en is evenmin de naam van de parameter.

Voor de true en false unaire operators is een paarsgewijze verklaring vereist. Er treedt een compilatiefout op als een klasse een van deze operators declareert zonder ook de andere te declareren. De true en false operators worden verder beschreven in §12.24.

Voorbeeld: In het volgende voorbeeld ziet u een implementatie en het daaropvolgende gebruik van operator++ voor een gehele vectorklasse:

public class IntVector
{
    public IntVector(int length) {...}
    public int Length { get { ... } }                      // Read-only property
    public int this[int index] { get { ... } set { ... } } // Read-write indexer

    public static IntVector operator++(IntVector iv)
    {
        IntVector temp = new IntVector(iv.Length);
        for (int i = 0; i < iv.Length; i++)
        {
            temp[i] = iv[i] + 1;
        }
        return temp;
    }
}

class Test
{
    static void Main()
    {
        IntVector iv1 = new IntVector(4); // Vector of 4 x 0
        IntVector iv2;
        iv2 = iv1++;              // iv2 contains 4 x 0, iv1 contains 4 x 1
        iv2 = ++iv1;              // iv2 contains 4 x 2, iv1 contains 4 x 2
    }
}

Let op hoe de operatormethode de waarde retourneert die wordt geproduceerd door 1 toe te voegen aan de operand, net zoals de operatoren voor incrementele en degradatie van het voorvoegsel (§12.8.16) en de operatoren voor voorvoegselverhoging en -degradatie (§12.9.6). In tegenstelling tot in C++, moet deze methode de waarde van de operand niet rechtstreeks wijzigen, omdat dit de standaardsemantiek van de operator voor het verhogen van postfixen zou schenden (§12.8.16).

eindvoorbeeld

15.10.3 Binaire operators

De volgende regels zijn van toepassing op declaraties van binaire operatoren, waarbij T het exemplaartype van de klasse of struct wordt aangegeven die de operatordeclaratie bevat:

  • Een binaire niet-shiftoperator heeft twee parameters, ten minste één van de parameters die een type T hebben of T?, en kan elk type retourneren.
  • Een binaire <<- of >> operator (§12.11) heeft twee parameters, waarvan het eerste type T of T? heeft en waarvan de tweede type int of int?heeft en elk type kan retourneren.

De handtekening van een binaire operator bestaat uit het operatortoken (+, , -*/%&|, ^<<>>==!=>, <, of >=<=) en de typen van de twee parameters. Het retourtype en de namen van de parameters maken geen deel uit van de handtekening van een binaire operator.

Voor bepaalde binaire operators is paarsgewijze declaratie vereist. Voor elke aangifte van een van beide operatoren van een paar is er een overeenkomende verklaring van de andere exploitant van het paar. Twee operatordeclaraties komen overeen als er identiteitsconversies bestaan tussen de retourtypen en de bijbehorende parametertypen. Voor de volgende operators is paarse declaratie vereist:

  • operator en operator ==!=
  • operator en operator ><
  • operator en operator >=<=

15.10.4 Conversieoperators

Een declaratie van een conversieoperator introduceert een door de gebruiker gedefinieerde conversie (§10.5), waarmee de vooraf gedefinieerde impliciete en expliciete conversies worden vergroot.

Een declaratie van een conversieoperator met het trefwoord introduceert een door de implicit gebruiker gedefinieerde impliciete conversie. Impliciete conversies kunnen in verschillende situaties optreden, waaronder aanroepen van functieleden, cast-expressies en toewijzingen. Dit wordt verder beschreven in §10.2.

Een declaratie van een conversieoperator met het trefwoord introduceert een door de explicit gebruiker gedefinieerde expliciete conversie. Expliciete conversies kunnen optreden in cast-expressies en worden verder beschreven in §10.3.

Een conversieoperator converteert van een brontype, aangegeven door het parametertype van de conversieoperator, naar een doeltype, aangegeven door het retourtype van de conversieoperator.

Voor een bepaald brontype en doeltype ST, als S of T nullable waardetypen zijn, kunt u de onderliggende typen laten S₀ en T₀ hiernaar verwijzen, anders S₀ en T₀ zijn ze gelijk aan S en T respectievelijk. Een klasse of struct mag alleen een conversie van een brontype S naar een doeltype T declareren als alle volgende waar zijn:

  • S₀ en T₀ zijn verschillende typen.

  • S₀ Of T₀ is het exemplaartype van de klasse of struct die de operatordeclaratie bevat.

  • S₀ Noch T₀ een interface_type.

  • Met uitzondering van door de gebruiker gedefinieerde conversies bestaat er geen conversie van S naar T of vanT.S

Voor de doeleinden van deze regels worden alle typeparameters die zijn gekoppeld aan S of T beschouwd als unieke typen die geen overnamerelatie met andere typen hebben en eventuele beperkingen voor deze typeparameters worden genegeerd.

Voorbeeld: In het volgende:

class C<T> {...}

class D<T> : C<T>
{
    public static implicit operator C<int>(D<T> value) {...}     // Ok
    public static implicit operator C<string>(D<T> value) {...}  // Ok
    public static implicit operator C<T>(D<T> value) {...}       // Error
}

de eerste twee operatordeclaraties zijn toegestaan omdat T en intstringrespectievelijk worden beschouwd als unieke typen zonder relatie. De derde operator is echter een fout omdat C<T> dit de basisklasse is van D<T>.

eindvoorbeeld

Uit de tweede regel volgt dat een conversieoperator wordt omgezet in of van de klasse of het structtype waarin de operator wordt gedeclareerd.

Voorbeeld: Het is mogelijk dat een klasse- of structtype C een conversie van C en naar intint , Cmaar niet van int naar booldefinieert. eindvoorbeeld

Het is niet mogelijk om een vooraf gedefinieerde conversie rechtstreeks opnieuw te definiëren. Conversieoperators mogen dus niet van of naar object converteren omdat impliciete en expliciete conversies al bestaan tussen object en alle andere typen. Op dezelfde manier kunnen noch de bron- en doeltypen van een conversie een basistype van het andere zijn, omdat er dan al een conversie zou bestaan. Het is echter mogelijk om operators te declareren voor algemene typen die voor bepaalde typeargumenten conversies opgeven die al bestaan als vooraf gedefinieerde conversies.

Voorbeeld:

struct Convertible<T>
{
    public static implicit operator Convertible<T>(T value) {...}
    public static explicit operator T(Convertible<T> value) {...}
}

wanneer het type object wordt opgegeven als een typeargument voor T, declareert de tweede operator een conversie die al bestaat (een impliciete en daarom ook een expliciete conversie bestaat van elk type naar typeobject).

eindvoorbeeld

In gevallen waarin een vooraf gedefinieerde conversie bestaat tussen twee typen, worden door de gebruiker gedefinieerde conversies tussen deze typen genegeerd. Specifiek:

  • Als er een vooraf gedefinieerde impliciete conversie (§10.2) bestaat van het type naar het type ST, worden alle door de gebruiker gedefinieerde conversies (impliciet of expliciet) genegeerd ST .
  • Als er een vooraf gedefinieerde expliciete conversie (§10.3) bestaat van het type naar het type ST, worden alle door de gebruiker gedefinieerde expliciete conversies van S waaruit T deze worden genegeerd. Bovendien:
    • Als een S interfacetype of T een interfacetype is, worden door de gebruiker gedefinieerde impliciete conversies van S waaruit T deze worden genegeerd.
    • Anders worden door de gebruiker gedefinieerde impliciete conversies van S naar T nog steeds overwogen.

Voor alle typen, maar objectde operators die door het Convertible<T> bovenstaande type zijn gedeclareerd, conflicteren niet met vooraf gedefinieerde conversies.

Voorbeeld:

void F(int i, Convertible<int> n)
{
    i = n;                    // Error
    i = (int)n;               // User-defined explicit conversion
    n = i;                    // User-defined implicit conversion
    n = (Convertible<int>)i;  // User-defined implicit conversion
}

Voor het type objectverbergen vooraf gedefinieerde conversies echter de door de gebruiker gedefinieerde conversies in alle gevallen, maar één:

void F(object o, Convertible<object> n)
{
    o = n;                       // Pre-defined boxing conversion
    o = (object)n;               // Pre-defined boxing conversion
    n = o;                       // User-defined implicit conversion
    n = (Convertible<object>)o;  // Pre-defined unboxing conversion
}

eindvoorbeeld

Door de gebruiker gedefinieerde conversies mogen niet worden geconverteerd van of naar interface_types. Deze beperking zorgt er met name voor dat er geen door de gebruiker gedefinieerde transformaties plaatsvinden bij het converteren naar een interface_type en dat een conversie naar een interface_type alleen slaagt als de object conversie daadwerkelijk de opgegeven interface_type implementeert.

De handtekening van een conversieoperator bestaat uit het brontype en het doeltype. (Dit is de enige vorm van lid waarvoor het retourtype deelneemt aan de handtekening.) De impliciete of expliciete classificatie van een conversieoperator maakt geen deel uit van de handtekening van de operator. Een klasse of struct kan dus niet zowel een impliciete als een expliciete conversieoperator met dezelfde bron- en doeltypen declareren.

Opmerking: In het algemeen moeten door de gebruiker gedefinieerde impliciete conversies worden ontworpen om nooit uitzonderingen te genereren en nooit informatie te verliezen. Als een door de gebruiker gedefinieerde conversie uitzonderingen kan veroorzaken (bijvoorbeeld omdat het bronargument buiten het bereik valt) of gegevensverlies (zoals het verwijderen van bits met hoge volgorde), moet die conversie worden gedefinieerd als een expliciete conversie. eindnotitie

Voorbeeld: In de volgende code

public struct Digit
{
    byte value;

    public Digit(byte value)
    {
        if (value < 0 || value > 9)
        {
            throw new ArgumentException();
        }
        this.value = value;
    }

    public static implicit operator byte(Digit d) => d.value;
    public static explicit operator Digit(byte b) => new Digit(b);
}

de conversie van Digit naar byte impliciet is omdat deze nooit uitzonderingen genereert of informatie verliest, maar de conversie van byte naar Digit expliciet is omdat Digit alleen een subset van de mogelijke waarden van een byte.

eindvoorbeeld

15.11 Exemplaarconstructors

15.11.1 Algemeen

Een exemplaarconstructor is een lid dat de acties implementeert die nodig zijn om een exemplaar van een klasse te initialiseren. Exemplaarconstructors worden gedeclareerd met behulp van constructor_declaration s:

constructor_declaration
    : attributes? constructor_modifier* constructor_declarator constructor_body
    ;

constructor_modifier
    : 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'extern'
    | unsafe_modifier   // unsafe code support
    ;

constructor_declarator
    : identifier '(' parameter_list? ')' constructor_initializer?
    ;

constructor_initializer
    : ':' 'base' '(' argument_list? ')'
    | ':' 'this' '(' argument_list? ')'
    ;

constructor_body
    : block
    | '=>' expression ';'
    | ';'
    ;

unsafe_modifier (§23.2) is alleen beschikbaar in onveilige code (§23).

Een constructor_declaration kan een set kenmerken (§22), een van de toegestane soorten toegankelijkheid (§15.3.6) en een extern modifier (§15.6.8) bevatten. Een constructordeclaratie is niet toegestaan om dezelfde wijziging meerdere keren op te nemen.

De id van een constructor_declarator noemt de klasse waarin de instantieconstructor wordt gedeclareerd. Als er een andere naam is opgegeven, treedt er een compilatietijdfout op.

De optionele parameter_list van een instantieconstructor is onderworpen aan dezelfde regels als de parameter_list van een methode (§15.6). Aangezien de this wijzigingsfunctie voor parameters alleen van toepassing is op extensiemethoden (§15.6.10), bevat geen parameter in de parameter_list van een constructor de this modifier. De lijst met parameters definieert de handtekening (§7.6) van een exemplaarconstructor en bepaalt het proces waarbij overbelastingsresolutie (§12.6.4) een bepaalde instantieconstructor in een aanroep selecteert.

Elk van de typen waarnaar in de parameter_list van een exemplaarconstructor wordt verwezen, moet ten minste zo toegankelijk zijn als de constructor zelf (§7.5.5).

De optionele constructor_initializer geeft een andere instantieconstructor op die moet worden aangeroepen voordat de instructies worden uitgevoerd die zijn opgegeven in de constructor_body van deze exemplaarconstructor. Dit wordt verder beschreven in §15.11.2.

Wanneer een constructordeclaratie een extern wijzigingsfunctie bevat, wordt de constructor als een externe constructor beschouwd. Omdat een externe constructordeclaratie geen werkelijke implementatie biedt, bestaat de constructor_body uit een puntkomma. Voor alle andere constructors bestaat de constructor_body uit een van beide

  • een blok, waarmee de instructies worden opgegeven voor het initialiseren van een nieuw exemplaar van de klasse; of
  • een expressietekst, die bestaat uit => gevolgd door een expressie en een puntkomma, en geeft één expressie aan om een nieuw exemplaar van de klasse te initialiseren.

Een constructor_body die een blok - of expressietekst is, komt exact overeen met het blok van een exemplaarmethode met een void retourtype (§15.6.11).

Exemplaarconstructors worden niet overgenomen. Een klasse heeft dus geen andere instantieconstructors dan die daadwerkelijk in de klasse zijn gedeclareerd, met uitzondering van dat als een klasse geen exemplaarconstructordeclaraties bevat, automatisch een standaardexemplarenconstructor wordt opgegeven (§15.11.5).

Exemplaarconstructors worden aangeroepen door object_creation_expression s (§12.8.17.2) en via constructor_initializers.

15.11.2 Initializers van constructor

Alle exemplaarconstructors (met uitzondering van die voor klasse object) bevatten impliciet een aanroep van een andere instantieconstructor direct vóór de constructor_body. De constructor die impliciet moet worden aangeroepen, wordt bepaald door de constructor_initializer:

  • Een instantieconstructor-initialisatiefunctie van het formulier base(argument_list) (waarbij argument_list optioneel is) zorgt ervoor dat een instantieconstructor van de directe basisklasse wordt aangeroepen. Deze constructor wordt geselecteerd met behulp van argument_list en de regels voor overbelastingsoplossing van §12.6.4. De set kandidaat-exemplaarconstructors bestaat uit alle toegankelijke exemplaarconstructors van de directe basisklasse. Als deze set leeg is of als er geen enkele constructor van het beste exemplaar kan worden geïdentificeerd, treedt er een compileertijdfout op.
  • Een instantieconstructor-initialisatiefunctie van het formulier this(argument_list) (waarbij argument_list optioneel is) roept een andere instantieconstructor aan uit dezelfde klasse. De constructor wordt geselecteerd met behulp van argument_list en de regels voor overbelastingsoplossing van §12.6.4. De set kandidaat-exemplaarconstructors bestaat uit alle exemplaarconstructors die in de klasse zelf zijn gedeclareerd. Als de resulterende set van toepasselijke instantieconstructors leeg is of als één beste exemplaarconstructor niet kan worden geïdentificeerd, treedt er een compilatietijdfout op. Als een instantieconstructordeclaratie zichzelf aanroept via een keten van een of meer constructor-initialisatieprogramma's, treedt er een compilatietijdfout op.

Als een instantieconstructor geen initialisatiefunctie voor de constructor heeft, wordt impliciet een constructor-initialisatiefunctie van het formulier base() opgegeven.

Opmerking: Een instantieconstructordeclaratie van het formulier

C(...) {...}

is precies gelijk aan

C(...) : base() {...}

eindnotitie

Het bereik van de parameters die zijn opgegeven door de parameter_list van een exemplaarconstructordeclaratie bevat de initialisatiefunctie van de constructor van die declaratie. Daarom is een constructor-initialisatiefunctie toegestaan om toegang te krijgen tot de parameters van de constructor.

Voorbeeld:

class A
{
    public A(int x, int y) {}
}

class B: A
{
    public B(int x, int y) : base(x + y, x - y) {}
}

eindvoorbeeld

Een initialisatiefunctie voor exemplaarconstructor heeft geen toegang tot het exemplaar dat wordt gemaakt. Daarom is het een compilatiefout om ernaar te verwijzen in een argumentexpressie van de initialisatiefunctie van de constructor, omdat het een compilatiefout is voor een argumentexpressie om te verwijzen naar een exemplaarlid via een simple_name.

15.11.3 Instantievariabele initialisaties

Wanneer een instantieconstructor geen initialisatiefunctie voor de constructor heeft of een initialisatiefunctie voor de constructor van het formulier base(...)heeft, voert die constructor impliciet de initialisaties uit die zijn opgegeven door de variable_initializers van de instantievelden die in de klasse zijn gedeclareerd. Dit komt overeen met een reeks toewijzingen die direct worden uitgevoerd na invoer aan de constructor en vóór de impliciete aanroep van de directe basisklasseconstructor. De variabele initializers worden uitgevoerd in de tekstvolgorde waarin ze worden weergegeven in de klassedeclaratie (§15.5.6).

15.11.4 Constructoruitvoering

Variabele initializers worden omgezet in toewijzingsinstructies en deze toewijzingsinstructies worden uitgevoerd vóór de aanroep van de constructor voor het basisklasseexemplaren. Deze volgorde zorgt ervoor dat alle exemplaarvelden worden geïnitialiseerd door hun variabele initialisaties voordat eventuele instructies die toegang tot dat exemplaar hebben, worden uitgevoerd.

Voorbeeld: Op basis van het volgende:

class A
{
    public A()
    {
        PrintFields();
    }

    public virtual void PrintFields() {}
}
class B: A
{
    int x = 1;
    int y;

    public B()
    {
        y = -1;
    }

    public override void PrintFields() =>
        Console.WriteLine($"x = {x}, y = {y}");
}

wanneer er nieuwe B() worden gebruikt om een exemplaar van Bte maken, wordt de volgende uitvoer geproduceerd:

x = 1, y = 0

De waarde van is 1 omdat de initialisatiefunctie van x de variabele wordt uitgevoerd voordat de constructor van het basisklasseexemplaren wordt aangeroepen. De waarde is y echter 0 (de standaardwaarde van een int) omdat de toewijzing y waaraan moet worden uitgevoerd pas wordt uitgevoerd nadat de constructor van de basisklasse is geretourneerd. Het is handig om instantievariabelen en constructor-initialisatieprogramma's te beschouwen als instructies die automatisch worden ingevoegd vóór de constructor_body. Het voorbeeld

class A
{
    int x = 1, y = -1, count;

    public A()
    {
        count = 0;
    }

    public A(int n)
    {
        count = n;
    }
}

class B : A
{
    double sqrt2 = Math.Sqrt(2.0);
    ArrayList items = new ArrayList(100);
    int max;

    public B(): this(100)
    {
        items.Add("default");
    }

    public B(int n) : base(n - 1)
    {
        max = n;
    }
}

bevat verschillende variabele initializers; het bevat ook constructor-initializers van beide vormen (base en this). Het voorbeeld komt overeen met de onderstaande code, waarbij elke opmerking een automatisch ingevoegde instructie aangeeft (de syntaxis die wordt gebruikt voor de automatisch ingevoegde constructoraanroepen, is niet geldig, maar dient alleen ter illustratie van het mechanisme).

class A
{
    int x, y, count;
    public A()
    {
        x = 1;      // Variable initializer
        y = -1;     // Variable initializer
        object();   // Invoke object() constructor
        count = 0;
    }

    public A(int n)
    {
        x = 1;      // Variable initializer
        y = -1;     // Variable initializer
        object();   // Invoke object() constructor
        count = n;
    }
}

class B : A
{
    double sqrt2;
    ArrayList items;
    int max;
    public B() : this(100)
    {
        B(100);                      // Invoke B(int) constructor
        items.Add("default");
    }

    public B(int n) : base(n - 1)
    {
        sqrt2 = Math.Sqrt(2.0);      // Variable initializer
        items = new ArrayList(100);  // Variable initializer
        A(n - 1);                    // Invoke A(int) constructor
        max = n;
    }
}

eindvoorbeeld

15.11.5 Standaardconstructors

Als een klasse geen instantieconstructordeclaraties bevat, wordt automatisch een standaardexemplarenconstructor opgegeven. Deze standaardconstructor roept gewoon een constructor van de directe basisklasse aan, alsof deze een initialisatiefunctie voor de constructor van het formulier base()had. Als de klasse abstract is, wordt de gedeclareerde toegankelijkheid voor de standaardconstructor beveiligd. Anders is de gedeclareerde toegankelijkheid voor de standaardconstructor openbaar.

Opmerking: De standaardconstructor is dus altijd van het formulier

protected C(): base() {}

or

public C(): base() {}

waar C is de naam van de klasse.

eindnotitie

Als overbelastingsresolutie geen unieke beste kandidaat voor de initialisatiefunctie van de basisklasse-constructor kan bepalen, treedt er een compilatietijdfout op.

Voorbeeld: In de volgende code

class Message
{
    object sender;
    string text;
}

er wordt een standaardconstructor opgegeven omdat de klasse geen exemplaarconstructordeclaraties bevat. Het voorbeeld is dus precies gelijk aan

class Message
{
    object sender;
    string text;

    public Message() : base() {}
}

eindvoorbeeld

15.12 Statische constructors

Een statische constructor is een lid dat de acties implementeert die nodig zijn om een gesloten klasse te initialiseren. Statische constructors worden gedeclareerd met behulp van static_constructor_declaration s:

static_constructor_declaration
    : attributes? static_constructor_modifiers identifier '(' ')'
        static_constructor_body
    ;

static_constructor_modifiers
    : 'static'
    | 'static' 'extern' unsafe_modifier?
    | 'static' unsafe_modifier 'extern'?
    | 'extern' 'static' unsafe_modifier?
    | 'extern' unsafe_modifier 'static'
    | unsafe_modifier 'static' 'extern'?
    | unsafe_modifier 'extern' 'static'
    ;

static_constructor_body
    : block
    | '=>' expression ';'
    | ';'
    ;

unsafe_modifier (§23.2) is alleen beschikbaar in onveilige code (§23).

Een static_constructor_declaration kan een set kenmerken (§22) en een extern wijzigingsfunctie (§15.6.8) bevatten.

De id van een static_constructor_declaration noemt de klasse waarin de statische constructor wordt gedeclareerd. Als er een andere naam is opgegeven, treedt er een compilatietijdfout op.

Wanneer een statische constructordeclaratie een extern wijzigingsfunctie bevat, wordt de statische constructor als een externe statische constructor beschouwd. Omdat een externe statische constructordeclaratie geen werkelijke implementatie biedt, bestaat de static_constructor_body uit een puntkomma. Voor alle andere statische constructordeclaraties bestaat de static_constructor_body uit een van beide

  • een blok, waarmee de instructies worden opgegeven die moeten worden uitgevoerd om de klasse te initialiseren; of
  • een expressietekst, die bestaat uit => gevolgd door een expressie en een puntkomma, en geeft één expressie aan die moet worden uitgevoerd om de klasse te initialiseren.

Een static_constructor_body die een blok - of expressietekst is, komt exact overeen met de method_body van een statische methode met een void retourtype (§15.6.11).

Statische constructors worden niet overgenomen en kunnen niet rechtstreeks worden aangeroepen.

De statische constructor voor een gesloten klasse wordt maximaal één keer uitgevoerd in een bepaald toepassingsdomein. De uitvoering van een statische constructor wordt geactiveerd door de eerste van de volgende gebeurtenissen in een toepassingsdomein:

  • Er wordt een exemplaar van de klasse gemaakt.
  • Naar een van de statische leden van de klasse wordt verwezen.

Als een klasse de Main methode (§7.1) bevat waarin de uitvoering begint, wordt de statische constructor voor die klasse uitgevoerd voordat de Main methode wordt aangeroepen.

Als u een nieuw type gesloten klasse wilt initialiseren, wordt eerst een nieuwe set statische velden (§15.5.2) gemaakt voor dat specifieke gesloten type. Elk van de statische velden wordt geïnitialiseerd tot de standaardwaarde (§15.5.5). Vervolgens worden de statische veld initialisaties (§15.5.6.2) uitgevoerd voor deze statische velden. Ten slotte wordt de statische constructor uitgevoerd.

Voorbeeld: Het voorbeeld

class Test
{
    static void Main()
    {
        A.F();
        B.F();
    }
}

class A
{
    static A()
    {
        Console.WriteLine("Init A");
    }

    public static void F()
    {
        Console.WriteLine("A.F");
    }
}

class B
{
    static B()
    {
        Console.WriteLine("Init B");
    }

    public static void F()
    {
        Console.WriteLine("B.F");
    }
}

moet de uitvoer produceren:

Init A
A.F
Init B
B.F

omdat de uitvoering van Ade statische constructor wordt geactiveerd door de aanroep naar A.Fen de uitvoering van Bde statische constructor wordt geactiveerd door de aanroep naar B.F.

eindvoorbeeld

Het is mogelijk om cirkelvormige afhankelijkheden te maken waarmee statische velden met variabele initialisaties in hun standaardwaardestatus kunnen worden waargenomen.

Voorbeeld: Het voorbeeld

class A
{
    public static int X;

    static A()
    {
        X = B.Y + 1;
    }
}

class B
{
    public static int Y = A.X + 1;

    static B() {}

    static void Main()
    {
        Console.WriteLine($"X = {A.X}, Y = {B.Y}");
    }
}

produceert de uitvoer

X = 1, Y = 2

Om de Main methode uit te voeren, voert het systeem eerst de initialisatiefunctie uit voor B.Y, vóór de statische constructor van klasse B. Y's initializer zorgt ervoor dat Ade static constructor wordt uitgevoerd omdat er naar de waarde wordt A.X verwezen. De statische constructor van A op zijn beurt gaat verder met het berekenen van de waarde van X, en hiermee wordt de standaardwaarde opgehaald, Ydie nul is. A.X wordt dus geïnitialiseerd tot 1. Het proces van het uitvoeren Avan statische veld initializers en statische constructor wordt vervolgens voltooid, terugkerend naar de berekening van de initiële waarde van Y, het resultaat hiervan wordt 2.

eindvoorbeeld

Omdat de statische constructor precies eenmaal wordt uitgevoerd voor elk gesloten, samengesteld klassetype, is het een handige plek om runtimecontroles af te dwingen op de typeparameter die niet tijdens het compileren kan worden gecontroleerd via beperkingen (§15.2.5).

Voorbeeld: Het volgende type maakt gebruik van een statische constructor om af te dwingen dat het typeargument een opsomming is:

class Gen<T> where T : struct
{
    static Gen()
    {
        if (!typeof(T).IsEnum)
        {
            throw new ArgumentException("T must be an enum");
        }
    }
}

eindvoorbeeld

15.13 Finalizers

Opmerking: In een eerdere versie van deze specificatie, wat nu een 'finalizer' wordt genoemd, werd een 'destructor' genoemd. Ervaring heeft aangetoond dat de term 'destructor' verwarring veroorzaakte en vaak tot onjuiste verwachtingen leidde, met name voor programmeurs die C++kennen. In C++wordt een destructor op een determinate manier aangeroepen, terwijl in C# een finalizer niet is. Als u het gedrag van C# wilt bepalen, moet u dit gebruiken Dispose. eindnotitie

Een finalizer is een lid dat de acties implementeert die nodig zijn om een exemplaar van een klasse te voltooien. Een finalizer wordt gedeclareerd met behulp van een finalizer_declaration:

finalizer_declaration
    : attributes? '~' identifier '(' ')' finalizer_body
    | attributes? 'extern' unsafe_modifier? '~' identifier '(' ')'
      finalizer_body
    | attributes? unsafe_modifier 'extern'? '~' identifier '(' ')'
      finalizer_body
    ;

finalizer_body
    : block
    | '=>' expression ';'
    | ';'
    ;

unsafe_modifier (§23.2) is alleen beschikbaar in onveilige code (§23).

Een finalizer_declaration kan een set kenmerken bevatten (§22).

De id van een finalizer_declarator noemt de klasse waarin de finalizer wordt gedeclareerd. Als er een andere naam is opgegeven, treedt er een compilatietijdfout op.

Wanneer een finalizerdeclaratie een extern modifier bevat, wordt gezegd dat de finalizer een externe finalizer is. Omdat een externe finalizerdeclaratie geen daadwerkelijke implementatie biedt, bestaat de finalizer_body uit een puntkomma. Voor alle andere finalizers bestaat de finalizer_body uit een van beide

  • een blok, waarmee de instructies worden opgegeven die moeten worden uitgevoerd om een exemplaar van de klasse te voltooien.
  • of een expressietekst, die bestaat uit => gevolgd door een expressie en een puntkomma, en geeft één expressie aan die moet worden uitgevoerd om een exemplaar van de klasse te voltooien.

Een finalizer_body die een blok - of expressietekst is, komt exact overeen met de method_body van een instantiemethode met een void retourtype (§15.6.11).

Finalizers worden niet overgenomen. Een klasse heeft dus geen andere finalizers dan de klasse die in die klasse kan worden gedeclareerd.

Opmerking: Omdat een finalizer vereist is om geen parameters te hebben, kan deze niet worden overbelast, zodat een klasse maximaal één finalizer kan hebben. eindnotitie

Finalizers worden automatisch aangeroepen en kunnen niet expliciet worden aangeroepen. Een exemplaar komt in aanmerking voor voltooien wanneer het niet meer mogelijk is dat code die instantie gebruikt. Uitvoering van de finalizer voor het exemplaar kan zich op elk gewenst moment voordoen nadat het exemplaar in aanmerking komt voor voltooien (§7.9). Wanneer een instantie is voltooid, worden de finalizers in de overnameketen van dat exemplaar in volgorde aangeroepen van de meest afgeleide naar minst afgeleide. Er kan een finalizer worden uitgevoerd op een thread. Zie §7.9 voor meer informatie over de regels die bepalen wanneer en hoe een finalizer wordt uitgevoerd.

Voorbeeld: De uitvoer van het voorbeeld

class A
{
    ~A()
    {
        Console.WriteLine("A's finalizer");
    }
}

class B : A
{
    ~B()
    {
        Console.WriteLine("B's finalizer");
    }
}

class Test
{
    static void Main()
    {
        B b = new B();
        b = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
}

is

B's finalizer
A's finalizer

aangezien finalizers in een overnameketen in volgorde worden aangeroepen, van de meeste afgeleide naar minst afgeleide.

eindvoorbeeld

Finalizers worden geïmplementeerd door de virtuele methode Finalize op System.Objectte overschrijven. C#-programma's mogen deze methode niet overschrijven of rechtstreeks aanroepen (of onderdrukkingen ervan).

Voorbeeld: bijvoorbeeld het programma

class A
{
    override protected void Finalize() {}  // Error
    public void F()
    {
        this.Finalize();                   // Error
    }
}

bevat twee fouten.

eindvoorbeeld

Een compiler gedraagt zich alsof deze methode en overriden ervan helemaal niet bestaan.

Voorbeeld: Dit programma:

class A
{
    void Finalize() {}  // Permitted
}

is geldig en de weergegeven methode verbergt System.Objectde Finalize methode.

eindvoorbeeld

Zie §21.4 voor een bespreking van het gedrag wanneer een uitzondering wordt gegenereerd vanuit een finalizer.

15.14 Iterators

15.14.1 Algemeen

Een functielid (§12.6) dat is geïmplementeerd met behulp van een iteratorblok (§13.3) wordt een iterator genoemd.

Een iteratorblok kan worden gebruikt als de hoofdtekst van een functielid, zolang het retourtype van het bijbehorende functielid een van de enumeratorinterfaces (§15.14.2) of een van de enumerable interfaces (§15.14.3) is. Het kan optreden als een method_body, operator_body of accessor_body, terwijl gebeurtenissen, instantieconstructors, statische constructors en finalizer niet als iterators worden geïmplementeerd.

Wanneer een functielid wordt geïmplementeerd met behulp van een iteratorblok, is het een compilatiefout voor de parameterlijst van het functielid om een inof meer parameters outref of een parameter van een ref struct type op te geven.

15.14.2 Enumerator-interfaces

De enumerator-interfaces zijn de niet-algemene interface System.Collections.IEnumerator en alle instantiëringen van de algemene interface System.Collections.Generic.IEnumerator<T>. In deze subclause en de broers en zussen worden deze interfaces in het kort vermeld als IEnumerator respectievelijk IEnumerator<T>.

15.14.3 Enumerable interfaces

De enumerable interfaces zijn de niet-algemene interface System.Collections.IEnumerable en alle instantiëringen van de algemene interface System.Collections.Generic.IEnumerable<T>. In deze subclause en de broers en zussen worden deze interfaces in het kort vermeld als IEnumerable respectievelijk IEnumerable<T>.

15.14.4 Rendementstype

Een iterator produceert een reeks waarden, allemaal van hetzelfde type. Dit type wordt het rendementstype van de iterator genoemd.

  • Het rendementstype van een iterator die retourneert IEnumerator of IEnumerable is object.
  • Het rendementstype van een iterator die retourneert IEnumerator<T> of IEnumerable<T> is T.

15.14.5 Enumerator-objecten

15.14.5.1 Algemeen

Wanneer een functielid dat een enumerator-interfacetype retourneert, wordt geïmplementeerd met behulp van een iteratorblok, wordt de code in het iteratorblok niet onmiddellijk uitgevoerd door het functielid aan te roepen. In plaats daarvan wordt een enumerator-object gemaakt en geretourneerd. Dit object bevat de code die is opgegeven in het iteratorblok en de uitvoering van de code in het iteratorblok vindt plaats wanneer de methode van MoveNext het enumerator-object wordt aangeroepen. Een enumerator-object heeft de volgende kenmerken:

  • Het implementeert enIEnumerator, waar IEnumerator<T> is het rendementstype T van de iterator.
  • Het implementeert System.IDisposable.
  • Deze wordt geïnitialiseerd met een kopie van de argumentwaarden (indien aanwezig) en exemplaarwaarde die wordt doorgegeven aan het functielid.
  • Het heeft vier mogelijke statussen, voordat, wordt uitgevoerd, onderbroken en daarna, en bevindt zich in eerste instantie in de voorstatus.

Een enumerator-object is doorgaans een exemplaar van een door compiler gegenereerde enumerator-klasse die de code in het iteratorblok inkapselt en de enumerator-interfaces implementeert, maar andere implementatiemethoden zijn mogelijk. Als een enumerator-klasse wordt gegenereerd door de compiler, wordt die klasse direct of indirect genest in de klasse die het functielid bevat, heeft deze persoonlijke toegankelijkheid en heeft deze een naam die is gereserveerd voor compilergebruik (§6.4.3).

Een enumerator-object kan meer interfaces implementeren dan hierboven is opgegeven.

De volgende subclauses beschrijven het vereiste gedrag van de MoveNext, Currenten Dispose leden van de IEnumerator en IEnumerator<T> interface-implementaties die worden geleverd door een enumerator-object.

Enumerator-objecten ondersteunen de IEnumerator.Reset methode niet. Als u deze methode aanroept, wordt er een System.NotSupportedException gegooid.

15.14.5.2 De Methode MoveNext

De MoveNext methode van een enumerator-object bevat de code van een iteratorblok. Als u de MoveNext methode aanroept, wordt code uitgevoerd in het iteratorblok en wordt de Current eigenschap van het enumerator-object zo nodig ingesteld. De precieze actie die wordt MoveNext uitgevoerd, is afhankelijk van de status van het enumerator-object wanneer MoveNext deze wordt aangeroepen:

  • Als de status van het enumerator-object zich eerder bevindt, wordt het volgende MoveNextaangeroepen:
    • Hiermee wijzigt u de status in uitvoering.
    • Initialiseert de parameters (inclusief this) van het iteratorblok naar de argumentwaarden en instantiewaarde die zijn opgeslagen toen het enumerator-object werd geïnitialiseerd.
    • Hiermee wordt het iteratorblok vanaf het begin uitgevoerd totdat de uitvoering wordt onderbroken (zoals hieronder wordt beschreven).
  • Als de status van het enumerator-object wordt uitgevoerd, is het resultaat van aanroepen MoveNext niet opgegeven.
  • Als de status van het enumerator-object is onderbroken, wordt MoveNext aangeroepen:
    • Hiermee wijzigt u de status in uitvoering.
    • Hiermee worden de waarden van alle lokale variabelen en parameters (inclusief this) hersteld naar de waarden die zijn opgeslagen tijdens het uitvoeren van het iterator-blok voor het laatst is onderbroken.

      Opmerking: de inhoud van objecten waarnaar wordt verwezen door deze variabelen kan zijn gewijzigd sinds de vorige aanroep naar MoveNext. eindnotitie

    • Hervat de uitvoering van het iteratorblok direct na de rendementsinstructie die de uitvoering heeft veroorzaakt en doorgaat totdat de uitvoering wordt onderbroken (zoals hieronder beschreven).
  • Als de status van het enumerator-object zich na bevindt, retourneert MoveNext het aanroepen onwaar.

Wanneer MoveNext het iteratorblok wordt uitgevoerd, kan de uitvoering op vier manieren worden onderbroken: Door een yield return instructie, een yield break instructie, door het einde van het iteratorblok tegen te komen, en door een uitzondering die wordt gegenereerd en doorgegeven uit het iteratorblok.

  • Wanneer een yield return instructie wordt aangetroffen (§9.4.4.20):
    • De expressie die in de instructie wordt gegeven, wordt impliciet geconverteerd naar het rendementstype en toegewezen aan de Current eigenschap van het enumerator-object.
    • Uitvoering van de iterator-hoofdtekst is onderbroken. De waarden van alle lokale variabelen en parameters (inclusief this) worden opgeslagen, net als de locatie van deze yield return instructie. Als de yield return instructie zich binnen een of meer try blokken bevindt, worden de bijbehorende blokken op dit moment niet uitgevoerd.
    • De status van het enumerator-object wordt gewijzigd in onderbroken.
    • De MoveNext methode keert true terug naar de aanroeper, waarmee wordt aangegeven dat de iteratie naar de volgende waarde is gevorderd.
  • Wanneer een yield break instructie wordt aangetroffen (§9.4.4.20):
    • Als de yield break instructie zich binnen een of meer try blokken bevindt, worden de bijbehorende finally blokken uitgevoerd.
    • De status van het enumerator-object wordt gewijzigd in na.
    • De MoveNext methode keert false terug naar de aanroeper, waarmee wordt aangegeven dat de iteratie is voltooid.
  • Wanneer het einde van de iteratortekst wordt aangetroffen:
    • De status van het enumerator-object wordt gewijzigd in na.
    • De MoveNext methode keert false terug naar de aanroeper, waarmee wordt aangegeven dat de iteratie is voltooid.
  • Wanneer er een uitzondering wordt gegenereerd en wordt doorgegeven uit het iterator-blok:
    • De juiste finally blokken in de iterator-hoofdtekst worden uitgevoerd door de uitzonderingsdoorgifte.
    • De status van het enumerator-object wordt gewijzigd in na.
    • De uitzonderingsdoorgifte blijft doorgaan naar de aanroeper van de MoveNext methode.

15.14.5.3 De huidige eigenschap

De eigenschap van Current een enumerator-object wordt beïnvloed door yield return instructies in het iteratorblok.

Wanneer een enumerator-object de status Onderbroken heeft, is de waarde van Current de waarde die is ingesteld door de vorige aanroep naar MoveNext. Wanneer een enumerator-object zich in de status vóór, actief of na bevindt, is het resultaat van het openen Current niet opgegeven.

Voor een iterator met een ander rendementstype dan object, komt het resultaat van het openen Current via de implementatie van IEnumerable het enumerator-object overeen met toegang Current via de implementatie van IEnumerator<T> het enumerator-object en cast het resultaat naar object.

15.14.5.4 De verwijderingsmethode

De Dispose methode wordt gebruikt om de iteratie op te schonen door het enumerator-object naar de nastatus te brengen.

  • Als de status van het enumerator-object eerder is, verandert het aanroepen van de status hiernaarDispose.
  • Als de status van het enumerator-object wordt uitgevoerd, is het resultaat van aanroepen Dispose niet opgegeven.
  • Als de status van het enumerator-object is onderbroken, wordt het volgende Disposeaangeroepen:
    • Hiermee wijzigt u de status in uitvoering.
    • Voert ten slotte blokken uit alsof de laatst uitgevoerde yield return instructie een yield break instructie is. Als dit ertoe leidt dat een uitzondering wordt gegenereerd en doorgegeven uit de hoofdtekst van de iterator, wordt de status van het enumerator-object ingesteld op erna en wordt de uitzondering doorgegeven aan de aanroeper van de Dispose methode.
    • Hiermee wijzigt u de status in na.
  • Als de status van het enumerator-object erna is, heeft het aanroepen Dispose geen invloed.

15.14.6 Enumerable objecten

15.14.6.1 Algemeen

Wanneer een functielid dat een enumerable interfacetype retourneert, wordt geïmplementeerd met behulp van een iteratorblok, wordt de code in het iteratorblok niet onmiddellijk uitgevoerd door het functielid aan te roepen. In plaats daarvan wordt een opsommingsobject gemaakt en geretourneerd. De methode van GetEnumerator het enumerable object retourneert een enumerator-object dat de code inkapselt die is opgegeven in het iteratorblok en de uitvoering van de code in het iteratorblok vindt plaats wanneer de methode van MoveNext het enumerator-object wordt aangeroepen. Een enumerable object heeft de volgende kenmerken:

  • Het implementeert enIEnumerable, waar IEnumerable<T> is het rendementstype T van de iterator.
  • Deze wordt geïnitialiseerd met een kopie van de argumentwaarden (indien aanwezig) en exemplaarwaarde die wordt doorgegeven aan het functielid.

Een enumerable object is doorgaans een exemplaar van een door compiler gegenereerde enumerable klasse die de code in het iteratorblok inkapselt en de enumerable interfaces implementeert, maar andere implementatiemethoden zijn mogelijk. Als een enumerable-klasse wordt gegenereerd door de compiler, wordt die klasse direct of indirect genest in de klasse die het functielid bevat, heeft deze persoonlijke toegankelijkheid en heeft deze een naam die is gereserveerd voor compilergebruik (§6.4.3).

Een opsommingsobject kan meer interfaces implementeren dan hierboven is opgegeven.

Opmerking: Een opsommingsobject kan bijvoorbeeld ook worden geïmplementeerd IEnumerator en IEnumerator<T>, zodat het kan fungeren als een opsommings- en opsommingsprogramma. Normaal gesproken retourneert een dergelijke implementatie een eigen exemplaar (om toewijzingen op te slaan) van de eerste aanroep naar GetEnumerator. Volgende aanroepen van GetEnumerator, indien van toepassing, retourneren een nieuw klasse-exemplaar, meestal van dezelfde klasse, zodat aanroepen naar verschillende enumerator-exemplaren elkaar niet beïnvloeden. Het kan niet hetzelfde exemplaar retourneren, zelfs als de vorige enumerator al voorbij het einde van de reeks is geïnventariseerd, omdat alle toekomstige aanroepen naar een uitgepute enumerator uitzonderingen moeten genereren. eindnotitie

15.14.6.2 De Methode GetEnumerator

Een opsommingsobject biedt een implementatie van de GetEnumerator methoden van de IEnumerable en IEnumerable<T> interfaces. De twee GetEnumerator methoden delen een gemeenschappelijke implementatie die een beschikbaar enumerator-object verkrijgt en retourneert. Het enumerator-object wordt geïnitialiseerd met de argumentwaarden en de instantiewaarde die is opgeslagen toen het enumerable object werd geïnitialiseerd, maar anders functioneert het enumerator-object zoals beschreven in §15.14.5.

15.15 Asynchrone functies

15.15.1 Algemeen

Een methode (§15.6) of anonieme functie (§12.19) met de async modifier wordt een asynchrone functie genoemd. Over het algemeen wordt de term asynchroon gebruikt om elk type functie met de async modifier te beschrijven.

Het is een compilatiefout voor de parameterlijst van een asynchrone functie om een inof meer out parameters refof parameters van een ref struct type op te geven.

Het return_type van een asynchrone methode moet een of een zijnvoid. Voor een asynchrone methode die een resultaatwaarde produceert, is een taaktype algemeen. Voor een asynchrone methode die geen resultaatwaarde produceert, is een taaktype niet algemeen. Dergelijke typen worden in deze specificatie aangeduid als «TaskType»<T> respectievelijk «TaskType». Het standaardbibliotheektype System.Threading.Tasks.Task en de typen waaruit is samengesteld System.Threading.Tasks.Task<TResult> , zijn taaktypen, evenals een klasse- of struct- of interfacetype dat is gekoppeld aan een type taakbouwer via het kenmerk System.Runtime.CompilerServices.AsyncMethodBuilderAttribute. Dergelijke typen worden in deze specificatie genoemd als «TaskBuilderType»<T> en «TaskBuilderType». Een taaktype kan maximaal één typeparameter hebben en kan niet worden genest in een algemeen type.

Een asynchrone methode die een taaktype retourneert, wordt als taak geretourneerd.

Taaktypen kunnen verschillen in de exacte definitie, maar vanuit het oogpunt van de taal is een taaktype in een van de statussen onvolledig, geslaagd of mislukt. Een foutieve taak registreert een relevante uitzondering. Met een geslaagde«TaskType»<T> record wordt een resultaat van het type Tvastgelegd. Taaktypen kunnen worden verwacht en taken kunnen daarom de operanden van wachtende expressies zijn (§12.9.8).

Voorbeeld: Het taaktype MyTask<T> is gekoppeld aan het type taakbouwer MyTaskMethodBuilder<T> en het type wachter Awaiter<T>:

using System.Runtime.CompilerServices; 
[AsyncMethodBuilder(typeof(MyTaskMethodBuilder<>))]
class MyTask<T>
{
    public Awaiter<T> GetAwaiter() { ... }
}

class Awaiter<T> : INotifyCompletion
{
    public void OnCompleted(Action completion) { ... }
    public bool IsCompleted { get; }
    public T GetResult() { ... }
}

eindvoorbeeld

Een type taakbouwer is een klasse- of structtype dat overeenkomt met een specifiek taaktype (§15.15.2). Het type opbouwfunctie voor taken komt exact overeen met de opgegeven toegankelijkheid van het bijbehorende taaktype.

Opmerking: Als het taaktype is gedeclareerd, moet het bijbehorende opbouwfunctietype ook worden gedeclareerd internalinternal en gedefinieerd in dezelfde assembly. Als het taaktype in een ander type is genest, moet het type taakbuider ook in datzelfde type zijn genest. eindnotitie

Een asynchrone functie heeft de mogelijkheid om de evaluatie te onderbreken met behulp van wachtexpressies (§12.9.8) in de hoofdtekst. De evaluatie kan later worden hervat op het moment van de onderbrekingsuitdrukkingsuitdrukking door middel van een hervattingsdelegatie. De hervattingsdelegatie is van het type System.Actionen wanneer deze wordt aangeroepen, wordt de evaluatie van de aanroep van de asynchrone functie hervat vanuit de wachtexpressie waar deze is gebleven. De huidige aanroeper van een asynchrone functieaanroep is de oorspronkelijke aanroeper als de functieaanroep nog nooit is onderbroken of als de meest recente aanroeper van de hervattingsdelegatie anders is.

15.15.2 Opbouwpatroon voor taaktypen

Een type opbouwfunctie voor taken kan maximaal één typeparameter hebben en kan niet worden genest in een algemeen type. Een type opbouwfunctie voor taken heeft de volgende leden (voor niet-algemene typen SetResult taakbouwer, heeft geen parameters) met gedeclareerde public toegankelijkheid:

class «TaskBuilderType»<T>
{
    public static «TaskBuilderType»<T> Create();
    public void Start<TStateMachine>(ref TStateMachine stateMachine)
                where TStateMachine : IAsyncStateMachine;
    public void SetStateMachine(IAsyncStateMachine stateMachine);
    public void SetException(Exception exception);
    public void SetResult(T result);
    public void AwaitOnCompleted<TAwaiter, TStateMachine>(
        ref TAwaiter awaiter, ref TStateMachine stateMachine)
        where TAwaiter : INotifyCompletion
        where TStateMachine : IAsyncStateMachine;
    public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
        ref TAwaiter awaiter, ref TStateMachine stateMachine)
        where TAwaiter : ICriticalNotifyCompletion
        where TStateMachine : IAsyncStateMachine;
    public «TaskType»<T> Task { get; }
}

Een compiler genereert code die gebruikmaakt van het «TaskBuilderType» om de semantiek van het onderbreken en hervatten van de evaluatie van de asynchrone functie te implementeren. Een compiler gebruikt als volgt het «TaskBuilderType»:

  • «TaskBuilderType».Create() wordt aangeroepen om een exemplaar van het «TaskBuilderType», genaamd builder in deze lijst, te maken.
  • builder.Start(ref stateMachine)wordt aangeroepen om de opbouwfunctie te koppelen aan een door compiler gegenereerd statusmachine-exemplaar. stateMachine
    • De opbouwfunctie roept stateMachine.MoveNext() in Start() of nadat Start() deze is teruggekomen om de statusmachine door te gaan.
  • Nadat Start() de taak is geretourneerd, wordt de async methode aangeroepen builder.Task om terug te keren vanuit de asynchrone methode.
  • Bij elke aanroep wordt stateMachine.MoveNext() de statusmachine verder uitgevoerd.
  • Als de statusmachine is voltooid, wordt aangeroepen builder.SetResult() , met de retourwaarde van de methode, indien van toepassing.
  • Als er anders een uitzondering e optreedt in de statusmachine, builder.SetException(e) wordt aangeroepen.
  • Als de statusmachine een await expr expressie bereikt, expr.GetAwaiter() wordt aangeroepen.
  • Als de wachter implementeert ICriticalNotifyCompletion en IsCompleted onwaar is, wordt de statusmachine aangeroepen builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine).
    • AwaitUnsafeOnCompleted() moet worden aangeroepen awaiter.UnsafeOnCompleted(action) met een Action oproep die wordt aangeroepen stateMachine.MoveNext() wanneer de wachter is voltooid.
  • Anders wordt de statusmachine aangeroepen builder.AwaitOnCompleted(ref awaiter, ref stateMachine).
    • AwaitOnCompleted() moet worden aangeroepen awaiter.OnCompleted(action) met een Action oproep die wordt aangeroepen stateMachine.MoveNext() wanneer de wachter is voltooid.
  • SetStateMachine(IAsyncStateMachine) kan worden aangeroepen door de door de compiler gegenereerde IAsyncStateMachine implementatie om het exemplaar te identificeren van de opbouwfunctie die is gekoppeld aan een exemplaar van de statusmachine, met name voor gevallen waarin de statusmachine wordt geïmplementeerd als een waardetype.
    • Als de opbouwfunctie aanroeptstateMachine.SetStateMachine(stateMachine), wordt het stateMachine aangeroepen builder.SetStateMachine(stateMachine) op het builder-exemplaar dat is gekoppeld aanstateMachine.

Opmerking: Voor zowel SetResult(T result) als «TaskType»<T> Task { get; }, de parameter en het argument moet respectievelijk de identiteit converteerbaar zijn naar T. Hierdoor kan een opbouwfunctie voor taaktypen ondersteuning bieden voor typen zoals tuples, waarbij twee typen die niet hetzelfde zijn, een converteerbare identiteit zijn. eindnotitie

15.15.3 Evaluatie van een asynchrone functie die een taak retourneert

Door het aanroepen van een asynchrone functie die een taak retourneert, wordt een exemplaar van het geretourneerde taaktype gegenereerd. Dit wordt de retourtaak van de asynchrone functie genoemd. De taak heeft in eerste instantie een onvolledige status.

De hoofdtekst van de asynchrone functie wordt vervolgens geëvalueerd totdat deze is onderbroken (door een wachtexpressie te bereiken) of wordt beëindigd, waarna het besturingselement naar de aanroeper wordt geretourneerd, samen met de retourtaak.

Wanneer de hoofdtekst van de asynchrone functie wordt beëindigd, wordt de retourtaak uit de onvolledige status verplaatst:

  • Als de hoofdtekst van de functie wordt beëindigd als gevolg van het bereiken van een retourinstructie of het einde van de hoofdtekst, wordt een resultaatwaarde vastgelegd in de retourtaak, die in een geslaagde status wordt geplaatst .
  • Als de hoofdtekst van de functie wordt beëindigd vanwege een ondeugd OperationCanceledException, wordt de uitzondering vastgelegd in de retourtaak die in de geannuleerde status wordt geplaatst .
  • Als de hoofdtekst van de functie wordt beëindigd als gevolg van een andere ondeugdende uitzondering (§13.10.6) wordt de uitzondering vastgelegd in de retourtaak die in een foutieve status wordt geplaatst .

15.15.4 Evaluatie van een ongeldige asynchrone functie

Als het retourtype van de asynchrone functie isvoid, verschilt de evaluatie op de volgende manier van het bovenstaande: Omdat er geen taak wordt geretourneerd, communiceert de functie in plaats daarvan voltooiing en uitzonderingen op de synchronisatiecontext van de huidige thread. De exacte definitie van de synchronisatiecontext is afhankelijk van de implementatie, maar is een weergave van 'waar' de huidige thread wordt uitgevoerd. De synchronisatiecontext wordt op de hoogte gesteld wanneer de evaluatie van een voidasynchrone functie begint, is voltooid of een niet-onderschepde uitzondering veroorzaakt.

Hierdoor kan de context bijhouden hoeveel voidasynchrone functies er worden uitgevoerd en bepalen hoe uitzonderingen worden doorgegeven die eruit komen.