7 Basisconcepten

7.1 Toepassing opstarten en beëindigen

7.1.1 Algemeen

Een programma kan worden gecompileerd als een klassebibliotheek die moet worden gebruikt als onderdeel van andere toepassingen of als een toepassing die rechtstreeks kan worden gestart. Het mechanisme voor het bepalen van deze compilatiemodus is door de implementatie gedefinieerd en extern voor deze specificatie.

Wanneer een toepassing wordt uitgevoerd, wordt er een nieuw toepassingsdomein gemaakt. Verschillende instantiëringen van een toepassing kunnen tegelijkertijd op dezelfde computer aanwezig zijn en elk een eigen toepassingsdomein heeft. Een toepassingsdomein maakt toepassingsisolatie mogelijk door te fungeren als een container voor de toepassingsstatus. Een toepassingsdomein fungeert als een container en grens voor de typen die in de toepassing zijn gedefinieerd en de klassebibliotheken die worden gebruikt. Typen die in één toepassingsdomein worden geladen, verschillen van dezelfde typen die in een ander toepassingsdomein worden geladen en exemplaren van objecten worden niet rechtstreeks gedeeld tussen toepassingsdomeinen. Elk toepassingsdomein heeft bijvoorbeeld een eigen kopie van statische variabelen voor deze typen en een statische constructor voor een type wordt maximaal één keer per toepassingsdomein uitgevoerd. Implementaties hebben de vrijheid om zelfgedefinieerd beleid en mechanismen te bieden voor het creëren en verwijderen van toepassingsdomeinen.

Een toepassingsuitvoering wordt gestart door een toegangspunt aan te roepen dat tijdens het compileren is geselecteerd vanaf kandidaat-toegangspunten die op een van de volgende manieren zijn opgegeven:

  1. Expliciet door een methode met de juiste kenmerken te declareren (§7.1.2).
  2. Impliciet, met behulp van instructies op het hoogste niveau (§7.1.3).
  3. Door de implementatie gedefinieerd, met behulp van een mechanisme buiten deze specificatie om het toegangspunt aan te wijzen (§7.1.4).

Opmerking: met uitzondering van kandidaten op het hoogste niveau (§7.1.3) zijn alle kandidaatinvoerpunten standaardmethoden; als kandidaat of het geselecteerde toegangspunt wordt niet voorkomen dat deze methoden als normaal worden aangeroepen. eindnotitie

Het is een compilatiefout als er geen kandidaat-toegangspunten zijn.

Anders wordt één kandidaat tijdens het compileren geselecteerd als het ingangspunt na het proces in §7.1.5.

Een toepassing wordt uitgevoerd door het geselecteerde toegangspunt aan te roepen, zoals beschreven in §7.1.6.

De beëindigingsstatuscode, die bedoeld is om communicatie van geslaagde of mislukte communicatie met de hostomgeving mogelijk te maken, wordt bepaald op basis van het resultaat van de aanroep van het toegangspunt (§7.1.6).

7.1.2 Benoemde toegangspunten

Een methode komt in aanmerking als een canditate ingangspunt door te voldoen aan de volgende vereisten:

  • Het heeft de naam Main.
  • Het zal zijn static.
  • Het mag niet algemeen zijn.
  • Het wordt gedeclareerd in een niet-algemeen type. Als het type dat de methode declareerde, een genest type is, kan geen van de bijbehorende insluittypen algemeen zijn.
  • Het retourtype moet void, int, System.Threading.Tasks.Task, of System.Threading.Tasks.Task<int>.
  • Het kan de async wijzigingsfunctie alleen hebben als het retourtype van de methode is System.Threading.Tasks.Task of System.Threading.Tasks.Task<int>.
  • Het is geen optionele gedeeltelijke methode (§15.6.9) zonder een uitvoeringsverklaring.
  • De parameterlijst moet leeg zijn of één waardeparameter van het type string[]hebben.

De gedeclareerde toegankelijkheid (§7.4.2) van een methode wordt genegeerd om in aanmerking te komen als kandidaat-ingangspunt. Bij het opstarten van de toepassing wordt het geselecteerde toegangspunt aangeroepen, ongeacht de gedeclareerde toegankelijkheid. Eventuele gedeclareerde toegankelijkheid blijft echter van toepassing als het geselecteerde kandidaat-toegangspunt wordt aangeroepen na het opstarten van de toepassing.

Opmerking: De vereisten hier geven niet op dat Main dit een klasselid moet zijn, het kan een struct-lid zijn. eindnotitie

7.1.3 Met behulp van instructies op het hoogste niveau

