Delen via


7 Basisconcepten

7.1 Toepassing opstarten

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.

Een programma dat als aanvraag is gecompileerd, moet ten minste één methode bevatten die als ingangspunt in aanmerking komt door aan de volgende vereisten te voldoen:

  • Zij heeft de naam Main.
  • Dat is het geval 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 kan de async wijzigingsfunctie hebben opgegeven als het retourtype van de methode is System.Threading.Tasks.Task of System.Threading.Tasks.Task<int>.
  • Het retourtype moet void, intof System.Threading.Tasks.TaskSystem.Threading.Tasks.Task<int>.
  • Het mag geen gedeeltelijke methode zijn (§15.6.9) zonder uitvoering.
  • De parameterlijst moet leeg zijn of één waardeparameter van het type string[]hebben.

Opmerking: methoden met de async modifier moeten exact een van de twee hierboven opgegeven retourtypen hebben om in aanmerking te komen als toegangspunt. Een async void methode of een async methode die een ander wachtbaar type retourneert, zoals ValueTask of ValueTask<int> komt niet in aanmerking als toegangspunt. eindnotitie

Als meer dan één methode die in aanmerking komt als een toegangspunt binnen een programma wordt gedeclareerd, kan een extern mechanisme worden gebruikt om aan te geven welke methode als het werkelijke toegangspunt voor de toepassing wordt beschouwd. Als een in aanmerking komende methode met een retourtype van int of void wordt gevonden, wordt een in aanmerking komende methode met een retourtype System.Threading.Tasks.Task of System.Threading.Tasks.Task<int> niet beschouwd als een invoerpuntmethode. Het is een compilatiefout voor een programma dat als een toepassing wordt gecompileerd zonder precies één toegangspunt. Een programma dat is gecompileerd als een klassebibliotheek kan methoden bevatten die in aanmerking komen als toepassingsinvoerpunten, maar de resulterende bibliotheek heeft geen toegangspunt.

Normaal gesproken wordt de gedeclareerde toegankelijkheid (§7.5.2) van een methode bepaald door de toegangsmodifiers (§15.3.6) die zijn opgegeven in de declaratie, en op dezelfde manier wordt de toegankelijkheid van een type bepaald door de toegangsmodifiers die zijn opgegeven in de declaratie. Om een bepaalde methode van een bepaald type aan te roepen, moeten zowel het type als het lid toegankelijk zijn. Het ingangspunt van de toepassing is echter een speciaal geval. In het bijzonder heeft de uitvoeringsomgeving toegang tot het toegangspunt van de toepassing, ongeacht de gedeclareerde toegankelijkheid en ongeacht de gedeclareerde toegankelijkheid van de declaraties van het bijbehorende type.

Wanneer de invoerpuntmethode een retourtype van System.Threading.Tasks.Task of System.Threading.Tasks.Task<int>heeft,synthetiseert de compiler een synchrone invoerpuntmethode die de bijbehorende Main methode aanroept. De gesynthetiseerde methode heeft parameters en retourtypen op basis van de Main methode:

  • De parameterlijst van de gesynthetiseerde methode is hetzelfde als de parameterlijst van de Main methode
  • Als het retourtype van de Main methode is System.Threading.Tasks.Task, is het retourtype van de gesynthetiseerde methode void
  • Als het retourtype van de Main methode is System.Threading.Tasks.Task<int>, is het retourtype van de gesynthetiseerde methode int

Uitvoering van de gesynthetiseerde methode gaat als volgt:

  • De gesynthetiseerde methode roept de methode aan Main en geeft de string[] parameterwaarde door als argument als de Main methode een dergelijke parameter heeft.
  • Als de Main methode een uitzondering genereert, wordt de uitzondering doorgegeven door de gesynthetiseerde methode.
  • Anders wacht het gesynthetiseerde invoerpunt totdat de geretourneerde taak is voltooid, waarbij de taak wordt aangeroepen GetAwaiter().GetResult() , met behulp van de methode zonder parameterloze instantie of de extensiemethode die wordt beschreven in §C.3. Als de taak mislukt, GetResult() genereert u een uitzondering en wordt deze uitzondering doorgegeven door de gesynthetiseerde methode.
  • Voor een Main methode met een retourtype , System.Threading.Tasks.Task<int>als de taak is voltooid, wordt de int geretourneerde waarde GetResult() geretourneerd door de gesynthetiseerde methode.

