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 isSystem.Threading.Tasks.Task
ofSystem.Threading.Tasks.Task<int>
. - Het retourtype moet
void
,int
ofSystem.Threading.Tasks.Task
System.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. Eenasync void
methode of eenasync
methode die een ander wachtbaar type retourneert, zoalsValueTask
ofValueTask<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 isSystem.Threading.Tasks.Task
, is het retourtype van de gesynthetiseerde methodevoid
- Als het retourtype van de
Main
methode isSystem.Threading.Tasks.Task<int>
, is het retourtype van de gesynthetiseerde methodeint
Uitvoering van de gesynthetiseerde methode gaat als volgt:
- De gesynthetiseerde methode roept de methode aan
Main
en geeft destring[]
parameterwaarde door als argument als deMain
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 deint
geretourneerde waardeGetResult()
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 0
de 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 variabelez
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
enMegacorp.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
enG
methoden dus in een compilatietijdfout omdat de naami
wordt gedeclareerd in het buitenste blok en niet opnieuw kan worden gedeclareerd in het binnenste blok. DeH
enI
methoden zijn echter geldig omdat de tweei
worden 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 klasobject
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 vanpublic
is 'toegang niet beperkt'. - Beveiligd, dat is geselecteerd door een
protected
wijzigingsfunctie op te geven in de liddeclaratie. De intuïtieve betekenis vanprotected
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 vaninternal
is "toegang beperkt tot deze assembly". - Beveiligd intern, dat wordt geselecteerd door zowel een
protected
als eeninternal
wijzigingsfunctie op te geven in de liddeclaratie. De intuïtieve betekenis vanprotected 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 eenprotected
wijzigingsfunctie in de liddeclaratie op te geven. De intuïtieve betekenis vanprivate 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 vanprivate
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
ofinternal
gedeclareerd en standaardinternal
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
ofinternal
toegankelijkheid kan hebben gedeclareerd. eindnotitie - Struct-leden kunnen toegankelijkheid of toegankelijkheid hebben
public
internal
ofprivate
als standaardprivate
toegankelijkheid hebben gedeclareerd, omdat structs impliciet zijn verzegeld. Struct-leden die zijn geïntroduceerd in eenstruct
(dat wil gezegd, niet overgenomen door die struct) kunnen geen ,protected internal
ofprivate protected
gedeclareerde toegankelijkheid hebbenprotected
.Opmerking: een type dat als lid van een struct is gedeclareerd, kan toegankelijkheid hebben
public
ofinternal
private
gedeclareerd, terwijl een type dat is gedeclareerd als lid van een naamruimte alleenpublic
ofinternal
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
, int
of 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 toegankelijkheidsdomeinT
de programmatekst vanP
en elk programma waarnaar wordt verwezenP
. - Als de gedeclareerde toegankelijkheid
T
intern is, is het toegankelijkheidsdomeinT
de programmatekst vanP
.
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
ispublic
, is het toegankelijkheidsdomeinM
T
van . - Als de toegankelijkheid
M
isprotected internal
gedeclareerd, laat uD
de samenvoeging zijn van de programmatekst vanP
en de programmatekst van elk type dat is afgeleid vanT
, die buitenP
is gedeclareerd . Het toegankelijkheidsdomein vanM
is het snijpunt van het toegankelijkheidsdomein vanT
metD
. - Als de gedeclareerde toegankelijkheid
M
isprivate protected
, laten weD
het snijpunt zijn van de programmatekst vanP
en de programmatekst vanT
en elk type dat is afgeleid vanT
. Het toegankelijkheidsdomein vanM
is het snijpunt van het toegankelijkheidsdomein vanT
metD
. - Als de gedeclareerde toegankelijkheid
M
isprotected
, laat uD
de samenvoeging zijn van de programmatekst vanT
en de programmatekst van elk type dat is afgeleid vanT
. Het toegankelijkheidsdomein vanM
is het snijpunt van het toegankelijkheidsdomein vanT
metD
. - Als de gedeclareerde toegankelijkheid
M
isinternal
, is het toegankelijkheidsdomeinM
het snijpunt van het toegankelijkheidsdomein vanT
met de programmatekst vanP
. - Als de gedeclareerde toegankelijkheid
M
isprivate
, is het toegankelijkheidsdomeinM
de programmatekst vanT
.
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 is
public
,M
is de toegang toegestaan.M
Als dat niet het geval isprotected internal
, is de toegang toegestaan als deze plaatsvindt in het programma waarinM
wordt gedeclareerd, of als deze plaatsvindt binnen een klasse die is afgeleid van de klasse waarinM
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 waarinM
wordt gedeclareerd, of als deze plaatsvindt binnen een klasse die is afgeleid van de klasse waarinM
wordt gedeclareerd en plaatsvindt via het afgeleide klassetype (§7.5.4).- Als dat niet het geval is
internal
,M
is de toegang toegestaan als deze plaatsvindt in het programma waarinM
wordt gedeclareerd.- Als dat niet het geval is
private
,M
is de toegang toegestaan als deze plaatsvindt binnen het type waarinM
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
enA.X
is onbeperkt.- Het toegankelijkheidsdomein van
A.Y
,B
,B.X
,B.Y
, , ,B.C
enB.C.X
isB.C.Y
de programmatekst van het betreffende programma.- Het toegankelijkheidsdomein van
A.Z
is de programmatekst vanA
.- Het toegankelijkheidsdomein van
B.Z
enB.D
is de programmatekst vanB
, inclusief de programmatekst vanB.C
enB.D
.- Het toegankelijkheidsdomein van
B.C.Z
is de programmatekst vanB.C
.- Het toegankelijkheidsdomein van
B.D.X
enB.D.Y
is de programmatekst vanB
, inclusief de programmatekst vanB.C
enB.D
.- Het toegankelijkheidsdomein van
B.D.Z
is de programmatekst vanB.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 zeX
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élidx
over van deA
klasse. Omdat het lid privé is, is het alleen toegankelijk binnen de class_body vanA
. De toegang totb.x
deA.F
methode slaagt dus, maar mislukt in deB.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 M
declareert en een klasse is D
die is afgeleid van B
. Binnen het class_body van D
kan 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 typeE
is of een klasse die isT
afgeleid vanT
, waarT
de klasseD
is of een klassetype dat is samengesteld uitD
. - Een primary_expression van het formulier
base.M
. - Een primary_expression van het formulier
base[
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 toegangx
te krijgen via exemplaren van beideA
enB
, aangezien in beide gevallen de toegang plaatsvindt via een exemplaar vanA
of een klasse afgeleid vanA
. Binnen isB
het echter niet mogelijk om toegang te krijgenx
via een exemplaar vanA
, omdatA
het niet is afgeleid vanB
.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
lidC<int>.x
isD
geldig, ook al is de klasseD
afgeleid vanC<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 omdatA
deze niet ten minste zo toegankelijk is alsB
.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 inB
een compilatiefout omdat het retourtypeA
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
, out
en ref
parameteraanpassingen als onderdeel van een handtekening worden beschouwd, kunnen leden die in één type zijn gedeclareerd, niet alleen verschillen in handtekening door in
, out
en 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
, out
en 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
,out
enref
. 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
in
out
, enref
parametermodifiers (§15.6.2) deel uitmaken van een handtekening. Dus,F(int)
,F(in int)
,F(out int)
enF(ref int)
zijn allemaal unieke handtekeningen. MaarF(in int)
F(out int)
, enF(ref int)
kan niet binnen dezelfde interface worden gedeclareerd omdat hun handtekeningen alleen verschillen vanin
,out
enref
. Houd er ook rekening mee dat het retourtype en deparams
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 deparams
wijzigingsfunctie. Als zodanig resulteren de declaraties van de methodenF(int)
enF(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 isN
of begint metN
, 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 defor
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
naari
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 naari
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 deG
methode is het gebruik vanj
in de initialisatiefunctie voor de declaratie geldigj
omdat het gebruik niet voorafgaat aan de declaratie. In deH
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 variabeleA
en in een typecontext om naar de klasseA
te 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 exemplaarvariabelei
verborgen door de lokale variabelei
, maar binnen deG
methodei
verwijst deze nog steeds naar de instantievariabele. Binnen de lokale functieM1
verbergt defloat i
directe buitenstei
functie. De lambdaparameteri
verbergt defloat 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 deF
gedeclareerde aanInner
omdat alle buitenste instantiesF
verborgen zijn door de binnenste declaratie. Om dezelfde reden resulteert de aanroepF("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
inDerived
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 vanBase
een methode is geïntroduceerdF
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 deF
inDerived
'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 nieuweF
versieDerived
privétoegang heeft, wordt het bereik niet uitgebreid naarMoreDerived
.F
Base
De aanroepF()
MoreDerived.G
is dus geldig en wordt aangeroepenBase.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 formulierI<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 naamI
bevat, 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 vanT
een typeparameter met de naamI
bevat, 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 naamI
enx
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
- Als
- 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 enI
de naam van een naamruimte inN
is, 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 naamI
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
.
- Als de locatie waar de namespace_or_type_name plaatsvindt, wordt ingesloten door een naamruimtedeclaratie voor
- Anders, als
N
het een toegankelijk type met naamI
enx
typeparameters bevat, dan:- Als
x
nul is en de locatie waar het namespace_or_type_name plaatsvindt, wordt ingesloten door een naamruimtedeclaratie voorN
en bevat de naamruimtedeclaratie een extern_alias_directive of using_alias_directive die de naam koppelt aan een naamruimteI
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
- 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 naamI
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
- enx
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
- enx
typeparameters bevatten, is de namespace_or_type_name dubbelzinnig en treedt er een fout op.
- Als
- Als
- Anders is de namespace_or_type_name niet gedefinieerd en treedt er een compilatietijdfout op.
- Als
- Anders is de namespace_or_type_name van het formulier
N.I
of van het formulierN.I<A₁, ..., Aₓ>
.N
wordt eerst opgelost als een namespace_or_type_name. Als de oplossing nietN
lukt, treedt er een compilatietijdfout op. Anders ofN.I
N.I<A₁, ..., Aₓ>
wordt dit opgelost als volgt:- Als
x
nul is enN
verwijst naar een naamruimte enN
een geneste naamruimte met naamI
bevat, verwijst de namespace_or_type_name naar die geneste naamruimte. N
Als u anders verwijst naar een naamruimte enN
een toegankelijk type met naamI
- enx
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 enN
of een van de basisklassen een geneste toegankelijk type met naamI
enx
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 vanN
dan wordt de directe basisklasse vanN
beschouwdobject
als (§15.2.4.2). eindnotitieN.I
Anders is het een ongeldige namespace_or_type_name en treedt er een compilatietijdfout op.
- Als
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 vormT.I
, of - De namespace_or_type_name staat
T
in een typeof_expression (§12.8.17) van het formuliertypeof(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.N
de volledig gekwalificeerde naam , waarS
de volledig gekwalificeerde naam van de naamruimte of typedeclaratie waarinN
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:
- Wanneer het object wordt gemaakt, wordt er geheugen toegewezen, wordt de constructor uitgevoerd en wordt het object beschouwd als live.
- 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
- 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.
- 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
- 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 klasseB
. Deze objecten komen in aanmerking voor garbagecollection wanneer de variabeleb
de waardenull
wordt 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 zijnFinalize 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 vanB
uit 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 enA
de finalizer werd uitgevoerd, is het nog steeds mogelijk om methoden vanA
(in dit gevalF
) 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 vanB
de finalizer ertoe geleid dat een exemplaar vanA
die eerder niet in gebruik was, toegankelijk werd vanuit de live referenceTest.RefA
. Na de aanroep naarWaitForPendingFinalizers
, komt het exemplaar vanB
in aanmerking voor verzameling, maar het exemplaarA
van is niet, vanwege de verwijzingTest.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.
ECMA C# draft specification