Eén compilatie-eenheid (§14.2 in een toepassing kan een statement_list bevatten; de instructies op het hoogste niveau genoemd. De betekenis van de instructies op het hoogste niveau is semantisch gelijk aan het declareren van het volgende in de globale naamruimte:

partial class Program
{
    static «AsyncAndReturnType» «Main»(string[] args)
    {
        «statement_list»
    }
}

De klasse Program is een gedeeltelijke declaratie van het type (§15.2.7) die wordt gecombineerd met eventuele andere gedeeltelijke klassedeclaraties voor Program binnen de toepassing.

Opmerking: Dit betekent dat een instructie op het hoogste niveau dezelfde toegangsrechten heeft als een instructie in de hoofdtekst van een statische methode die is gedefinieerd in de klasse Program in de globale naamruimte. eindnotitie

De naam «Main» van de methode is een tijdelijke aanduiding voor een niet-opgegeven implementatienaam die rechtstreeks toegankelijk is voor de uitvoeringsomgeving en niet rechtstreeks vanuit de toepassing kan worden verwezen. Verder is er geen vereiste dat de gebruikte naam geldig is als een C#-methodenaam.

Opmerking: Een weergave van de gebruikte naam kan echter worden verkregen als een string tijdens het uitvoeren van een instructie op het hoogste niveau als deze een methode aanroept die gebruikmaakt van het CallerMemberName kenmerk (§23.5.6.4). eindnotitie

Dit «statement_list» is een tijdelijke aanduiding voor statement_list van de compilatie-eenheid.

De parameter args valt binnen het bereik binnen de instructies op het hoogste niveau en niet anders. Regels voor conflicterende/schaduwregels zijn van toepassing. Deze parameter ontvangt de toepassingsparameters (§7.1.6).

Asynchrone bewerkingen zijn toegestaan in instructies op het hoogste niveau tot de mate waarin ze zijn toegestaan in instructies binnen een benoemde asynchrone invoerpuntmethode.

De vervanging van de «AsyncAndReturnType» tijdelijke aanduiding in de methodehandtekening wordt als volgt bepaald op basis van de inhoud van de instructies op het hoogste niveau:

Instructies op het hoogste niveau bevatten Gegenereerde handtekening
Nee await of return met waarde static void «Main»(string[] args)
return met alleen waarde static int «Main»(string[] args)
await alleen static async Task «Main»(string[] args)
await en return met waarde static async Task<int> «Main»(string[] args)

Deze gegenereerde handtekeningen voldoen aan de vereisten voor benoemde toegangspunten (§7.1.2) en is daarom «Main» een kandidaat-ingangspunt.

Opmerking: deze handtekeningen bevatten geen gedeclareerde toegankelijkheid (§7.4.2). Een implementatie kan echter gedeclareerde toegankelijkheid omvatten, omdat toegankelijkheid wordt genegeerd voor ingangspuntmethoden (§7.1.2), zou de methode nog steeds semantisch gelijkwaardig zijn.

7.1.4 Extern gedefinieerd toegangspunt

Een implementatie kan een mechanisme bieden dat buiten deze specificatie staat om een kandidaat-ingangspunt op te geven.

Als dit mechanisme een type binnen het programma identificeert, wordt het kandidaatinvoerpunt bepaald zoals opgegeven voor benoemde toegangspunten (§7.1.2).

Als dit mechanisme een methode binnen het programma identificeert, kan de methode een naam hebben, maar anders moet voldoen aan de vereisten die zijn opgegeven voor benoemde toegangspunten (§7.1.2).

Het externe mechanisme mag slechts één kandidaatinvoerpunt opgeven.

Een kandidaatinvoerpunt dat door een extern mechanisme is opgegeven, heeft voorrang bij de selectie van toegangspunten (§7.1.5).

7.1.5 Selectie van toegangspunt

Het is een compilatiefout als er geen kandidaat-toegangspunten zijn.

Anders wordt het ingangspunt uit de kandidaten geselecteerd:

  • Als een van de kandidaten wordt opgegeven door een extern mechanisme (§7.1.4) wordt deze geselecteerd als ingangspunt;
  • Anders wordt een van de kandidaten gedefinieerd door instructies op het hoogste niveau (§7.1.3) die als ingangspunt wordt geselecteerd;
  • Anders:
    • Als een van de kandidaten een retourtype of intvoid een van de kandidaten heeft die een retourtype System.Threading.Tasks.Task hebben of System.Threading.Tasks.Task<int> uit de kandidaatgroep worden verwijderd.
    • Als er één resterende kandidaat is, wordt deze geselecteerd als invoerpunt.
    • Anders kan het ingangspunt niet worden bepaald en wordt er een compilatiefout gerapporteerd.

Het ingangspunt dat tijdens het compileren is geselecteerd, wordt tijdens runtime aangeroepen als onderdeel van het opstarten van de toepassing (§7.1.6).

7.1.6 Ingangspunt aanroepen

Als het invoerpunt een parameter declareert, geeft de implementatie als de initiële waarde van die parameter een niet-null-verwijzing naar een tekenreeksmatrix op. Deze matrix bestaat uit niet-null-verwijzingen naar nul of meer tekenreeksen, genaamd toepassingsparameters, die door de implementatie gedefinieerde waarden door de hostomgeving worden gegeven voordat de toepassing wordt opgestart.

Opmerking: Op systemen die een opdrachtregel ondersteunen, komen toepassingsparameters overeen met wat algemeen bekend staat als opdrachtregelargumenten. eindnotitie

Het opstart- en beëindigingsproces van de toepassing is semantisch gelijk aan de volgende stappen:

  • Een toepassingsuitvoering wordt gestart door:
    • Aanroepen (§12.8.10) de ingangsmethode, indien het retourtype is void of ; of int
    • Wachtend op (§12.9.9) is het resultaat van het aanroepen van de invoerpuntmethode, als het retourtype een Task type is.
    • Als voor het toegangspunt een argument is vereist, wordt de matrix van de toepassingsparameter opgegeven als waarde.

Opmerking: als u de invoerpuntmethode aanroept, wordt de statische constructor, indien van toepassing, eerst uitgevoerd (§15.12, §16.8.10). eindnotitie

  • De toepassing wordt beëindigd
    • Als de uitvoering resulteert in een int waarde die fungeert als de beëindigingsstatuscode;
    • Als de uitvoering anders resulteert in geen retourwaarde, is 0de beëindigingsstatuscode ;
    • Als de uitvoering wordt beëindigd vanwege een uitzondering (§22.4), wordt de afsluitcode door de implementatie gedefinieerd. Daarnaast kan de implementatie alternatieve API's bieden voor het opgeven van de afsluitcode.

Of finalizers (§15.13) al dan niet worden uitgevoerd als onderdeel van de beëindiging van de toepassing, is door de implementatie gedefinieerd.

Note: De implementatie van het .NET Framework doet alle redelijke inspanningen om finalizers aan te roepen (§15.13) voor alle objecten die nog niet zijn verzameld, tenzij dergelijke opschoning is onderdrukt (bijvoorbeeld door een aanroep naar de bibliotheekmethode GC.SuppressFinalize). eindnotitie

7.2 Verklaringen

Declaraties in een C#-programma definiëren de samenstellende elementen van het programma. C#-programma's zijn ingedeeld met behulp van naamruimten. Deze worden geïntroduceerd met behulp van naamruimtedeclaraties (§14), die typedeclaraties en geneste naamruimtedeclaraties kunnen bevatten. Typedeclaraties (§14.8) worden gebruikt voor het definiëren van klassen (§15), structs (§16), interfaces (§19), enums (§20) en gemachtigden (§21). De soorten leden die in een typedeclaratie zijn toegestaan, zijn afhankelijk van de vorm van de typedeclaratie. Bijvoorbeeld, klassedeclaraties kunnen declaraties bevatten voor constanten (§15.4), velden (§15.5), methoden (§15.6), eigenschappen (§15.7), gebeurtenissen (§15.8), indexeerfuncties (§15.9), operators (§15.10), instantieconstructors (§15.11), statische constructors (§15.12), finalizers (§15.13) en geneste typen (§15.3.9).

Een declaratie definieert een naam in de declaratieruimte waartoe de declaratie behoort. Het is een compilatiefout met twee of meer declaraties die leden met dezelfde naam in een declaratieruimte introduceren, behalve in de volgende gevallen:

  • Twee of meer naamruimtedeclaraties met dezelfde naam zijn toegestaan in dezelfde declaratieruimte. Dergelijke naamruimtedeclaraties worden samengevoegd om één logische naamruimte te vormen en één declaratieruimte te delen.
  • Declaraties in afzonderlijke programma's, maar in dezelfde ruimte voor naamruimtedeclaratie mogen dezelfde naam delen.

    Opmerking: deze declaraties kunnen echter dubbelzinnigheden veroorzaken als ze in dezelfde toepassing zijn opgenomen. eindnotitie

  • Twee of meer methoden met dezelfde naam, maar afzonderlijke handtekeningen zijn toegestaan in dezelfde declaratieruimte (§7.5).
  • Twee of meer typedeclaraties met dezelfde naam, maar afzonderlijke getallen van typeparameters zijn toegestaan in dezelfde declaratieruimte (§7.7.2).
  • Twee of meer typedeclaraties met de gedeeltelijke wijziging in dezelfde declaratieruimte kunnen dezelfde naam, hetzelfde aantal typeparameters en dezelfde classificatie (klasse, struct of interface) delen. In dit geval dragen de typedeclaraties bij aan één type en worden ze samengevoegd om één declaratieruimte te vormen (§15.2.7).
  • Een naamruimtedeclaratie en een typedeclaratie in dezelfde declaratieruimte kunnen dezelfde naam delen zolang de typedeclaratie ten minste één typeparameter heeft (§7.7.2).

Er zijn verschillende typen declaratieruimten, zoals beschreven in het volgende.

  • Binnen alle compilatie-eenheden van een programma zijn namespace_member_declarations die niet omsloten zijn door een namespace_declaration onderdeel van een enkele gecombineerde declaratieruimte die de globale declaratieruimte wordt genoemd.
  • Binnen alle compilatie-eenheden van een programma zijn namespace_member_declarationbinnen namespace_declarations met dezelfde volledig gekwalificeerde naamruimtenaam lid van één gecombineerde declaratieruimte. Per §14.3 omvat dit file_scoped_namespace_declarations.
  • Elke compilation_unit en namespace_body hebben een declaratieruimte voor aliassen. Elke extern_alias_directive en using_alias_directive van de compilation_unit of namespace_body draagt een lid bij aan de aliasdeclaratieruimte (§14.6.2).
  • Elke niet-gedeeltelijke klasse-, struct- of interfacedeclaratie maakt een nieuwe declaratieruimte. Elke gedeeltelijke klasse-, struct- of interfacedeclaratie draagt bij aan een declaratieruimte die wordt gedeeld door alle overeenkomende onderdelen in hetzelfde programma (§16.2.4). Namen worden in deze declaratieruimte ingevoerd via class_member_declarations, struct_member_declarations, interface_member_declarations of type_parameters. Behalve voor overbelaste instantieconstructordeclaraties en statische constructordeclaraties kan een klasse, struct of interface geen liddeclaratie bevatten met dezelfde naam als de klasse, struct of interface. Een klasse, struct of interface staat de declaratie van overbelaste methoden en indexeerfuncties toe. Bovendien staat een klasse of struct de declaratie van overbelaste exemplaarconstructors en operators toe. Een klasse, struct of interface kan bijvoorbeeld meerdere methodedeclaraties met dezelfde naam bevatten, mits deze methodedeclaraties verschillen in hun handtekening (§7.5). Basisklassen dragen niet bij aan de declaratieruimte van een klasse en basisinterfaces dragen niet bij aan de declaratieruimte van een interface. Een afgeleide klasse of interface mag dus een lid met dezelfde naam declareren als een overgenomen lid. Een dergelijk lid wordt gezegd het overgenomen lid te verbergen .
  • Elke gedelegeerdedeclaratie maakt een nieuwe declaratieruimte. Namen worden in deze declaratieruimte ingevoerd via parameters (fixed_parameteren parameter_collections) en type_parameters.
  • Elke opsommingsdeclaratie maakt een nieuwe declaratieruimte. Namen worden in deze declaratieruimte ingevoerd via enum_member_declarations.
  • Elke methodeverklaring, eigenschapverklaring, eigenschapstoegangsverklaring, indexeerderverklaring, indexeerdertoegangsverklaring, operatorverklaring, instantieconstructorverklaring, anonieme functieverklaring en lokale functieverklaring creëert een nieuwe declaratieruimte genaamd een lokale variabele declaratieruimte. Namen worden in deze declaratieruimte ingevoerd via parameters (fixed_parameteren parameter_collections) en type_parameters. De set en init-toegangsfunctie voor een eigenschap of een indexeerfunctie introduceren de naam value als parameter. De hoofdtekst van het functielid, de anonieme functie of de lokale functie, indien aanwezig, wordt beschouwd als genest binnen de declaratieruimte van de lokale variabele. Wanneer een ruimte voor declaratie van lokale variabelen en een geneste ruimte voor lokale variabelen elementen bevatten met dezelfde naam, binnen het bereik van de geneste lokale naam, wordt de buitenste lokale naam verborgen (§7.6.1) door de geneste lokale naam.

    Opmerking: Verwijder parameters van anonieme functies (§12.22.2) introduceer geen namen in een declaratieruimte. eindnotitie

  • Er kunnen extra lokale declaratieruimten voor variabelen optreden binnen liddeclaraties, anonieme functies en lokale functies. Namen worden in deze declaratieruimten geïntroduceerd via patroons, declaration_expressions, declaration_statementen exception_specifiers. Lokale variabelendeclaratieruimten kunnen genest zijn, maar het is een fout als een lokale variabelendeclaratieruimte en een geneste lokale variabelendeclaratieruimte elementen met dezelfde naam bevatten. In een geneste declaratieruimte is het dus niet mogelijk om een lokale variabele, lokale functie of constante te declareren met dezelfde naam als een parameter, typeparameter, lokale variabele, lokale functie of constante in een ingesloten declaratieruimte. Het is mogelijk dat twee declaratieruimten elementen met dezelfde naam bevatten zolang geen van beide declaratieruimte de andere bevat. Lokale declaratieruimten worden gemaakt door de volgende constructies:
    • Elke variable_initializer in een veld- en eigenschapsdeclaratie introduceert een eigen lokale ruimte voor variabeledeclaratie, die niet is genest binnen een andere ruimte voor declaratie van lokale variabelen.
    • De hoofdtekst van een functielid, anonieme functie of lokale functie, indien van toepassing, maakt een lokale ruimte voor variabeledeclaratie die wordt beschouwd als genest binnen de declaratieruimte van de lokale variabele van de functie.
    • Elke constructor_initializer maakt een lokale ruimte voor variabeledeclaratie die is genest binnen de declaratie van de instantieconstructor. De ruimte voor declaratie van lokale variabelen voor de hoofdtekst van de constructor is op zijn beurt genest binnen deze lokale ruimte voor declaratie van variabelen.
    • Elk blok, switch_block, specific_catch_clause, iteration_statement en using_statement maakt een geneste ruimte voor lokale variabeledeclaratie.
    • Elke embedded_statement die niet rechtstreeks deel uitmaakt van een statement_list maakt een geneste ruimte voor declaratie van lokale variabelen.
    • Elke switch_section maakt een geneste lokale variabeledeclaratieruimte. Variabelen die rechtstreeks in de statement_list van de switch_section worden gedeclareerd (maar niet binnen een geneste ruimte voor lokale variabelen binnen de statement_list) worden echter rechtstreeks toegevoegd aan de ruimte voor lokale variabelendeclaratie van de switch_block in plaats van die van de switch_section.
    • De syntactische vertaling van een query_expression (§12.23.3) kan een of meer lambda-expressies introduceren. Als anonieme functies maakt elk van deze functies een lokale ruimte voor variabeledeclaratie zoals hierboven beschreven.
  • Elk blok of switch_block maakt een afzonderlijke declaratieruimte voor labels. Namen worden in deze declaratieruimte binnengebracht via labeled_statements en de namen worden verwezen via goto_statements. De labeldeclaratieruimte van een blok omvat ook ingesloten blokken. Daarom is het binnen een geneste blok niet mogelijk om een label met dezelfde naam als een label in een omsluitblok te declareren.

Opmerking: het feit dat variabelen die rechtstreeks in een switch_section zijn gedeclareerd, worden toegevoegd aan de lokale ruimte voor variabeledeclaratie van de switch_block in plaats van de switch_section kunnen leiden tot verrassende code. In het onderstaande voorbeeld bevindt de lokale variabele y zich binnen het bereik van de switchsectie voor de standaardcase, ondanks de declaratie die wordt weergegeven in de switchsectie voor case 0. De lokale variabele z valt niet binnen het bereik van de switchsectie voor de standaardcase, omdat deze wordt geïntroduceerd in de ruimte voor lokale variabeledeclaratie voor de switchsectie waarin de declaratie plaatsvindt.

int x = 1;
switch (x)
{
    case 0:
        int y;
        break;
    case var z when z < 10:
        break;
    default:
        y = 10;
        // Valid: y is in scope
        Console.WriteLine(x + y);
        // Invalid: z is not scope
        Console.WriteLine(x + z);
        break;
}

eindnotitie

De tekstvolgorde waarin namen worden gedeclareerd, is over het algemeen van geen betekenis. Met name de tekstvolgorde is niet belangrijk voor de declaratie en het gebruik van naamruimten, constanten, methoden, eigenschappen, gebeurtenissen, indexeerfuncties, operators, instantieconstructors, finalizers, statische constructors en typen. De volgorde van declaraties is op de volgende manieren aanzienlijk:

  • Declaratievolgorde voor velddeclaraties bepaalt de volgorde waarin hun initialisatieprogramma's (indien aanwezig) worden uitgevoerd (§15.5.6.2, §15.5.6.3).
  • Lokale variabelen worden gedefinieerd voordat ze worden gebruikt (§7.6).
  • De declaratievolgorde voor enumliddeclaraties (§20.4) is significant wanneer constant_expression waarden worden weggelaten.

Voorbeeld: De declaratieruimte van een naamruimte is 'open ended' en twee naamruimtedeclaraties met dezelfde volledig gekwalificeerde naam dragen bij aan dezelfde declaratieruimte. Bijvoorbeeld

namespace Megacorp.Data
{
    class Customer
    {
        ...
    }
}

namespace Megacorp.Data
{
    class Order
    {
        ...
    }
}

De twee bovenstaande naamruimtedeclaraties dragen bij aan dezelfde declaratieruimte, in dit geval het declareren van twee klassen met de volledig gekwalificeerde namen Megacorp.Data.Customer en Megacorp.Data.Order. Omdat de twee declaraties bijdragen aan dezelfde declaratieruimte, zou dit een compilatiefout hebben veroorzaakt als elk een declaratie van een klasse met dezelfde naam bevatte.

eindvoorbeeld

Opmerking: Zoals hierboven is opgegeven, bevat de declaratieruimte van een blok de geneste blokken. In het volgende voorbeeld resulteren de F en G methoden dus in een compilatietijdfout omdat de naam i wordt gedeclareerd in het buitenste blok en niet opnieuw kan worden gedeclareerd in het binnenste blok. De H en I methoden zijn echter geldig omdat de twee iworden gedeclareerd in afzonderlijke niet-geneste blokken.

class A
{
    void F()
    {
        int i = 0;
        if (true)
        {
            int i = 1;
        }
    }

    void G()
    {
        if (true)
        {
            int i = 0;
        }
        int i = 1;
    }

    void H()
    {
        if (true)
        {
            int i = 0;
        }
        if (true)
        {
            int i = 1;
        }
    }

    void I()
    {
        for (int i = 0; i < 10; i++)
        {
            H();
        }
        for (int i = 0; i < 10; i++)
        {
            H();
        }
    }
}

eindnotitie

7.3 Leden

7.3.1 Algemeen

Naamruimten en typen hebben lid-s.

Opmerking: de leden van een entiteit zijn algemeen beschikbaar via het gebruik van een gekwalificeerde naam die begint met een verwijzing naar de entiteit, gevolgd door een token '.' gevolgd door de naam van het lid. eindnotitie

Leden van een type worden gedeclareerd in de typedeclaratie of overgenomen van de basisklasse van het type. Wanneer een type wordt overgenomen van een basisklasse, worden alle leden van de basisklasse, behalve instantieconstructors, finalizers en statische constructors lid van het afgeleide type. De gedeclareerde toegankelijkheid van een basisklasselid bepaalt niet of het lid wordt overgenomen. Overname wordt uitgebreid naar een lid dat geen instantieconstructor, statische constructor of finalizer is.

Opmerking: een overgenomen lid is mogelijk niet toegankelijk in een afgeleid type, bijvoorbeeld vanwege de gedeclareerde toegankelijkheid (§7.4.2). eindnotitie

7.3.2 Naamruimteleden

Naamruimten en typen die geen naamruimte insluiten, zijn lid van de globale naamruimte. Dit komt rechtstreeks overeen met de namen die zijn gedeclareerd in de globale declaratieruimte.

Naamruimten en typen die in een naamruimte zijn gedeclareerd, zijn lid van die naamruimte. Dit komt rechtstreeks overeen met de namen die zijn gedeclareerd in de declaratieruimte van de naamruimte.

Naamruimten hebben geen toegangsbeperkingen. Het is niet mogelijk om persoonlijke, beveiligde of interne naamruimten te declareren en namen van naamruimten zijn altijd openbaar toegankelijk.

7.3.3 Struct-leden

De leden van een struct zijn de leden die zijn gedeclareerd in de struct en de leden die zijn overgenomen van de directe basisklasse System.ValueType van de struct en de indirecte basisklasse object.

De leden van een eenvoudig type komen rechtstreeks overeen met de leden van het struct-type, aangeduid met het eenvoudige type (§8.3.5).

7.3.4 Opsommingsleden

De leden van een opsomming zijn de constanten die zijn gedeclareerd in de opsomming en de leden die zijn overgenomen van de directe basisklasse System.Enum van de opsomming en de indirecte basisklassen System.ValueType en object.

7.3.5 Klasleden

De leden van een klasse zijn de leden die zijn gedeclareerd in de klasse en de leden die zijn overgenomen van de basisklasse (met uitzondering van klasse object die geen basisklasse heeft). De leden die zijn overgenomen van de basisklasse omvatten de constanten, velden, methoden, eigenschappen, gebeurtenissen, indexeerfuncties, operators en typen van de basisklasse, maar niet de instantieconstructors, finalizers en statische constructors van de basisklasse. Basisklasseleden worden overgenomen zonder rekening te houden met hun toegankelijkheid.

Een klassedeclaratie kan declaraties bevatten van constanten, velden, methoden, eigenschappen, gebeurtenissen, indexeerfuncties, operators, instantieconstructors, finalizers, statische constructors en typen.

De leden van object (§8.2.3) en string (§8.2.5) komen rechtstreeks overeen met de leden van de klassetypen die ze alias hebben.

7.3.6 Interfaceleden

De leden van een interface zijn de leden die zijn gedeclareerd in de interface en in alle basisinterfaces van de interface.

Opmerking: De leden in de klas object zijn niet strikt genomen leden van een interface (§19.4). De leden in de klas object zijn echter beschikbaar via het opzoeken van leden in elk interfacetype (§12.5). eindnotitie

7.3.7 Matrixleden

De leden van een matrix zijn de leden die zijn overgenomen van klasse System.Array.

7.3.8 Gedelegeerden

Een gemachtigde neemt leden over van de klasse System.Delegate. Daarnaast bevat het een methode met de naam Invoke met hetzelfde retourtype en dezelfde lijst met parameters die zijn opgegeven in de declaratie (§21.2). Een aanroep van deze methode gedraagt zich identiek aan een gemachtigde aanroep (§21.6) op dezelfde gemachtigde instantie.

Een implementatie kan extra leden bieden, hetzij via overname of rechtstreeks in de gedelegeerde zelf.

7.4 Toegang tot leden

7.4.1 Algemeen

Declaraties van leden staan controle over toegang tot leden toe. De toegankelijkheid van een lid wordt vastgesteld door de gedeclareerde toegankelijkheid (§7.4.2) van het lid in combinatie met de toegankelijkheid van het onmiddellijk bevattende type, indien van toepassing.

Wanneer toegang tot een bepaald lid is toegestaan, wordt gezegd dat het lid toegankelijk is. Als de toegang tot een bepaald lid daarentegen niet is toegestaan, wordt gezegd dat het lid niet toegankelijk is. Toegang tot een lid is toegestaan wanneer de tekstlocatie waar de toegang plaatsvindt, is opgenomen in het toegankelijkheidsdomein (§7.4.3) van het lid.

7.4.2 Opgegeven toegankelijkheid

De gedeclareerde toegankelijkheid van een lid kan een van de volgende zijn:

  • Openbaar, dat is geselecteerd door een public modifier op te nemen in de liddeclaratie. De intuïtieve betekenis van public is 'toegang niet beperkt'.
  • Beveiligd, geselecteerd door het opgeven van een protected modifier in de liddeclaratie. De intuïtieve betekenis van protected is "toegang beperkt tot de met klasse of interface, of klassen of interfaces die zijn afgeleid van het betreffende type".
  • Intern, die wordt geselecteerd door een internal modifier op te nemen in de lidverklaring. De intuïtieve betekenis van internal is "toegang beperkt tot deze assemblage".
  • Beveiligd intern, dat wordt gekozen door zowel een protected als een internal modifier op te nemen in de liddeclaratie. De intuïtieve betekenis van protected internal is 'toegankelijk binnen deze assembly, evenals typen die zijn afgeleid van de klasse die de inhoud bevat'.
  • Privé beschermd, dat wordt geselecteerd door zowel een private als een protected modifier in de liddefinitie op te geven. De intuïtieve betekenis van private protected is 'toegankelijk binnen deze assembly door de bevatde klasse en typen die zijn afgeleid van de inhoudsklasse'.
  • Privé, die wordt geselecteerd door een private modificator op te nemen in de liddeclaratie. De intuïtieve betekenis van private is 'toegang beperkt tot het betreffende type'.

Afhankelijk van de context waarin een liddeclaratie plaatsvindt, zijn alleen bepaalde typen gedeclareerde toegankelijkheid toegestaan. Wanneer een liddeclaratie geen wijzigingsfuncties voor toegang bevat, bepaalt de context waarin de declaratie plaatsvindt, bovendien de standaard gedeclareerde toegankelijkheid.

  • Naamruimten hebben public impliciet toegankelijkheid gedeclareerd. Er zijn geen toegangsaanpassingen toegestaan voor naamruimtedeclaraties.
  • Typen die rechtstreeks zijn gedeclareerd in compilatie-eenheden of naamruimten (in tegenstelling tot binnen andere typen), kunnen met public of internal gedeclareerde toegankelijkheid hebben en hebben standaard toegankelijkheid van internal.
  • Klasleden kunnen elke van de toegestane soorten gedeclareerde toegankelijkheid hebben en standaard gesteld worden op private gedeclareerde toegankelijkheid.

    Opmerking: een type dat is gedeclareerd als lid van een klasse, kan een van de toegestane soorten toegankelijkheid hebben, terwijl een type dat is gedeclareerd als lid van een naamruimte alleen public of internal toegankelijkheid kan hebben gedeclareerd. eindnotitie

  • Struct-leden kunnen public, internal of private gedeclareerde toegankelijkheid hebben en standaard hebben zij private gedeclareerde toegankelijkheid, omdat structs impliciet verzegeld zijn. Struct-leden die zijn geïntroduceerd in een struct (dat wil zeggen, niet overgenomen door die struct) kunnen geen protected, protected internal, of private protected gedeclareerde toegankelijkheid hebben.

    Opmerking: een type dat als lid van een struct is gedeclareerd, kan toegankelijkheid hebben zoals public, internal of private gedeclareerd, terwijl een type dat is gedeclareerd als lid van een naamruimte alleen toegankelijkheid kan hebben zoals public of internal. eindnotitie

  • Interfaceleden hebben public impliciet toegankelijkheid gedeclareerd.
  • Opsommingsleden hebben public impliciet toegankelijkheid gedeclareerd. Er zijn geen toegangsmodifiers toegestaan voor inventarisatieliddeclaraties.

7.4.3 Toegankelijkheidsdomeinen

Het toegankelijkheidsdomein van een lid bestaat uit de (mogelijk niet-aaneengesloten) secties van programmatekst waarin toegang tot het lid is toegestaan. Voor het definiëren van het toegankelijkheidsdomein van een lid zegt men dat een lid toplevel is als het niet binnen een type wordt gedeclareerd, en een lid wordt genest als het binnen een ander type wordt gedeclareerd. Bovendien wordt de programmatekst van een programma gedefinieerd als alle tekst in alle compilatie-eenheden van het programma en wordt de programmatekst van een type gedefinieerd als alle tekst in de type_declarations van dat type (inclusief mogelijk typen die zijn genest binnen het type).

Het toegankelijkheidsdomein van een vooraf gedefinieerd type (zoals object, intof double) is onbeperkt.

Het toegankelijkheidsdomein van een niet-afhankelijk type T op het hoogste niveau (§8.4.4) dat in een programma P wordt gedeclareerd, wordt als volgt gedefinieerd:

  • Als de gedeclareerde toegankelijkheid T openbaar is, is het toegankelijkheidsdomein T de programmatekst van P en elk programma waarnaar wordt verwezen P.
  • Als de gedeclareerde toegankelijkheid T intern is, is het toegankelijkheidsdomein T de programmatekst van P.

Opmerking: Uit deze definities volgt dat het toegankelijkheidsdomein van een niet-afhankelijk type op het hoogste niveau altijd ten minste de programmatekst is van het programma waarin dat type wordt gedeclareerd. eindnotitie

Het toegankelijkheidsdomein voor een samengesteld type T<A₁, ..., Aₑ> is het snijpunt van het toegankelijkheidsdomein van het niet-afhankelijke algemene type T en de toegankelijkheidsdomeinen van de typeargumenten A₁, ..., Aₑ.

Het toegankelijkheidsdomein van een geneste lid M dat is gedeclareerd in een type T binnen een programma P, wordt als volgt gedefinieerd (waarbij wordt aangegeven dat M zichzelf mogelijk een type is):

  • Als de gedeclareerde toegankelijkheid van Mpublic is, is het toegankelijkheidsdomein van M hetzelfde als dat van T.
  • Als de toegankelijkheid van Mprotected internal is, moet D de samenvoeging zijn van de programmatekst van P en de programmatekst van elk type dat van Tis afgeleid en buiten P is gedeclareerd. Het toegankelijkheidsdomein van M is het snijpunt van het toegankelijkheidsdomein van T met D.
  • Als de gedeclareerde toegankelijkheid M is private protected, laten we D het snijpunt zijn van de programmatekst van P en de programmatekst van T en elk type dat is afgeleid van T. Het toegankelijkheidsdomein van M is het snijpunt van het toegankelijkheidsdomein van T met D.
  • Als de gedeclareerde toegankelijkheid M is protected, laat u D de samenvoeging zijn van de programmatekst van Ten de programmatekst van elk type dat is afgeleid van T. Het toegankelijkheidsdomein van M is het snijpunt van het toegankelijkheidsdomein van T met D.
  • Als de gedeclareerde toegankelijkheid M is internal, is het toegankelijkheidsdomein M het snijpunt van het toegankelijkheidsdomein van T met de programmatekst van P.
  • Als de gedeclareerde toegankelijkheid M is private, is het toegankelijkheidsdomein M de programmatekst van T.

Opmerking: Uit deze definities volgt het dat het toegankelijkheidsdomein van een genest lid altijd ten minste de programmatekst is van het type waarin het lid wordt gedeclareerd. Bovendien is het toegankelijkheidsdomein van een lid nooit inclusiever dan het toegankelijkheidsdomein van het type waarin het lid wordt gedeclareerd. eindnotitie

Opmerking: Wanneer een type of lid M wordt geopend, worden de volgende stappen geëvalueerd om ervoor te zorgen dat de toegang is toegestaan:

  • M Als deze wordt gedeclareerd binnen een type (in plaats van een compilatie-eenheid of een naamruimte), treedt er eerst een compilatiefout op als dat type niet toegankelijk is.
  • Vervolgens, als Mpublic is, is toegang toegestaan.
  • Als dat niet het geval isprotected internal, M is de toegang toegestaan als deze plaatsvindt in het programma waarin M wordt gedeclareerd, of als deze plaatsvindt binnen een klasse die is afgeleid van de klasse waarin M wordt gedeclareerd en plaatsvindt via het afgeleide klassetype (§7.4.4).
  • M Als dat niet het geval isprotected, is de toegang toegestaan als deze plaatsvindt binnen de klasse waarin M wordt gedeclareerd, of als deze plaatsvindt binnen een klasse die is afgeleid van de klasse waarin M wordt gedeclareerd en plaatsvindt via het afgeleide klassetype (§7.4.4).
  • Anders, als Minternal is, is de toegang toegestaan als het plaatsvindt binnen het programma waarin M wordt gedeclareerd.
  • Anders, als Mprivate is, is de toegang toegestaan als dit gebeurt binnen het type waarin M wordt gedeclareerd.
  • Anders is het type of lid niet toegankelijk en treedt er een compileertijdfout op. eindnotitie

Voorbeeld: In de volgende code

public class A
{
    public static int X;
    internal static int Y;
    private static int Z;
}

internal class B
{
    public static int X;
    internal static int Y;
    private static int Z;

    public class C
    {
        public static int X;
        internal static int Y;
        private static int Z;
    }

    private class D
    {
        public static int X;
        internal static int Y;
        private static int Z;
    }
}

de klassen en leden beschikken over de volgende toegankelijkheidsdomeinen:

  • Het toegankelijkheidsdomein van A en A.X is onbeperkt.
  • Het toegankelijkheidsdomein van A.Y, B, B.X, B.Y, B.C, B.C.X en B.C.Y is de programmatekst van het programma.
  • Het toegankelijkheidsdomein van A.Z is de programmatekst van A.
  • Het toegankelijkheidsdomein van B.Z en B.D is de programmatekst van B, inclusief de programmatekst van B.C en B.D.
  • Het toegankelijkheidsdomein van B.C.Z is de programmatekst van B.C.
  • Het toegankelijkheidsdomein van B.D.X en B.D.Y is de programmatekst van B, inclusief de programmatekst van B.C en B.D.
  • Het toegankelijkheidsdomein van B.D.Z is de programmatekst van B.D. Zoals in het voorbeeld wordt geïllustreerd, is het toegankelijkheidsdomein van een lid nooit groter dan die van een type dat het bevat. Hoewel alle leden bijvoorbeeld openbare toegankelijkheid hebben opgegeven, hebben alle behalve X toegankelijkheidsdomeinen die beperkt worden door een omsluitend type.

eindvoorbeeld

Zoals beschreven in §7.3 worden alle leden van een basisklasse, met uitzondering van instantieconstructors, finalizers en statische constructors, overgenomen door afgeleide typen. Dit omvat zelfs privéleden van een basisklasse. Het toegankelijkheidsdomein van een privélid bevat echter alleen de programmatekst van het type waarin het lid wordt gedeclareerd.

Voorbeeld: In de volgende code

class A
{
    int x;

    static void F(B b)
    {
        b.x = 1;         // Ok
    }
}

class B : A
{
    static void F(B b)
    {
        b.x = 1;         // Error, x not accessible
    }
}

de B klasse neemt het privélid x over van de A klasse. Omdat het lid privé is, is het alleen toegankelijk binnen de class_body van A. In de b.x methode slaagt de toegang tot A.F dus, maar in de B.F methode mislukt het.

eindvoorbeeld

7.4.4 Beveiligde toegang

Wanneer een protected of private protected exemplaarlid wordt geopend buiten de programmatekst van de klasse waarin het wordt gedeclareerd en wanneer een protected internal exemplaarlid wordt geopend buiten de programmatekst van het programma waarin het wordt gedeclareerd, vindt de toegang plaats binnen een klassedeclaratie die is afgeleid van de klasse waarin het wordt gedeclareerd. Bovendien moet de toegang plaatsvinden via een exemplaar van dat afgeleide klassetype of een klassetype dat daaruit is samengesteld. Deze beperking voorkomt dat een afgeleide klasse toegang heeft tot beveiligde leden van andere afgeleide klassen, zelfs wanneer de leden worden overgenomen van dezelfde basisklasse. Leden van de instantieinterface die zijn gedefinieerd met protected of private protected toegang, kunnen niet worden geopend vanuit een class interface of struct die die interface implementeert. Deze kunnen alleen worden geopend vanuit afgeleide interfaces. class Typen struct kunnen echter instantieleden implementeren protected die zijn gedeclareerd in een interface die ze implementeren.

Laten we B een basisklasse zijn die een beveiligd exemplaarlid Mdeclareert en een klasse is D die is afgeleid van B. Binnen de class_body (of record_class_body) van Dkan toegang tot M een van de volgende vormen worden gebruikt:

  • Een niet-gekwalificeerde type_name of primary_expression in de vorm van M.
  • Een primary_expression van de vorm E.M, op voorwaarde dat het type ET is of een klasse die is afgeleid van T, waar T de klasse D is, of een klassetype dat is samengesteld uit D.
  • Een primary_expression van de vorm base.M.
  • Een primaire_expressie van de vorm base[argumentenlijst].

Naast deze vormen van toegang heeft een afgeleide klasse toegang tot een beveiligde exemplaarconstructor van een basisklasse in een constructor_initializer (§15.11.2).

Voorbeeld: In de volgende code

public class A
{
    protected int x;

    static void F(A a, B b)
    {
        a.x = 1; // Ok
        b.x = 1; // Ok
    }
}

public class B : A
{
    static void F(A a, B b)
    {
        a.x = 1; // Error, must access through instance of B
        b.x = 1; // Ok
    }
}

binnen A, is het mogelijk om toegang x te krijgen via exemplaren van beide A en B, aangezien in beide gevallen de toegang plaatsvindt via een exemplaar van A of een klasse afgeleid van A. Binnen B is het echter niet mogelijk om via een exemplaar van x toegang te krijgen tot A, omdat A niet afkomstig is van B.

eindvoorbeeld

Voorbeeld:

class C<T>
{
    protected T x;
}

class D<T> : C<T>
{
    static void F()
    {
        D<T> dt = new D<T>();
        D<int> di = new D<int>();
        D<string> ds = new D<string>();
        dt.x = default(T);
        di.x = 123;
        ds.x = "test";
    }
}

Hier zijn de drie toewijzingen aan x toegestaan omdat ze allemaal plaatsvinden door middel van instanties van klassetypen die zijn samengesteld vanuit het generieke type.

eindvoorbeeld

Opmerking: Het toegankelijkheidsdomein (§7.4.3) van een beveiligd lid dat in een algemene klasse is gedeclareerd, bevat de programmatekst van alle klassedeclaraties die zijn afgeleid van elk type dat is samengesteld uit die algemene klasse. In het voorbeeld:

class C<T>
{
    protected static T x;
}

class D : C<string>
{
    static void Main()
    {
        C<int>.x = 5;
    }
}

de verwijzing naar protected lid C<int>.x in D is geldig, ook al is de klasse D afgeleid van C<string>. eindnotitie

7.4.5 Toegankelijkheidsbeperkingen

Voor verschillende constructies in de C#-taal moet een type ten minste zo toegankelijk zijn als lid of een ander type. Een type T wordt geacht minstens zo toegankelijk te zijn als lid of type M als het toegankelijkheidsdomein T een superset is van het toegankelijkheidsdomein van M. Met andere woorden, T is minstens zo toegankelijk als M als T toegankelijk is in alle contexten waarin M toegankelijk is.

De volgende toegankelijkheidsbeperkingen bestaan:

  • De directe basisklasse van een klassetype moet ten minste zo toegankelijk zijn als het klassetype zelf.
  • De expliciete basisinterfaces van een interfacetype moeten ten minste zo toegankelijk zijn als het interfacetype zelf.
  • Het retourtype en de parametertypen van een delegate moeten ten minste zo toegankelijk zijn als het delegatetype zelf.
  • Het type constante moet minstens zo toegankelijk zijn als de constante zelf.
  • Het type veld moet minstens zo toegankelijk zijn als het veld zelf.
  • Het retourtype en de parametertypen van een methode moeten ten minste zo toegankelijk zijn als de methode zelf.
  • Het type van een eigenschap moet minstens zo toegankelijk zijn als de eigenschap zelf.
  • Het type gebeurtenis moet minstens zo toegankelijk zijn als de gebeurtenis zelf.
  • Het type en de parametertypen van een indexeerfunctie moeten ten minste zo toegankelijk zijn als de indexeerfunctie zelf.
  • Het retourtype en de parametertypen van een operator moeten ten minste zo toegankelijk zijn als de operator zelf.
  • De parametertypen van een exemplaarconstructor moeten ten minste zo toegankelijk zijn als de instantieconstructor zelf.
  • Een interface- of klassetypebeperking voor een typeparameter moet ten minste zo toegankelijk zijn als het lid dat de beperking declareert.

Voorbeeld: In de volgende code

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

de B klasse veroorzaakt een compilatiefout omdat A niet minstens zo toegankelijk is als B.

eindvoorbeeld

Voorbeeld: Op dezelfde manier, in de volgende code

class A {...}

public class B
{
    A F() {...}
    internal A G() {...}
    public A H() {...}
}

de H methode resulteert in B een compilatiefout omdat het retourtype A niet ten minste zo toegankelijk is als de methode.

eindvoorbeeld

7.5 Handtekeningen en overbelasting

Methoden, instantieconstructors, indexeerfuncties en operators worden gekenmerkt door hun handtekening:

  • De handtekening van een methode bestaat uit de naam van de methode, het aantal parameters van het type en het type en de parameterdoorgiftemodus van elk van de parameters, beschouwd in de volgorde van links naar rechts. Voor deze doeleinden wordt elk type parameter van de methode die voorkomt in het type van een parameter niet geïdentificeerd door de naam, maar door de rangschikkelijkheid in de parameterlijst van het type van de methode. De handtekening van een methode bevat niet specifiek het retourtype, parameternamen, type parameternamen, parameterbeperkingen, de params, scopedof this parameter modifiers, noch of parameters vereist of optioneel zijn.
  • De handtekening van een exemplaarconstructor bestaat uit het type en de parameterdoorgiftemodus van elk van de parameters, in de volgorde van links naar rechts. De handtekening van een instantieconstructor bevat niet specifiek de params wijzigingsfunctie die kan worden opgegeven voor de meest rechtse parameter, noch of parameters vereist of optioneel zijn.
  • De handtekening van een indexeerfunctie bestaat uit het type van elk van de parameters, in de volgorde van links naar rechts. De handtekening van een indexeerfunctie bevat niet specifiek het elementtype of de scoped wijzigingsfunctie, noch bevat deze de params wijzigingsfunctie die kan worden opgegeven voor de meest rechtse parameter, of de scoped wijzigingsfunctie, noch of parameters vereist of optioneel zijn.
  • De handtekening van een operator bestaat uit de naam van de operator en het type van elk van de parameters, beschouwd in de volgorde van links naar rechts. De handtekening van een operator bevat niet specifiek het resultaattype of de scoped wijzigingsfunctie.
  • De handtekening van een conversieoperator bestaat uit het brontype en het doeltype. De impliciete of expliciete classificatie van een conversieoperator maakt geen deel uit van de handtekening en is evenmin de scoped wijzigingsfunctie.
  • Twee handtekeningen van hetzelfde type lid (methode, instantieconstructor, indexeerfunctie of operator) worden beschouwd als dezelfde handtekeningen als ze dezelfde naam hebben, het aantal typeparameters, het aantal parameters en de modi voor het doorgeven van parameters en een identiteitsconversie bestaat tussen de typen van de bijbehorende parameters (§10.2.2).

Handtekeningen zijn het inschakelende mechanisme voor het overbelasten van leden in klassen, structs en interfaces:

  • Door overbelasting van methoden kan een klasse, struct of interface meerdere methoden met dezelfde naam declareren, mits hun handtekeningen uniek zijn binnen die klasse, struct of interface.
  • Door het overladen van instantieconstructoren kan een klasse of struct meerdere instantieconstructoren declareren, mits hun signaturen uniek zijn binnen die klasse of struct.
  • Door overbelasting van indexeerfuncties kan een klasse, struct of interface meerdere indexeerfuncties declareren, mits hun handtekeningen uniek zijn binnen die klasse, struct of interface.
  • Door overbelasting van operators kan een klasse of struct meerdere operators met dezelfde naam declareren, mits hun handtekeningen uniek zijn binnen die klasse of struct.

Hoewel parameter_mode_modifierals onderdeel van een handtekening worden beschouwd, kunnen leden die in één type zijn gedeclareerd, niet alleen verschillen in handtekening door deze wijzigingsaanpassingen. Er treedt een compilatiefout op als twee leden in hetzelfde type zijn gedeclareerd met signaturen die hetzelfde zouden zijn als alle parameters in beide methoden met out of in modifiers worden gewijzigd naar ref modifiers. Voor andere doeleinden van handtekeningkoppeling (bijvoorbeeld verbergen of overschrijven), worden parameter_mode_modifiers beschouwd als onderdeel van de handtekening en komen ze niet overeen met elkaar.

Opmerking: Met deze beperking kunnen C#-programma's eenvoudig worden vertaald om te worden uitgevoerd op een platform dat geen manier biedt om methoden te definiëren die alleen verschillen in hun parameter_mode_modifiers. eindnotitie

De typen object en dynamic worden niet onderscheiden bij het vergelijken van handtekeningen. Leden die zijn gedeclareerd in één type waarvan de handtekeningen alleen verschillen door object te vervangen door dynamic zijn niet toegestaan.

Voorbeeld: In het volgende voorbeeld ziet u een set overbelaste methodedeclaraties samen met hun handtekeningen.

interface ITest
{
    void F();                   // F()
    void F(int x);              // F(int)
    void F(ref int x);          // F(ref int)
    void F(ref readonly int x); // F(ref int) error
    void F(out int x);          // F(out int) error
    void F(object o);           // F(object)
    void F(dynamic d);          // error.
    void F(int x, int y);       // F(int, int)
    int F(string s);            // F(string)
    int F(int x);               // F(int) error
    void F(string[] a);         // F(string[])
    void F(params string[] a);  // F(string[]) error
    void F<S>(S s);             // F<0>(0)
    void F<T>(T t);             // F<0>(0) error
    void F<S,T>(S s);           // F<0,1>(0)
    void F<T,S>(S s);           // F<0,1>(1) ok
}

Houd er rekening mee dat een parameter_mode_modifier(§15.6.2) deel uitmaakt van een handtekening. F(int) F(ref int) F(in int) F(out int) F(ref readonly int) Dit zijn dus allemaal unieke handtekeningen. MaarF(in int)F(out int), en F(ref int)F(ref readonly int) kunnen niet binnen dezelfde interface worden gedeclareerd omdat hun handtekeningen alleen verschillen door hun parameter_mode_modifiers. Houd er ook rekening mee dat het retourtype en de params wijzigingsfunctie geen deel uitmaken van een handtekening, dus het is niet mogelijk om alleen te overbelasten op basis van het retourtype of op de opname of uitsluiting van de params wijzigingsfunctie. Als zodanig resulteren de declaraties van de methoden F(int) en F(params string[]) die hierboven zijn geïdentificeerd, tot een compilatiefout. eindvoorbeeld

7.6 Bereiken

7.6.1 Algemeen

Het bereik van een naam is de regio van programmatekst waarin het mogelijk is om te verwijzen naar de entiteit die is gedeclareerd door de naam zonder kwalificatie van de naam. Bereiken kunnen worden genest en een binnenste bereik kan de betekenis van een naam van een buitenste bereik opnieuw declareren. (Dit verwijdert echter niet de beperking die is opgelegd door §7.2 dat het binnen een geneste blok niet mogelijk is om een lokale variabele of lokale constante met dezelfde naam als een lokale variabele of lokale constante in een omsluitblok te declareren.) De naam van het buitenste bereik wordt vervolgens verborgen in de regio van de programmatekst die wordt gedekt door het binnenste bereik en toegang tot de buitenste naam is alleen mogelijk door de naam in aanmerking te komen.

Opmerking: het is mogelijk niet mogelijk om toegang te krijgen tot de verborgen buitennaam van het binnenste bereik omdat er geen manier is om deze in aanmerking te komen, zoals met typeparameters voor geneste typedeclaraties. eindnotitie

  • Het bereik van een naamruimtelid dat is gedeclareerd door een namespace_member_declaration (§14.7) zonder namespace_declaration is de volledige programmatekst.
  • Het bereik van een naamruimtelid dat is gedeclareerd door een compilation_unit_body waarvan de volledig gekwalificeerde naam is N, is elke compilation_unit_body waarvan de volledig gekwalificeerde naam is N of begint met N, gevolgd door een punt.
  • Het bereik van een naamruimtelid dat is gedeclareerd door een namespace_member_declaration binnen een namespace_declaration waarvan de volledig gekwalificeerde naam is N, is de namespace_body van elke namespace_declaration waarvan de volledig gekwalificeerde naam is N of begint met N, gevolgd door een punt. Per §14,3 worden file_scoped_namespace_declarations behandeld als gelijkwaardige namespace_declarations voor deze regel.
  • Het bereik van een naam die is gedefinieerd door een extern_alias_directive (§14.4) strekt zich uit over de global_using_directives, using_directives, global_attributes en compilation_unit_body van de naam die onmiddellijk compilation_unit of namespace_body bevat. Een extern_alias_directive draagt geen nieuwe leden bij aan de onderliggende declaratieruimte. Met andere woorden, een extern_alias_directive is niet transitief, maar is in plaats daarvan alleen van invloed op de compilation_unit, namespace_body en file_scoped_namespace_declaration waarin deze plaatsvindt.
  • Het bereik van een naam die is gedefinieerd of geïmporteerd door een global_using_directive breidt zich uit over de global_attributes en compilation_unit_body van alle compilation_units in het programma.
  • Het bereik van een naam die is gedefinieerd of geïmporteerd door een using_directive (§14.6) strekt zich uit over de global_attributes, statement_lists en namespace_member_declarationvan de compilation_unit of namespace_body waarin de using_directive plaatsvindt. Een using_directive kan nul of meer naamruimte- of typenamen beschikbaar maken binnen een bepaalde compilation_unit of namespace_body, maar draagt geen nieuwe leden bij aan de onderliggende declaratieruimte. Met andere woorden, een using_directive is niet transitief, maar is alleen van invloed op de compilation_unit of namespace_body waarin deze plaatsvindt.
  • Het bereik van een typeparameter die is gedeclareerd door een type_parameter_list op een class_declaration (§15.2) is de class_base, type_parameter_constraints_clauseen class_body (of record_class_body) van die class_declaration.

    Opmerking: In tegenstelling tot leden van een klasse, wordt dit bereik niet uitgebreid naar afgeleide klassen. eindnotitie

  • Het bereik van een typeparameter die is gedeclareerd door een type_parameter_list op een struct_declaration (§16.2) is de struct_interfaces, type_parameter_constraints_clauses en struct_body (of record_struct_body) van die struct_declaration.
  • Het bereik van een typeparameter die is gedeclareerd door een type_parameter_list op een interface_declaration (§19.2) is de interface_base, type_parameter_constraints_clauses en interface_body van die interface_declaration.
  • Het bereik van een typeparameter die is gedeclareerd door een type_parameter_list op een delegate_declaration (§21.2) is de return_type, parameter_list en type_parameter_constraints_clausevan die delegate_declaration.
  • Het bereik van een typeparameter die is gedeclareerd door een type_parameter_list op een method_declaration (§15.6.1) is de method_declaration en nameof expressies in een kenmerk op de method_declaration of de bijbehorende parameters.
  • Het bereik van een lid dat is gedeclareerd door een class_member_declaration (§15.3.1) is de class_body (of record_class_body) waarin de aangifte plaatsvindt. Daarnaast is het bereik van een klasselid uitgebreid tot de class_body (of record_class_body) van die afgeleide klassen die zijn opgenomen in het toegankelijkheidsdomein (§7.4.3) van het lid.
  • Het bereik van een lid dat door een struct_member_declaration (§16.3) is gedeclareerd, is de struct_body (of record_struct_body) waarin de aangifte plaatsvindt.
  • Het bereik van een lid dat is gedeclareerd door een enum_member_declaration (§20.4) is de enum_body waarin de verklaring plaatsvindt.
  • Het bereik van een parameter die is gedeclareerd in een method_declaration (§15.6) is de method_body of ref_method_body van die method_declaration en nameof expressies in een kenmerk voor de methodedeclaratie of de bijbehorende parameters.
  • Het bereik van een parameter die is gedeclareerd in een indexer_declaration (§15.9) is de indexer_body van die indexer_declaration.
  • Het bereik van een parameter die is gedeclareerd in een operator_declaration (§15.10) is de operator_body van die operator_declaration.
  • Het bereik van een parameter die is gedeclareerd in een constructeurverklaring (§15.11) is de constructeurinitialisatie en het blok van die constructeurverklaring.
  • Met uitzondering van verwijderingsparameters (§12.22.2), is het bereik van een parameter die is gedeclareerd in een lambda_expression (§12.22) de lambda_expression_body van die lambda_expression en nameof expressies in een kenmerk op de anonieme functie of de bijbehorende parameters.
  • Met uitzondering van verwijderingsparameters (§12.22.2), is het bereik van een parameter die is gedeclareerd in een anonymous_method_expression (§12.22) het blok van dat anonymous_method_expression en nameof expressies in een kenmerk op de anonieme functie of de bijbehorende parameters.
  • Het bereik van een label dat is gedeclareerd in een labeled_statement (§13.5) is het blok waarin de declaratie plaatsvindt.
  • Het bereik van een lokale variabele die is gedeclareerd in een local_variable_declaration (§13.6.2) is het blok waarin de declaratie plaatsvindt.
  • Het bereik van een lokale variabele die is gedeclareerd in een switch_block van een switch instructie (§13.8.3) is de switch_block.
  • Het bereik van een lokale variabele die is gedeclareerd in een for_initializer van een for instructie (§13.9.4) omvat de for_initializer, for_condition, for_iterator, en embedded_statement van de for instructie.
  • Het bereik van een lokale constante die is gedeclareerd in een local_constant_declaration (§13.6.3) is het blok waarin de declaratie plaatsvindt. Het is een compilatietijdfout om te verwijzen naar een lokale constante in een tekstuele positie die zich vóór zijn constant_declarator bevindt.
  • Het bereik van een variabele die is gedeclareerd als onderdeel van een foreach_statement, using_statement, lock_statement of query_expression wordt bepaald door de uitbreiding van de opgegeven constructie.

Binnen het bereik van een naamruimte, klasse, struct of opsommingslid is het mogelijk om te verwijzen naar het lid in een tekstuele positie die voorafgaat aan de declaratie van het lid.

Voorbeeld:

class A
{
    void F()
    {
        i = 1;
    }

    int i = 0;
}

Hier is het geldig om F naar i te verwijzen voordat deze wordt gedeclareerd.

eindvoorbeeld

Binnen het bereik van een lokale variabele is het een compilatiefout om naar de lokale variabele te verwijzen in een tekstuele positie die voorafgaat aan zijn declarator.

Voorbeeld:

class A
{
    int i = 0;

    void F()
    {
        i = 1;                // Error, use precedes declaration
        int i;
        i = 2;
    }

    void G()
    {
        int j = (j = 1);     // Valid
    }

    void H()
    {
        int a = 1, b = ++a; // Valid
    }
}

In de bovenstaande methode verwijst de eerste toewijzing naar F specifiek niet naar het veld dat in het buitenste bereik is gedeclareerd. In plaats daarvan verwijst deze naar de lokale variabele en resulteert deze in een compileertijdfout, omdat deze tekstually voorafgaat aan de declaratie van de variabele. In de G-methode is het gebruik van j in de initialisatiefunctie voor de declaratie van j geldig, omdat het gebruik niet voorafgaat aan de declarator. In de H methode verwijst een volgende declaratie juist naar een lokale variabele die is gedeclareerd in een eerdere declaratie binnen dezelfde local_variable_declaration.

eindvoorbeeld

Opmerking: De bereikregels voor lokale variabelen en lokale constanten zijn ontworpen om te garanderen dat de betekenis van een naam die wordt gebruikt in een expressiecontext altijd hetzelfde is binnen een blok. Als het bereik van een lokale variabele alleen zou worden uitgebreid van de declaratie tot het einde van het blok, zou in het bovenstaande voorbeeld de eerste toewijzing worden toegewezen aan de instantievariabele en de tweede toewijzing zou worden toegewezen aan de lokale variabele, wat mogelijk leidt tot compileertijdfouten als de instructies van het blok later opnieuw zouden worden gerangschikt.)