Het effectieve toegangspunt van een toepassing is het ingangspunt dat in het programma is gedeclareerd, of de gesynthetiseerde methode als deze is vereist, zoals hierboven beschreven. Het retourtype van het effectieve ingangspunt is daarom altijd void of int.

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 zijn gratis om implementatiebeleid of mechanismen te bieden voor het maken en vernietigen van toepassingsdomeinen.

Het opstarten van de toepassing vindt plaats wanneer de uitvoeringsomgeving het effectieve toegangspunt van de toepassing aanroept. Als het effectieve toegangspunt een parameter declareert, zorgt de implementatie er tijdens het opstarten van de toepassing voor dat de initiële waarde van die parameter een niet-null-verwijzing naar een tekenreeksmatrix is. Deze matrix bestaat uit niet-null-verwijzingen naar tekenreeksen, toepassingsparameters genoemd, die door de implementatie gedefinieerde waarden krijgen door de hostomgeving voordat de toepassing wordt opgestart. De bedoeling is om de toepassingsgegevens op te geven die vóór het opstarten van de toepassing zijn bepaald vanaf een andere locatie in de gehoste omgeving.

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

Als het retourtype van het effectieve toegangspunt is int, wordt de retourwaarde van de methode-aanroep door de uitvoeringsomgeving gebruikt bij het beëindigen van de toepassing (§7.2).

Afgezien van de bovenstaande situaties gedragen de invoerpuntmethoden zich als methoden die geen toegangspunten zijn in elk opzicht. Met name als het toegangspunt wordt aangeroepen op een ander punt tijdens de levensduur van de toepassing, zoals bij regelmatige aanroep van methoden, is er geen speciale verwerking van de methode: als er een parameter is, kan deze een initiële waarde hebben van null, of een niet-waardenull die verwijst naar een matrix die null-verwijzingen bevat. Op dezelfde manier heeft de retourwaarde van het toegangspunt geen speciale betekenis dan in de aanroep vanuit de uitvoeringsomgeving.

7.2 Beëindiging van toepassing

Toepassingsbeëindiging retourneert controle over de uitvoeringsomgeving.

Als het retourtype van de effectieve invoerpuntmethode van de toepassing is en de uitvoering wordt int voltooid zonder dat er een uitzondering ontstaat, dient de waarde van de int geretourneerde waarde als de beëindigingsstatuscode van de toepassing. Het doel van deze code is om communicatie van succes of mislukking van de uitvoeringsomgeving mogelijk te maken. Als het retourtype van de effectieve invoerpuntmethode is en de uitvoering is void voltooid zonder dat er een uitzondering optreedt, is 0de beëindigingsstatuscode.

Als de effectieve ingangsmethode wordt beëindigd vanwege een uitzondering (§21.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.

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

7.3 Declaraties

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.7) worden gebruikt voor het definiëren van klassen (§15), structs (§16), interfaces (§18), enums (§19) en gemachtigden (§20). 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.6).
  • Twee of meer typedeclaraties met dezelfde naam, maar afzonderlijke getallen van typeparameters zijn toegestaan in dezelfde declaratieruimte (§7.8.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.8.2).

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

  • Binnen alle compilatie-eenheden van een programma zijn namespace_member_declarationzonder omsluiten namespace_declaration lid zijn van één gecombineerde declaratieruimte genaamd de globale declaratieruimte.
  • Binnen alle compilatie-eenheden van een programma zijn namespace_member_declarationbinnen namespace_declarations met dezelfde volledig gekwalificeerde naamruimtenaam lid van één gecombineerde declaratieruimte.
  • 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.5.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_declarationof type_parameters. Behalve voor overbelaste instantieconstructordeclaraties en statische constructordeclaraties kan een klasse of struct geen liddeclaratie bevatten met dezelfde naam als de klasse of struct. 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.6). 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 geïntroduceerd via parameters (fixed_parameters en parameter_arrays) en type_parameters.
  • Elke opsommingsdeclaratie maakt een nieuwe declaratieruimte. Namen worden in deze declaratieruimte ingevoerd via enum_member_declarations.
  • Elke methodedeclaratie, eigenschapsdeclaratie, declaratie van eigenschapstoegangsfunctie, declaratie van indexeerfunctie, declaratie van de indexeerfunctie, declaratie van instantieconstructor, anonieme functie en lokale functie maakt een nieuwe declaratieruimte genaamd een lokale variabele declaratieruimte. Namen worden in deze declaratieruimte geïntroduceerd via parameters (fixed_parameters en parameter_arrays) en type_parameters. De set accessor voor een eigenschap of een indexeerfunctie introduceert de naam value als een 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.7.1) door de geneste lokale naam.
  • 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 declaratieruimten voor variabelen kunnen genest zijn, maar het is een fout voor een lokale ruimte voor declaratie van variabelen en een geneste ruimte voor lokale variabelendeclaratie die elementen met dezelfde naam bevat. 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.20.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 bevat geneste 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 moeten worden gedefinieerd voordat ze worden gebruikt (§7.7).
  • De declaratievolgorde voor enumliddeclaraties (§19.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 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.4 Leden

7.4.1 Algemeen

Naamruimten en typen hebben leden.

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.5.2). eindnotitie