De betekenis van een naam binnen een blok kan verschillen op basis van de context waarin de naam wordt gebruikt. In het voorbeeld

class A {}

class Test
{
    static void Main()
    {
        string A = "hello, world";
        string s = A;                      // expression context
        Type t = typeof(A);                // type context
        Console.WriteLine(s);              // writes "hello, world"
        Console.WriteLine(t);              // writes "A"
    }
}

de naam A wordt gebruikt in een expressiecontext om te verwijzen naar de lokale variabele A en in een typecontext om naar de klasse Ate verwijzen.

eindnotitie

Zoals beschreven in §7.1.3, worden brontokens op het hoogste niveau ingesloten door de gegenereerde invoerpuntmethode.

Als de algemene naamruimte eenmaal is bereikt, wordt eerst geprobeerd de naam binnen de gegenereerde invoerpuntmethode te evalueren en alleen als deze poging mislukt, de evaluatie binnen de globale naamruimtedeclaratie wordt uitgevoerd.

Dit kan leiden tot naamschaduwing van naamruimten en typen die zijn gedeclareerd in de globale naamruimte en tot schaduw van geïmporteerde namen.

Als de eenvoudige naamevaluatie buiten de instructies op het hoogste niveau plaatsvindt en de evaluatie een lokale variabele of functie op het hoogste niveau oplevert, resulteert een foutresultaten in de compilatietijd.

7.6.2 Naam verbergen

7.6.2.1 Algemeen

Het bereik van een entiteit omvat doorgaans meer programmatekst dan de declaratieruimte van de entiteit. Het bereik van een entiteit kan met name declaraties bevatten die nieuwe declaratieruimten met entiteiten met dezelfde naam introduceren. Dergelijke declaraties zorgen ervoor dat de oorspronkelijke entiteit verborgen wordt. Omgekeerd wordt gezegd dat een entiteit zichtbaar is wanneer deze niet verborgen is.

Naamverberging treedt op wanneer omgevingen overlappen door nesten en wanneer omgevingen overlappen door overerving. De kenmerken van de twee typen verbergen worden beschreven in de volgende subclauses.

7.6.2.2 Verbergen door nesten

Naamverberging kan optreden als gevolg van het nesten van naamruimten of typen binnen naamruimten, als gevolg van typen die genest zijn binnen klassen of structs, als gevolg van een lokale functie of lambda, en als gevolg van parameter-, lokale variabele- en lokale constanten-declaraties.

Voorbeeld: In de volgende code

class A
{
    int i = 0;
    void F()
    {
        int i = 1;

        void M1()
        {
            float i = 1.0f;
            Func<double, double> doubler = (double i) => i * 2.0;
        }
    }