7.4.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.4.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.4.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.4.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.4.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 (§18.4). De leden in de klas object zijn echter beschikbaar via het opzoeken van leden in elk interfacetype (§12.5). eindnotitie

7.4.7 Matrixleden

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

7.4.8 Gedelegeerden

Een gemachtigde neemt leden over van de klasse System.Delegate. Daarnaast bevat het een methode met Invoke dezelfde retourtype en parameterlijst die is opgegeven in de declaratie (§20.2). Een aanroep van deze methode gedraagt zich identiek aan een gemachtigde aanroep (§20.6) op dezelfde gemachtigde instantie.

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

7.5 Toegang tot leden

7.5.1 Algemeen

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

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.5.3) van het lid.

7.5.2 Opgegeven toegankelijkheid

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

  • Openbaar, dat is geselecteerd door een public wijzigingsfunctie op te neemt in de liddeclaratie. De intuïtieve betekenis van public is 'toegang niet beperkt'.
  • Beveiligd, dat is geselecteerd door een protected wijzigingsfunctie op te geven in de liddeclaratie. De intuïtieve betekenis van protected is "toegang beperkt tot de betreffende klasse of typen die zijn afgeleid van de betreffende klasse".
  • Intern, dat wordt geselecteerd door een internal wijzigingsfunctie op te neemt in de liddeclaratie. De intuïtieve betekenis van internal is "toegang beperkt tot deze assembly".
  • Beveiligd intern, dat wordt geselecteerd door zowel een protected als een internal wijzigingsfunctie op te geven 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ébeveiliging, die wordt geselecteerd door zowel een private als een protected wijzigingsfunctie in de liddeclaratie 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 wijzigingsfunctie op te neemt 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 toegankelijkheid hebben public of internal gedeclareerd en standaard internal toegankelijkheid hebben gedeclareerd.
  • Klasleden kunnen elk van de toegestane soorten toegankelijkheid hebben en standaard private toegankelijkheid hebben gedeclareerd.

    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 toegankelijkheid of toegankelijkheid hebben publicinternalof private als standaard private toegankelijkheid hebben gedeclareerd, omdat structs impliciet zijn verzegeld. Struct-leden die zijn geïntroduceerd in een struct (dat wil gezegd, niet overgenomen door die struct) kunnen geen , protected internalof private protected gedeclareerde toegankelijkheid hebbenprotected.

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

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

7.5.3 Toegankelijkheidsdomeinen

Het toegankelijkheidsdomein van een lid bestaat uit de (mogelijk niet-aaneengesloten) secties van programmatekst waarin toegang tot het lid is toegestaan. Voor de doeleinden van het definiëren van het toegankelijkheidsdomein van een lid wordt gezegd dat een lid het hoogste niveau is als het niet binnen een type wordt gedeclareerd en een lid wordt geneste 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 M is public, is het toegankelijkheidsdomein M Tvan .
  • Als de toegankelijkheid M is protected internalgedeclareerd, laat u D de samenvoeging zijn van de programmatekst van P en de programmatekst van elk type dat is afgeleid van T, die buiten Pis 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.
  • Als dat het ispublic, M is de toegang toegestaan.
  • M Als dat niet het geval isprotected internal, 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.5.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.5.4).
  • Als dat niet het geval isinternal, M is de toegang toegestaan als deze plaatsvindt in het programma waarin M wordt gedeclareerd.
  • Als dat niet het geval isprivate, M is de toegang toegestaan als deze plaatsvindt 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.Cen B.C.Xis B.C.Y de programmatekst van het betreffende 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 gedeclareerd, hebben ze X A.X allemaal toegankelijkheidsdomeinen die worden beperkt door een type.