    void G()
    {
        i = 1;
    }
}

binnen de F methode wordt de exemplaarvariabele i verborgen door de lokale variabele i, maar binnen de G methode i verwijst deze nog steeds naar de instantievariabele. Binnen de lokale functie M1 verbergt de float i de directe buitenste i functie. De lambdaparameter i verbergt de float i binnenkant van het lambda-lichaam.

eindvoorbeeld

Wanneer een naam in een binnenbereik een naam in een buitenste bereik verbergt, worden alle overbelaste exemplaren van die naam verborgen.

Voorbeeld: In de volgende code

class Outer
{
    static void F(int i) {}
    static void F(string s) {}

    class Inner
    {
        static void F(long l) {}

        void G()
        {
            F(1); // Invokes Outer.Inner.F
            F("Hello"); // Error
        }
    }
}

De oproep F(1) roept de F gedeclareerd in Inner aan omdat alle buitenste voorkomens van F verborgen zijn door de binnenste declaratie. Om dezelfde reden resulteert de aanroep F("Hello") in een compilatietijdfout.

eindvoorbeeld

7.6.2.3 Verbergen door overname

De naam die wordt verborgen door overname, treedt op wanneer klassen of structs namen opnieuw declareren die zijn overgenomen van basisklassen. Dit type verbergen van namen neemt een van de volgende vormen aan:

  • Een constante, veld, eigenschap, gebeurtenis of type die is geïntroduceerd in een klasse, struct of interface verbergt alle leden van de basisklasse met dezelfde naam.
  • Een methode die is geïntroduceerd in een klasse, struct of interface verbergt alle niet-methode basisklasseleden met dezelfde naam en alle basisklassemethoden met dezelfde handtekening (§7.5).
  • Een indexeerfunctie die is geïntroduceerd in een klasse, struct of interface verbergt alle indexeerfuncties van het basistype met dezelfde handtekening (§7.5).

De regels voor operatordeclaraties (§15.10) maken het onmogelijk voor een afgeleide klasse om een operator met dezelfde handtekening als een operator in een basisklasse te declareren. Operators verbergen elkaar dus nooit.

In tegenstelling tot het verbergen van een naam uit een buitenste scope, zorgt het verbergen van een zichtbare naam uit een geërfde scope ervoor dat er een waarschuwing wordt gerapporteerd.

Voorbeeld: In de volgende code

class Base
{
    public void F() {}
}

class Derived : Base
{
    public void F() {} // Warning, hiding an inherited name
}

de verklaring van F in Derived veroorzaakt dat er een waarschuwing wordt gemeld. Het verbergen van een overgenomen naam is specifiek geen fout, omdat hierdoor afzonderlijke evolutie van basisklassen wordt uitgesloten. De bovenstaande situatie kan bijvoorbeeld ontstaan omdat een latere versie van het geïntroduceerde Base een F methode heeft geïntroduceerd die niet aanwezig was in een eerdere versie van de klasse.

eindvoorbeeld

De waarschuwing die wordt veroorzaakt door het verbergen van een overgenomen naam, kan worden geëlimineerd door het gebruik van de new wijzigingsfunctie:

Voorbeeld:

class Base
{
    public void F() {}
}