eindvoorbeeld

Zoals beschreven in §7.4, 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. De toegang tot b.x de A.F methode slaagt dus, maar mislukt in de B.F methode.

eindvoorbeeld

7.5.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 is de toegang vereist om te worden uitgevoerd via een exemplaar van dat afgeleide klassetype of een klassetype dat ermee 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.

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

  • Een niet-gekwalificeerde type_name of primary_expression van het formulier M.
  • Een primary_expression van het formulier E.M, mits het type E is of een klasse die is T afgeleid van T, waar T de klasse Dis of een klassetype dat is samengesteld uit D.
  • Een primary_expression van het formulier base.M.
  • Een primary_expression van het formulierbase[ argument_list].

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 is Bhet echter niet mogelijk om toegang te krijgen x via een exemplaar van A, omdat A het niet is afgeleid 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 x toegestaan omdat ze allemaal plaatsvinden via exemplaren van klassetypen die zijn samengesteld vanuit het algemene type.

eindvoorbeeld

Opmerking: Het toegankelijkheidsdomein (§7.5.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 is D geldig, ook al is de klasse D afgeleid van C<string>. eindnotitie

7.5.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 in T alle contexten waarin ze M toegankelijk zijn.

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 gemachtigde zijn ten minste zo toegankelijk als het type gemachtigde 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 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 exploitant 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 resulteert in een compilatiefout omdat A deze niet ten minste 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.6 Handtekeningen en overbelasting

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

  • 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, parameternamen, parameterbeperkingen, parameterbeperkingen params of parameteraanpassingen, this of parameters zijn vereist of optioneel.
  • 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, noch bevat het de params wijzigingsfunctie die kan worden opgegeven voor de meest rechtse parameter, 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 het resultaattype.
  • 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.
  • 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 overbelasting van exemplaarconstructors kan een klasse of struct meerdere exemplaarconstructors declareren, mits hun handtekeningen 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 in, outen ref parameteraanpassingen als onderdeel van een handtekening worden beschouwd, kunnen leden die in één type zijn gedeclareerd, niet alleen verschillen in handtekening door in, outen ref. Er treedt een compilatiefout op als twee leden zijn gedeclareerd in hetzelfde type met handtekeningen die hetzelfde zijn als alle parameters in beide methoden met out of in modifiers zijn gewijzigd in ref modifiers. Voor andere doeleinden van handtekeningkoppeling (bijvoorbeeld verbergen of overschrijven), in, outen ref worden ze 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 de Common Language Infrastructure (CLI), die geen manier biedt om methoden te definiëren die alleen verschillen in in, outen ref. 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 te object dynamic vervangen door 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(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 alle inout, en ref parametermodifiers (§15.6.2) deel uitmaken van een handtekening. Dus, F(int), F(in int), F(out int) en F(ref int) zijn allemaal unieke handtekeningen. Maar F(in int)F(out int) , en F(ref int) kan niet binnen dezelfde interface worden gedeclareerd omdat hun handtekeningen alleen verschillen van in, outen ref. 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.7 Bereiken

7.7.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.3 dat in een geneste blok het 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.

  • Het bereik van een naamruimtelid dat is gedeclareerd door een namespace_member_declaration (§14.6) zonder namespace_declaration is de volledige programmatekst.

  • 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.

  • Het bereik van een naam die is gedefinieerd door een extern_alias_directive (§14.4) strekt zich uit over de using_directives, global_attributes en namespace_member_declarationvan 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 of namespace_body waarin deze plaatsvindt.

  • Het bereik van een naam die is gedefinieerd of geïmporteerd door een using_directive (§14.5) strekt zich uit over de global_attributes 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_clauses en 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_clauseen struct_body van die struct_declaration.

  • Het bereik van een typeparameter die is gedeclareerd door een type_parameter_list op een interface_declaration (§18.2) is de interface_base, type_parameter_constraints_clauseen interface_body van die interface_declaration.

  • Het bereik van een typeparameter die is gedeclareerd door een type_parameter_list op een delegate_declaration (§20.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.

  • Het bereik van een lid dat is gedeclareerd door een class_member_declaration (§15.3.1) is het class_body waarin de aangifte plaatsvindt. Daarnaast is het bereik van een klasselid uitgebreid tot de class_body van die afgeleide klassen die zijn opgenomen in het toegankelijkheidsdomein (§7.5.3) van het lid.

  • Het bereik van een lid dat is gedeclareerd door een struct_member_declaration (§16.3) is de struct_body waarin de verklaring plaatsvindt.

  • Het bereik van een lid dat door een enum_member_declaration (§19.4) is gedeclareerd, 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.

  • 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 constructor_declaration (§15.11) is het constructor_initializer en blok van dat constructor_declaration.

  • Het bereik van een parameter die is gedeclareerd in een lambda_expression (§12.19) is de lambda_expression_body van die lambda_expression.

  • Het bereik van een parameter die is gedeclareerd in een anonymous_method_expression (§12.19) is het blok van dat anonymous_method_expression.

  • 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) is 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 voorafgaat aan de constant_declarator.

  • 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 die verwijst naar de lokale variabele in een tekstuele positie die voorafgaat aan de declaratie.

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 F bovenstaande methode verwijst de eerste toewijzing naar i specifiek niet 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 geldig j omdat het gebruik niet voorafgaat aan de declaratie. 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

7.7.2 Naam verbergen

7.7.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 bereiken overlappen door nesten en wanneer bereiken overlappen door overname. De kenmerken van de twee typen verbergen worden beschreven in de volgende subclauses.

7.7.2.2 Verbergen door nesten

Naam verbergen via nesten kan optreden als gevolg van geneste naamruimten of typen in naamruimten, als gevolg van nesttypen binnen klassen of structs, als gevolg van een lokale functie of lambda, en als gevolg van parameter-, lokale variabele en lokale constantedeclaraties.

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 directe buitenste ifunctie. 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 aanroep F(1) roept de F gedeclareerde aan Inner omdat alle buitenste instanties F verborgen zijn door de binnenste declaratie. Om dezelfde reden resulteert de aanroep F("Hello") in een compilatietijdfout.

eindvoorbeeld

7.7.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 naam verbergen heeft een van de volgende formulieren:

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

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 buitenbereik, zorgt het verbergen van een zichtbare naam van een overgenomen bereik 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 declaratie van F in Derived het geval van 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 er een latere versie van Base een methode is geïntroduceerd F 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 inDerived, maar omdat de nieuwe F versie Derived privétoegang heeft, wordt het bereik niet uitgebreid naar MoreDerived.F Base De aanroep F() MoreDerived.G is dus geldig en wordt aangeroepen Base.F.

eindvoorbeeld

7.8 Naamruimte en typenamen

7.8.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?
    | namespace_or_type_name '.' identifier type_argument_list?
    | qualified_alias_member
    ;

Een namespace_name is een namespace_or_type_name die verwijst naar een naamruimte.

De volgende oplossing zoals hieronder beschreven, verwijst de namespace_or_type_name van een namespace_name 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. De volgende oplossing zoals hieronder beschreven, verwijst de namespace_or_type_name van een type_name naar een type, of op een andere manier treedt er een compilatiefout op.

Als de namespace_or_type_name een qualified_alias_member de betekenis ervan is zoals beschreven in §14.8.1. Anders heeft een namespace_or_type_name een van de vier vormen:

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

waarbij I één id is, N een namespace_or_type_name is en <A₁, ..., Aₓ> een optionele type_argument_list is. Wanneer er geen type_argument_list is opgegeven, kunt u overwegen x nul te zijn.

De betekenis van een namespace_or_type_name wordt als volgt bepaald:

  • Als de namespace_or_type_name een qualified_alias_member is, is de betekenis zoals opgegeven in §14.8.1.
  • Anders, als de namespace_or_type_name van het formulier I of van het formulier I<A₁, ..., Aₓ>is:
    • 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 de namespace_or_type_name naar die typeparameter.
    • Als de namespace_or_type_name in een typedeclaratie wordt weergegeven, wordt voor elk exemplaartype T (§15.3.2) gestart met het exemplaartype van dat typedeclaratie en verdergaan met het exemplaartype van elke struct-declaratie van elke klasse of struct-declaratie (indien van toepassing):
      • Als x nul is en de declaratie van T een typeparameter met de naam Ibevat, verwijst de namespace_or_type_name naar die typeparameter.
      • Anders, 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 de namespace_or_type_name 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.

      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 worden voor elke naamruimte, beginnend met de naamruimte waarin de namespace_or_type_name plaatsvindt, verdergaan met elke omsluitende naamruimte N(indien van toepassing) en eindigend met de globale naamruimte, de volgende stappen worden geëvalueerd totdat een entiteit zich bevindt:
      • 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, is de namespace_or_type_name dubbelzinnig en treedt er een compilatiefout op.
        • Anders verwijst de namespace_or_type_name naar de naamruimte met de naam in I N.
      • Anders, als N het een toegankelijk type met 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 die de naam koppelt aan een naamruimte I of type, is de namespace_or_type_name dubbelzinnig en treedt er een compilatietijdfout op.
        • Anders verwijst de namespace_or_type_name naar het type dat is samengesteld met de opgegeven typeargumenten.
      • Als de locatie waar de namespace_or_type_name zich voordoet, anders wordt 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, verwijst de namespace_or_type_name naar die naamruimte of dat type.
        • Als de naamruimten die door de using_namespace_directives van de declaratie van de naamruimte zijn geïmporteerd, precies één type met naam I - en x typeparameters bevatten, verwijst de namespace_or_type_name naar dat type dat is samengesteld met de opgegeven typeargumenten.
        • Als de naamruimten die zijn geïmporteerd door de using_namespace_directives van de naamruimtedeclaratie meer dan één type met naam I - en x typeparameters bevatten, is de namespace_or_type_name dubbelzinnig en treedt er een fout op.
    • Anders is de namespace_or_type_name niet gedefinieerd en treedt er een compilatietijdfout op.
  • Anders is de namespace_or_type_name van het formulier N.I of van het formulier N.I<A₁, ..., Aₓ>. N wordt eerst opgelost als een namespace_or_type_name. Als de oplossing niet N lukt, treedt er een compilatietijdfout op. Anders of N.I N.I<A₁, ..., Aₓ> wordt dit opgelost als volgt:
    • Als x nul is en N verwijst naar een naamruimte en N een geneste naamruimte met naam Ibevat, verwijst de namespace_or_type_name naar die geneste naamruimte.
    • N Als u anders verwijst naar een naamruimte en N een toegankelijk type met naamI- en x typeparameters bevat, verwijst de namespace_or_type_name naar dat type dat is samengesteld met de opgegeven typeargumenten.
    • N Als dit niet verwijst naar een (mogelijk samengestelde) klasse of structtype en N of een van de basisklassen een geneste toegankelijk type met naam I en x typeparameters bevat, verwijst de namespace_or_type_name 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.

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

    • N.I Anders is het een ongeldige namespace_or_type_name en treedt er een compilatietijdfout op.

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 staat T in een typeof_expression (§12.8.17) van het formulier typeof(T)

7.8.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 id die is opgegeven in de declaratie, gevolgd door de generic_dimension_specifier (§12.8.17) voor K-typeparameters.

7.8.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 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 is N het volledige hiërarchische pad van id's 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 voor een typedeclaratie zonder de gedeeltelijke wijziging om dezelfde volledig gekwalificeerde naam te hebben 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.9 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 garbagecollector. 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 het object noch een van de exemplaarvelden kan worden geopend 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 voltooien.

    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 voltooien, wordt het object op een later tijdstip (§15.13) (indien van toepassing) voor het object uitgevoerd. Onder normale omstandigheden wordt de finalizer voor het object slechts één keer uitgevoerd, hoewel implementatie-gedefinieerde 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 de exemplaarvelden ervan niet toegankelijk is door een mogelijke voortzetting van de uitvoering, met inbegrip van het uitvoeren van finalizers, het object wordt beschouwd als niet toegankelijk en komt het object 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.2).

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 een verzameling aan te vragen voor uitvoering, finalizers die moeten 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

or

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

Hoewel het exemplaar niet A in gebruik was en Ade finalizer werd uitgevoerd, is het nog steeds mogelijk 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.10 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 afhankelijkheid van gegevens blijft behouden binnen een thread van uitvoering. 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.