class Derived : Base
{
    public new void F() {}
}

De new wijzigingsfunctie geeft aan dat de F in Derived 'nieuw' is en dat het inderdaad is bedoeld om het overgenomen lid te verbergen.

eindvoorbeeld

Een declaratie van een nieuw lid verbergt een overgenomen lid alleen binnen het bereik van het nieuwe lid.

Voorbeeld:

class Base
{
    public static void F() {}
}

class Derived : Base
{
    private new static void F() {} // Hides Base.F in Derived only
}

class MoreDerived : Derived
{
    static void G()
    {
        F();                       // Invokes Base.F
    }
}

In het bovenstaande voorbeeld verbergt de declaratie van F in Derived de F die werd geërfd van Base, maar aangezien de nieuwe F in Derived privétoegang heeft, wordt het bereik niet uitgebreid naar MoreDerived. De aanroep F() in MoreDerived.G is dus geldig en zal Base.F oproepen.

eindvoorbeeld

7.7 Naamruimte en typenamen

7.7.1 Algemeen

Voor verschillende contexten in een C#-programma moet een namespace_name of een type_name worden opgegeven.

namespace_name
    : namespace_or_type_name
    ;

type_name
    : namespace_or_type_name
    ;

namespace_or_type_name
    : identifier type_argument_list? ('.' identifier type_argument_list?)*
    | qualified_alias_member ('.' identifier type_argument_list?)*
    ;

Een namespace_name is een namespace_or_type_name die verwijst naar een naamruimte.

Na resolutie zoals hieronder beschreven is, moet de namespace_or_type_name van een namespace_name verwijzen naar een naamruimte, of anders treedt er een compilatietijdfout op. Er kunnen geen typeargumenten (§8.4.2) aanwezig zijn in een namespace_name (alleen typen kunnen typeargumenten hebben).

Een type_name is een namespace_or_type_name die verwijst naar een type.

Na de hieronder beschreven resolutie, moet de namespace_or_type_name van een type_name naar een type verwijzen, anders treedt er een compilatiefout op.

Een namespace_or_type_name verwijst naar een type of een naamruimte. Oplossing voor een bepaalde naamruimte of een bepaald type omvat twee stappen op basis van het delen van de grammatica in een voorlooponderdeel, een van de grammaticafragmenten:

  • identifier type_argument_list?
  • qualified_alias_member

en een volgonderdeel, het grammaticafragment:

  • ('.' identifier type_argument_list?)*

Eerst wordt het voorlooponderdeel opgelost om de beginnende naamruimte of het type te bepalen R₀.

Als het voorloopgedeelte van de namespace_or_type_name een qualified_alias_member is, is dit R₀ de naamruimte of het type dat wordt geïdentificeerd door de oplossing zoals beschreven in §14.9.1.

Anders heeft het voorlooponderdeel, de id van het grammaticafragment type_argument_list?, een van de volgende vormen:

  • I
  • I<A₁, ..., Aₓ>

waarbij geldt:

  • I is een enkele identificator; en
  • <A₁, ..., Aₓ> is een type_argument_list; wanneer er geen type_argument_list is opgegeven, beschouw x als nul.

R₀ wordt als volgt bepaald:

  • Als x nul is en de namespace_or_type_name wordt weergegeven in een algemene methodedeclaratie (§15.6), maar buiten de kenmerken van de methode-header, en als die declaratie een typeparameter (§15.2.3) met de naam Ibevat, verwijst dan R₀ naar die typeparameter.
  • Als de namespace_or_type_name wordt weergegeven binnen een typedeclaratie, wordt voor elk exemplaartype T (§15.3.2) gestart met het exemplaartype van dat typedeclaratie en verdergaan met het exemplaartype van elke klasse, struct of interfacedeclaratie (indien van toepassing):
    • Als x nul is en de declaratie van T een typeparameter met de naam Ibevat, R₀ verwijst u naar die typeparameter.
    • Als de namespace_or_type_name wordt weergegeven in de hoofdtekst van de typedeclaratie en T of een van de basistypen een genest toegankelijk type met naam I en x typeparameters bevat, verwijst u dan R₀ naar dat type dat is samengesteld met de opgegeven typeargumenten. Als er meer dan één dergelijk type is, wordt het type geselecteerd dat binnen het meer afgeleide type is gedeclareerd. Het is een compilerfout als er geen type meer is afgeleid dan alle andere.

      Opmerking: niet-type leden (constanten, velden, methoden, eigenschappen, indexeerfuncties, operators, instantieconstructors, finalizers en statische constructors) en typeleden met een ander aantal typeparameters worden genegeerd bij het bepalen van de betekenis van de namespace_or_type_name. eindnotitie

    • Anders, voor elke naamruimte N, beginnend met de naamruimte waarin de namespace_or_type_name voorkomt, vervolgens verder te gaan met elke omsluitende naamruimte (indien van toepassing) en eindigend met de globale naamruimte, worden de volgende stappen geëvalueerd totdat een entiteit wordt gevonden.
      • Als x nul is en I de naam van een naamruimte in Nis, gaat u als volgt te werk:
        • Als de locatie waar de namespace_or_type_name plaatsvindt, wordt ingesloten door een naamruimtedeclaratie voor N en de naamruimtedeclaratie een extern_alias_directive of using_alias_directive bevat die de naam I koppelt aan een naamruimte of type, of een naamruimtedeclaratie voor N in het programma een global_using_alias_directive bevat die de naam I koppelt aan een naamruimte of type, dan is de namespace_or_type_name niet eenduidig en treedt er een compilatietijdfout op.
        • R₀ Anders verwijst u naar de naamruimte met de naam I in N.
      • Anders, als N een toegankelijk type met de naam I en x typeparameters bevat, dan:
        • Als x nul is en de locatie waar het namespace_or_type_name plaatsvindt, wordt ingesloten door een naamruimtedeclaratie voor N en bevat de naamruimtedeclaratie een extern_alias_directive of using_alias_directive waarmee de naam I wordt gekoppeld aan een naamruimte of type, of een naamruimtedeclaratie voor N in het programma een global_using_alias_directive die de naam I koppelt aan een naamruimte of type, dan is de namespace_or_type_name dubbelzinnig en treedt er een compileertijdfout op.
        • R₀ Anders verwijst u naar het type dat is samengesteld met de opgegeven typeargumenten.
      • Anders, als de locatie waar de namespace_or_type_name zich voordoet, wordt omsloten door een naamruimtedeclaratie voor N:
        • Als x nul is en de naamruimtedeclaratie een extern_alias_directive of using_alias_directive bevat die de naam I koppelt aan een geïmporteerde naamruimte of -type, of een naamruimtedeclaratie voor N in het programma bevat een global_using_alias_directive die de naam I koppelt aan een geïmporteerde naamruimte of -type, verwijst dan R₀ naar die naamruimte of het type.
        • Als de naamruimten die zijn geïmporteerd door de using_namespace_directives van de naamruimtedeclaratie en de naamruimten en typedeclaraties die zijn geïmporteerd door de global_using_namespace_directives en global_using_static_directivevan een naamruimtedeclaratie N voor in het programma exact één type met naam I - en x typeparameters bevatten, verwijst dan R₀ naar dat type dat is samengesteld met de opgegeven typeargumenten.
        • Anders, als de naamruimten die zijn geïmporteerd door de using_namespace_directives van de naamruimtedeclaratie en de naamruimten en typedeclaraties die zijn geïmporteerd door de global_using_namespace_directives en global_using_static_directivevan een naamruimtedeclaratie voor N in het programma meer dan één type met naam I - en x typeparameters bevatten, is de namespace_or_type_name dubbelzinnig en treedt er een compilatiefout op.
    • Anders is de namespace_or_type_name niet gedefinieerd en treedt er een compilatietijdfout op.

Als R₀ succesvol is opgelost, wordt het achterste deel van de namespace_or_type_name opgelost. Het afsluitende grammaticafragment bestaat uit k ≥ 0 herhalingen, waarbij elke herhaling de naamruimte of het type waarnaar wordt verwezen verder oplost.

Als k nul is, is er dus geen volggedeelte, dan wordt de namespace_or_type_name omgezet in R₀.

Anders heeft elke herhaling een van de vormen:

  • .I
  • .I<A₁, ..., Aₓ>

waarbij I, A en x zijn gedefinieerd zoals hierboven.

Voor elke herhaling n, waarbij 1 ≤ n ≤ k, wordt de resolutie Rₙ bepaald. Dit omvat Rₚ, waarbij p = n - 1, de resolutie van de voorgaande herhaling, wordt vastgesteld als volgt:

  • Als x nul is en Rₚ verwijst naar een naamruimte en Rₚ een geneste naamruimte met een naam Ibevat, verwijst u naar Rₙ die geneste naamruimte.
  • Anders, indien Rₚ verwijst naar een naamruimte en Rₚ een toegankelijk type bevat met de naam I en typeparameters x, dan verwijst Rₙ naar dat type, samengesteld met de opgegeven typeargumenten.
  • Rₚ Als dit niet verwijst naar een (mogelijk samengestelde) klasse, struct of interfacetype en Rₚ of een van de basistypen een genest toegankelijk type met naam I en x typeparameters, verwijst dan Rₙ naar dat type dat is samengesteld met de opgegeven typeargumenten. Als er meer dan één dergelijk type is, wordt het type geselecteerd dat binnen het meer afgeleide type is gedeclareerd. Het is een compilerfout als er geen type meer is afgeleid dan alle andere.

    Opmerking: Als de betekenis van T.I, voor een bepaald type T, wordt bepaald als onderdeel van het omzetten van de basisklassespecificatie van T dan wordt de directe basisklasse beschouwd T als object (§15.2.4.2). eindnotitie

  • Anders is de namespace_or_type_name ongeldig en treedt er een compilatiefout op.

De resolutie van de namespace_or_type_name is de resolutie van de laatste herhaling, Rₖ.

Een namespace_or_type_name mag alleen verwijzen naar een statische klasse (§15.2.2.4) 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_name is de T in een typeof_expression (§12.8.18) van de vorm typeof(T)

Voorbeeld:

interface A
{
    class NestedClass { public static void M() {} }
}

interface B
{
    class NestedClass { public static void M() {} }
}

interface C : A, B
{
    public void Test() { NestedClass.M(); } // ambiguity between
                                            // A.NestedClass and B.NestedClass
}

In het bovenstaande voorbeeld is de aanroep naar binnen NestedClass.M()C.Test() dubbelzinnig tussen B.NestedClass.M() en A.NestedClass.M() omdat geen van beide meer is afgeleid dan de andere. Een expliciete verwijzing naar een van A.NestedClass.M() beide of B.NestedClass.M() is vereist om de dubbelzinnigheid op te lossen.

eindvoorbeeld

7.7.2 Niet-gekwalificeerde namen

Elke naamruimtedeclaratie en typedeclaratie heeft een niet-gekwalificeerde naam die als volgt wordt bepaald:

  • Voor een naamruimtedeclaratie is de niet-gekwalificeerde naam de qualified_identifier die is opgegeven in de declaratie.
  • Voor een typedeclaratie zonder type_parameter_list is de niet-gekwalificeerde naam de id die is opgegeven in de declaratie.
  • Voor een typedeclaratie met K-typeparameters is de niet-gekwalificeerde naam de identifier die is opgegeven in de declaratie, gevolgd door de generieke_afmetingspecificatie (§12.8.18) voor K-typeparameters.

7.7.3 Volledig gekwalificeerde namen

Elke naamruimte en typedeclaratie hebben een volledig gekwalificeerde naam, die de naamruimte of typedeclaratie uniek identificeert onder alle andere binnen het programma. De volledig gekwalificeerde naam van een naamruimte of typedeclaratie met niet-gekwalificeerde naam N wordt als volgt bepaald:

  • Als N een lid is van de globale naamruimte, is de volledig gekwalificeerde naam N.
  • Anders is S.Nde volledig gekwalificeerde naam , waar S de volledig gekwalificeerde naam van de naamruimte of typedeclaratie waarin N wordt gedeclareerd.

Met andere woorden, de volledig gekwalificeerde naam van N is het volledige hiërarchische pad van identifiers en generic_dimension_specifiers die leiden tot N, beginnend vanuit de globale naamruimte. Omdat elk lid van een naamruimte of type een unieke naam heeft, is de volledig gekwalificeerde naam van een naamruimte of typedeclaratie altijd uniek. Het is een compilatiefout voor dezelfde volledig gekwalificeerde naam om te verwijzen naar twee afzonderlijke entiteiten. Met name:

  • Het is een fout voor zowel een naamruimtedeclaratie als een typedeclaratie om dezelfde volledig gekwalificeerde naam te hebben.
  • Het is een fout voor twee verschillende typen declaraties om dezelfde volledig gekwalificeerde naam te hebben (bijvoorbeeld als zowel een struct- als klassedeclaratie dezelfde volledig gekwalificeerde naam hebben).
  • Het is een fout als een typedeclaratie zonder partial-modifier dezelfde volledig gekwalificeerde naam heeft als een andere typedeclaratie (§15.2.7).

Voorbeeld: In het onderstaande voorbeeld ziet u verschillende naamruimte- en typedeclaraties, samen met de bijbehorende volledig gekwalificeerde namen.

class A {}                 // A
namespace X                // X
{
    class B                // X.B
    {
        class C {}         // X.B.C
    }
    namespace Y            // X.Y
    {
        class D {}         // X.Y.D
    }
}
namespace X.Y              // X.Y
{
    class E {}             // X.Y.E
    class G<T>             // X.Y.G<>
    {
        class H {}         // X.Y.G<>.H
    }
    class G<S,T>           // X.Y.G<,>
    {
        class H<U> {}      // X.Y.G<,>.H<>
    }
}

eindvoorbeeld

7.8 Automatisch geheugenbeheer

C# maakt gebruik van automatisch geheugenbeheer, waardoor ontwikkelaars het geheugen dat door objecten wordt bezet handmatig kunnen toewijzen en vrijmaken. Beleid voor automatisch geheugenbeheer wordt geïmplementeerd door een vuilnisophaler. De levenscyclus van een object voor geheugenbeheer is als volgt:

  1. Wanneer het object wordt gemaakt, wordt er geheugen toegewezen, wordt de constructor uitgevoerd en wordt het object beschouwd als live.
  2. Als noch het object, noch een van zijn exemplaarvelden toegankelijk is door een mogelijke voortzetting van de uitvoering, met uitzondering van het uitvoeren van finalizers, wordt het object beschouwd als niet meer in gebruik en komt het in aanmerking voor finalisatie.

    Opmerking: De C#-compiler en de garbagecollector kunnen ervoor kiezen om code te analyseren om te bepalen welke verwijzingen naar een object in de toekomst kunnen worden gebruikt. Als een lokale variabele die binnen het bereik valt bijvoorbeeld de enige bestaande verwijzing naar een object is, maar die lokale variabele nooit wordt verwezen in een mogelijke voortzetting van de uitvoering vanaf het huidige uitvoeringspunt in de procedure, kan de garbagecollector het object behandelen als niet meer in gebruik. eindnotitie

  3. Zodra het object in aanmerking komt voor finalisatie, wordt op een later tijdstip de finalizer (§15.13) voor het object uitgevoerd, indien van toepassing. Onder normale omstandigheden wordt de finalizer voor het object slechts één keer uitgevoerd, hoewel implementatiegedefinieerde API's dit gedrag kunnen toestaan om te worden overschreven.
  4. Zodra de finalizer voor een object wordt uitgevoerd, wordt het object, als het object of een van zijn exemplaarvelden niet toegankelijk is door een mogelijke voortzetting van de uitvoering, met inbegrip van het uitvoeren van finalizers, als ontoegankelijk beschouwd en komt het in aanmerking voor verzameling.

    Opmerking: Een object dat eerder niet kon worden geopend, is mogelijk opnieuw toegankelijk vanwege de finalizer. Hieronder ziet u een voorbeeld hiervan. eindnotitie

  5. Ten slotte, op een bepaald moment nadat het object in aanmerking komt voor verzameling, maakt de garbagecollector het geheugen vrij dat aan dat object is gekoppeld.

De garbagecollector onderhoudt informatie over het gebruik van objecten en gebruikt deze informatie om beslissingen te nemen over geheugenbeheer, zoals waar in het geheugen een nieuw gemaakt object moet worden gevonden, wanneer een object moet worden verplaatst en wanneer een object niet meer wordt gebruikt of niet meer toegankelijk is.

Net als andere talen die ervan uitgaan dat er een garbagecollector bestaat, is C# zo ontworpen dat de garbagecollection een breed scala aan beleidsregels voor geheugenbeheer kan implementeren. C# specificeert geen tijdsbeperking binnen die periode, noch een volgorde waarin finalizers worden uitgevoerd. Of finalizers al dan niet worden uitgevoerd als onderdeel van de beëindiging van de toepassing, is door de implementatie gedefinieerd (§7.1).

Het gedrag van de garbagecollector kan in zekere mate worden beheerd via statische methoden in de klasse System.GC. Deze klasse kan worden gebruikt om te verzoeken dat er een verzameling plaatsvindt, dat finalizers worden uitgevoerd (of niet worden uitgevoerd), enzovoort.

Voorbeeld: Omdat de garbagecollector brede breedtegraad heeft bij het bepalen wanneer objecten moeten worden verzameld en finalizers moeten worden uitgevoerd, kan een conforme implementatie uitvoer produceren die verschilt van de uitvoer die wordt weergegeven door de volgende code. Het programma

class A
{
    ~A()
    {
        Console.WriteLine("Finalize instance of A");
    }
}

class B
{
    object Ref;
    public B(object o)
    {
        Ref = o;
    }

    ~B()
    {
        Console.WriteLine("Finalize instance of B");
    }
}

class Test
{
    static void Main()
    {
        B b = new B(new A());
        b = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
}

maakt een exemplaar van klasse A en een exemplaar van klasse B. Deze objecten komen in aanmerking voor garbagecollection wanneer de variabele b de waarde nullwordt toegewezen, omdat het na deze tijd onmogelijk is voor elke door de gebruiker geschreven code om ze te openen. De uitvoer kan een van beide zijn

Finalize instance of A
Finalize instance of B

of

Finalize instance of B
Finalize instance of A

omdat de taal geen beperkingen oplegt voor de volgorde waarin objecten afval worden verzameld.

In subtiele gevallen kan het onderscheid tussen 'in aanmerking komen voor finalisatie' en 'in aanmerking komen voor verzameling' belangrijk zijn. Bijvoorbeeld:

class A
{
    ~A()
    {
        Console.WriteLine("Finalize instance of A");
    }

    public void F()
    {
        Console.WriteLine("A.F");
        Test.RefA = this;
    }
}

class B
{
    public A Ref;

    ~B()
    {
        Console.WriteLine("Finalize instance of B");
        Ref.F();
    }
}

class Test
{
    public static A RefA;
    public static B RefB;

    static void Main()
    {
        RefB = new B();
        RefA = new A();
        RefB.Ref = RefA;
        RefB = null;
        RefA = null;
        // A and B now eligible for finalization
        GC.Collect();
        GC.WaitForPendingFinalizers();
        // B now eligible for collection, but A is not
        if (RefA != null)
        {
            Console.WriteLine("RefA is not null");
        }
    }
}

In het bovenstaande programma, als de garbagecollector ervoor kiest om de finalizer van A vóór de finalizer van Buit te voeren, kan de uitvoer van dit programma zijn:

Finalize instance of A
Finalize instance of B
A.F
RefA is not null

Merk op dat hoewel het exemplaar van A niet in gebruik was en de finalizer van A werd uitgevoerd, het nog steeds mogelijk is om methoden van A (in dit geval F) aan te roepen vanuit een andere finalizer. Houd er ook rekening mee dat het uitvoeren van een finalizer ertoe kan leiden dat een object opnieuw bruikbaar wordt vanuit het mainlineprogramma. In dit geval heeft het uitvoeren van Bde finalizer ertoe geleid dat een exemplaar van A die eerder niet in gebruik was, toegankelijk werd vanuit de live reference Test.RefA. Na de aanroep naar WaitForPendingFinalizers, komt het exemplaar van B in aanmerking voor verzameling, maar het exemplaar A van is niet, vanwege de verwijzing Test.RefA.

eindvoorbeeld

7.9 Uitvoeringsvolgorde

De uitvoering van een C#-programma verloopt zodanig dat de bijwerkingen van elke uitvoeringsthread behouden blijven op kritieke uitvoeringspunten. Een neveneffect wordt gedefinieerd als een lees- of schrijfbewerking van een vluchtig veld, een schrijfbewerking naar een niet-vluchtige variabele, een schrijfbewerking naar een externe resource en het genereren van een uitzondering. De kritieke uitvoeringspunten waarop de volgorde van deze bijwerkingen behouden blijft, zijn verwijzingen naar vluchtige velden (§15.5.4), lock instructies (§13.13) en het maken en beëindigen van threads. De uitvoeringsomgeving is gratis om de volgorde van uitvoering van een C#-programma te wijzigen, afhankelijk van de volgende beperkingen:

  • De gegevensafhankelijkheid blijft behouden binnen een uitvoeringsdraad. Dat wil gezegd, de waarde van elke variabele wordt berekend alsof alle instructies in de thread in de oorspronkelijke programmavolgorde zijn uitgevoerd.
  • Regels voor initialisatievolgorde blijven behouden (§15.5.5, §15.5.6).
  • De volgorde van bijwerkingen blijft behouden met betrekking tot vluchtige lees- en schrijfbewerkingen (§15.5.4). Daarnaast hoeft de uitvoeringsomgeving geen deel van een expressie te evalueren als deze kan afleiden dat de waarde van die expressie niet wordt gebruikt en dat er geen benodigde bijwerkingen worden geproduceerd (inclusief eventuele oorzaken die worden veroorzaakt door het aanroepen van een methode of het openen van een vluchtig veld). Wanneer de uitvoering van het programma wordt onderbroken door een asynchrone gebeurtenis (zoals een uitzondering die door een andere thread wordt gegenereerd), is het niet gegarandeerd dat de waarneembare bijwerkingen zichtbaar zijn in de oorspronkelijke programmavolgorde.