Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
7.1 Applicatie 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:
- Het heeft de naam
Main
. - Het zal zijn
static
. - Het mag niet algemeen zijn.
- Het wordt gedeclareerd in een niet-algemeen type. Als het type dat de methode declareerde, een genest type is, kan geen van de bijbehorende insluittypen algemeen zijn.
- Het kan de
async
modifier hebben, mits het retourtype van de methodeSystem.Threading.Tasks.Task
ofSystem.Threading.Tasks.Task<int>
is. - Het retourtype moet
void
,int
,System.Threading.Tasks.Task
, ofSystem.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>
, kwalificeert zich niet als een 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, maakt een 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 toegangspunt totdat de geretourneerde taak is voltooid, waarbij
GetAwaiter().GetResult()
op de taak wordt aangeroepen, gebruikmakend van de parameterloze instantiemethode of de extensiemethode die wordt beschreven in §C.3. Als de taak mislukt, zalGetResult()
een uitzondering genereren, en deze uitzondering wordt door de gesynthetiseerde methode doorgegeven. - Voor een
Main
methode met een retourtypeSystem.Threading.Tasks.Task<int>
, als de taak succesvol is voltooid, wordt de waardeint
die doorGetResult()
geretourneerd wordt, door de gesynthetiseerde methode geretourneerd.
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 hebben de vrijheid om zelfgedefinieerd beleid en mechanismen te bieden voor het creëren en verwijderen 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 aan de toepassing informatie te leveren die vóór het opstarten van de toepassing is bepaald op een andere locatie binnen 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 behalve bij de aanroep vanuit de uitvoeringsomgeving.
7.2 Beëindiging van applicatie
Toepassingsbeëindiging retourneert controle over de uitvoeringsomgeving.
Als het retourtype van de effectieve invoermethode van de toepassing int
is, en de uitvoering voltooit zonder dat er een uitzondering optreedt, dient de waarde van hetgeen int
retourneert 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 void
is en de uitvoering voltooit zonder dat er een uitzondering optreedt, dan is de beëindigingsstatuscode 0
.
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 door de afvalverzameling zijn vrijgemaakt, tenzij dergelijke opschoning is onderdrukt (bijvoorbeeld door een oproep naar de bibliotheekmethode
GC.SuppressFinalize
). eindnotitie
7.3 Verklaringen
Declaraties in een C#-programma definiëren de samenstellende elementen van het programma. C#-programma's zijn ingedeeld met behulp van naamruimten. Deze worden geïntroduceerd met behulp van naamruimtedeclaraties (§14), die typedeclaraties en geneste naamruimtedeclaraties kunnen bevatten. Typedeclaraties (§14.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_declarations die niet omsloten zijn door een namespace_declaration onderdeel van een enkele gecombineerde declaratieruimte die de globale declaratieruimte wordt genoemd.
- Binnen alle compilatie-eenheden van een programma zijn namespace_member_declarationbinnen namespace_declarations met dezelfde volledig gekwalificeerde naamruimtenaam lid van één gecombineerde declaratieruimte.
- 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_declarations of 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 methodeverklaring, eigenschapverklaring, eigenschapstoegangsverklaring, indexeerderverklaring, indexeerdertoegangsverklaring, operatorverklaring, instantieconstructorverklaring, anonieme functieverklaring en lokale functieverklaring creëert een nieuwe declaratieruimte genaamd een lokale variabele declaratieruimte. Namen worden in deze declaratieruimte 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 variabelendeclaratieruimten kunnen genest zijn, maar het is een fout als een lokale variabelendeclaratieruimte en een geneste lokale variabelendeclaratieruimte elementen met dezelfde naam bevatten. In een geneste declaratieruimte is het dus niet mogelijk om een lokale variabele, lokale functie of constante te declareren met dezelfde naam als een parameter, typeparameter, lokale variabele, lokale functie of constante in een ingesloten declaratieruimte. Het is mogelijk dat twee declaratieruimten elementen met dezelfde naam bevatten zolang geen van beide declaratieruimte de andere bevat. Lokale declaratieruimten worden gemaakt door de volgende constructies:
- Elke variable_initializer in een veld- en eigenschapsdeclaratie introduceert een eigen lokale ruimte voor variabeledeclaratie, die niet is genest binnen een andere ruimte voor declaratie van lokale variabelen.
- De hoofdtekst van een functielid, anonieme functie of lokale functie, indien van toepassing, maakt een lokale ruimte voor variabeledeclaratie die wordt beschouwd als genest binnen de declaratieruimte van de lokale variabele van de functie.
- Elke constructor_initializer maakt een lokale ruimte voor variabeledeclaratie die is genest binnen de declaratie van de instantieconstructor. De ruimte voor declaratie van lokale variabelen voor de hoofdtekst van de constructor is op zijn beurt genest binnen deze lokale ruimte voor declaratie van variabelen.
- Elk blok, switch_block, specific_catch_clause, iteration_statement en using_statement maakt een geneste ruimte voor lokale variabeledeclaratie.
- Elke embedded_statement die niet rechtstreeks deel uitmaakt van een statement_list maakt een geneste ruimte voor declaratie van lokale variabelen.
- Elke switch_section maakt een geneste lokale variabeledeclaratieruimte. Variabelen die rechtstreeks in de statement_list van de switch_section worden gedeclareerd (maar niet binnen een geneste ruimte voor lokale variabelen binnen de statement_list) worden echter rechtstreeks toegevoegd aan de ruimte voor lokale variabelendeclaratie van de switch_block in plaats van die van de switch_section.
- De syntactische vertaling van een query_expression (§12.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 omvat ook ingesloten blokken. Daarom is het binnen een geneste blok niet mogelijk om een label met dezelfde naam als een label in een omsluitblok te declareren.
Opmerking: het feit dat variabelen die rechtstreeks in een switch_section zijn gedeclareerd, worden toegevoegd aan de lokale ruimte voor variabeledeclaratie van de switch_block in plaats van de switch_section kunnen leiden tot verrassende code. In het onderstaande voorbeeld bevindt de lokale variabele
y
zich binnen het bereik van de switchsectie voor de standaardcase, ondanks de declaratie die wordt weergegeven in de switchsectie voor case 0. De lokale 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 de waarden van constant_expression zijn 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 de 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 Namespace-leden
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 Structleden
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
.
Gedelegeerde leden
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 direct omvattende type, als dat er is.
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
modifier op te nemen in de liddeclaratie. De intuïtieve betekenis vanpublic
is 'toegang niet beperkt'. - Beveiligd, geselecteerd door het opgeven van een
protected
modifier 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, die wordt geselecteerd door een
internal
modifier op te nemen in de lidverklaring. De intuïtieve betekenis vaninternal
is "toegang beperkt tot deze assemblage". - Beveiligd intern, dat wordt gekozen door zowel een
protected
als eeninternal
modifier op te nemen 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é beschermd, dat wordt geselecteerd door zowel een
private
als eenprotected
modifier in de liddefinitie 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
modificator op te nemen 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 met
public
ofinternal
gedeclareerde toegankelijkheid hebben en hebben standaard toegankelijkheid vaninternal
. - Klasleden kunnen elke van de toegestane soorten gedeclareerde toegankelijkheid hebben en standaard gesteld worden op
private
gedeclareerde toegankelijkheid.Opmerking: een type dat is gedeclareerd als lid van een klasse, kan een van de toegestane soorten toegankelijkheid hebben, terwijl een type dat is gedeclareerd als lid van een naamruimte alleen
public
ofinternal
toegankelijkheid kan hebben gedeclareerd. eindnotitie - Struct-leden kunnen
public
,internal
ofprivate
gedeclareerde toegankelijkheid hebben en standaard hebben zijprivate
gedeclareerde toegankelijkheid, omdat structs impliciet verzegeld zijn. Struct-leden die zijn geïntroduceerd in eenstruct
(dat wil zeggen, niet overgenomen door die struct) kunnen geenprotected
,protected internal
, ofprivate protected
gedeclareerde toegankelijkheid hebben.Opmerking: een type dat als lid van een struct is gedeclareerd, kan toegankelijkheid hebben zoals
public
,internal
ofprivate
gedeclareerd, terwijl een type dat is gedeclareerd als lid van een naamruimte alleen toegankelijkheid kan hebben zoalspublic
ofinternal
. 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 het definiëren van het toegankelijkheidsdomein van een lid zegt men dat een lid toplevel is als het niet binnen een type wordt gedeclareerd, en een lid wordt genest als het binnen een ander type wordt gedeclareerd. Bovendien wordt de programmatekst van een programma gedefinieerd als alle tekst in alle compilatie-eenheden van het programma en wordt de programmatekst van een type gedefinieerd als alle tekst in de type_declarations van dat type (inclusief mogelijk typen die zijn genest binnen het type).
Het toegankelijkheidsdomein van een vooraf gedefinieerd type (zoals object
, 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 van
M
public
is, is het toegankelijkheidsdomein vanM
hetzelfde als dat vanT
. - Als de toegankelijkheid van
M
protected internal
is, moetD
de samenvoeging zijn van de programmatekst vanP
en de programmatekst van elk type dat vanT
is afgeleid en 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.- Vervolgens, als
M
public
is, is toegang toegestaan.- Anders, als
M
protected internal
is, is de toegang toegestaan indien dit zich voordoet binnen het programma waarinM
gedeclareerd is, of binnen een klasse die is afgeleid van de klasse waarinM
gedeclareerd is en plaatsvindt via het afgeleide klassetype (§7.5.4).- Anders, als
M
protected
is, is de toegang toegestaan als het plaatsvindt binnen de klasse waarinM
is gedeclareerd, of als het plaatsvindt binnen een klasse die is afgeleid van de klasse waarinM
is gedeclareerd en plaatsvindt via het afgeleide klassetype (§7.5.4).- Anders, als
M
internal
is, is de toegang toegestaan als het plaatsvindt binnen het programma waarinM
wordt gedeclareerd.- Anders, als
M
private
is, is de toegang toegestaan als dit gebeurt 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
,B.C.X
enB.C.Y
is de programmatekst van het 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 opgegeven, hebben alle behalveX
toegankelijkheidsdomeinen die beperkt worden door een omsluitend 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
. In deb.x
methode slaagt de toegang totA.F
dus, maar in deB.F
methode mislukt het.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 moet de toegang plaatsvinden via een exemplaar van dat afgeleide klassetype of een klassetype dat daaruit is samengesteld. Deze beperking voorkomt dat een afgeleide klasse toegang heeft tot beveiligde leden van andere afgeleide klassen, zelfs wanneer de leden worden overgenomen van dezelfde basisklasse.
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 in de vorm van
M
. - Een primary_expression van de vorm
E.M
, op voorwaarde dat het typeE
T
is of een klasse die is afgeleid vanT
, waarT
de klasseD
is, of een klassetype dat is samengesteld uitD
. - Een primary_expression van de vorm
base.M
. - Een primaire_expressie van de vorm
base[
argumentenlijst]
.
Naast deze vormen van toegang heeft een afgeleide klasse toegang tot een beveiligde exemplaarconstructor van een basisklasse in een constructor_initializer (§15.11.2).
Voorbeeld: In de volgende code
public class A { protected int x; static void F(A a, B b) { a.x = 1; // Ok b.x = 1; // Ok } } public class B : A { static void F(A a, B b) { a.x = 1; // Error, must access through instance of B b.x = 1; // Ok } }
binnen
A
, is het mogelijk om toegangx
te krijgen via exemplaren van beideA
enB
, aangezien in beide gevallen de toegang plaatsvindt via een exemplaar vanA
of een klasse afgeleid vanA
. BinnenB
is het echter niet mogelijk om via een exemplaar vanx
toegang te krijgen totA
, omdatA
niet afkomstig is 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 aan
x
toegestaan omdat ze allemaal plaatsvinden door middel van instanties van klassetypen die zijn samengesteld vanuit het generieke type.eindvoorbeeld
Opmerking: Het toegankelijkheidsdomein (§7.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
inD
is 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
als T
toegankelijk is in alle contexten waarin M
toegankelijk is.
De volgende toegankelijkheidsbeperkingen bestaan:
- De directe basisklasse van een klassetype moet ten minste zo toegankelijk zijn als het klassetype zelf.
- De expliciete basisinterfaces van een interfacetype moeten ten minste zo toegankelijk zijn als het interfacetype zelf.
- Het retourtype en de parametertypen van een delegate moeten ten minste zo toegankelijk zijn als het delegatetype zelf.
- Het type constante moet minstens zo toegankelijk zijn als de constante zelf.
- Het type veld moet minstens zo toegankelijk zijn als het veld zelf.
- Het retourtype en de parametertypen van een methode moeten ten minste zo toegankelijk zijn als de methode zelf.
- Het type van een eigenschap moet minstens zo toegankelijk zijn als de eigenschap zelf.
- Het type gebeurtenis moet minstens zo toegankelijk zijn als de gebeurtenis zelf.
- Het type en de parametertypen van een indexeerfunctie moeten ten minste zo toegankelijk zijn als de indexeerfunctie zelf.
- Het retourtype en de parametertypen van een operator moeten ten minste zo toegankelijk zijn als de operator zelf.
- De parametertypen van een exemplaarconstructor moeten ten minste zo toegankelijk zijn als de instantieconstructor zelf.
- Een interface- of klassetypebeperking voor een typeparameter moet ten minste zo toegankelijk zijn als het lid dat de beperking declareert.
Voorbeeld: In de volgende code
class A {...} public class B: A {...}
de
B
klasse veroorzaakt een compilatiefout omdatA
niet minstens 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 specifiek niet het returntype, de parameternamen, de typeparameter-constraints, de
params
ofthis
parametermodificaties, noch of parameters vereist of optioneel zijn. - De handtekening van een exemplaarconstructor bestaat uit het type en de parameterdoorgiftemodus van elk van de parameters, in de volgorde van links naar rechts. De handtekening van een instantieconstructor bevat niet specifiek de
params
wijzigingsfunctie die kan worden opgegeven voor de meest rechtse parameter, noch of parameters vereist of optioneel zijn. - De handtekening van een indexeerfunctie bestaat uit het type van elk van de parameters, in de volgorde van links naar rechts. De handtekening van een indexeerfunctie bevat niet specifiek het elementtype, 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 specifiek 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 het overladen van instantieconstructoren kan een klasse of struct meerdere instantieconstructoren declareren, mits hun signaturen uniek zijn binnen die klasse of struct.
- Door overbelasting van indexeerfuncties kan een klasse, struct of interface meerdere indexeerfuncties declareren, mits hun handtekeningen uniek zijn binnen die klasse, struct of interface.
- Door overbelasting van operators kan een klasse of struct meerdere operators met dezelfde naam declareren, mits hun handtekeningen uniek zijn binnen die klasse of struct.
Hoewel 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 in hetzelfde type zijn gedeclareerd met signaturen die hetzelfde zouden zijn als alle parameters in beide methoden met out
of in
modifiers worden gewijzigd naar ref
modifiers. Voor andere doeleinden van handtekeningkoppeling (bijvoorbeeld verbergen of overschrijven), 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 object
te vervangen door dynamic
zijn niet toegestaan.
Voorbeeld: In het volgende voorbeeld ziet u een set overbelaste methodedeclaraties samen met hun handtekeningen.
interface ITest { void F(); // F() void F(int x); // F(int) void F(ref int x); // F(ref int) void F(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 namespace-lid dat is gedeclareerd door een namespace_member_declaration (§14.6) zonder namespace_declaration bestrijkt 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_declarations van de compilation_unit of namespace_body die het onmiddellijk 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 door een type_parameter_list gedeclareerde typeparameter op een class_declaration (§15.2) is de class_base, type_parameter_constraints_clauses en class_body van dat 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 door een class_member_declaration (§15.3.1) is gedeclareerd, is het class_body waarin de declaratie 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 toepassingsgebied van een lid dat wordt gedeclareerd door een enum_member_declaration (§19.4) is de enum_body waarin de declaratie 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 constructeurverklaring (§15.11) is de constructeurinitialisatie en het blok van die constructeurverklaring.
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 die 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) omvat 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 zich vóór zijn constant_declarator bevindt.
Het bereik van een variabele die is gedeclareerd als onderdeel van een foreach_statement, using_statement, lock_statement of query_expression wordt bepaald door de uitbreiding van de opgegeven constructie.
Binnen het bereik van een naamruimte, klasse, struct of opsommingslid is het mogelijk om te verwijzen naar het lid in een tekstuele positie die voorafgaat aan de declaratie van het lid.
Voorbeeld:
class A { void F() { i = 1; } int i = 0; }
Hier is het geldig om
F
naari
te verwijzen voordat deze wordt gedeclareerd.eindvoorbeeld
Binnen het bereik van een lokale variabele is het een compilatiefout om naar de lokale variabele te verwijzen in een tekstuele positie die voorafgaat aan zijn declarator.
Voorbeeld:
class A { int i = 0; void F() { i = 1; // Error, use precedes declaration int i; i = 2; } void G() { int j = (j = 1); // Valid } void H() { int a = 1, b = ++a; // Valid } }
In de bovenstaande methode verwijst de eerste toewijzing naar
F
specifiek niet naar het veld dat in het buitenste bereik is gedeclareerd. In plaats daarvan verwijst deze naar de lokale variabele en resulteert deze in een compileertijdfout, omdat deze tekstually voorafgaat aan de declaratie van de variabele. In deG
-methode is het gebruik vanj
in de initialisatiefunctie voor de declaratie vanj
geldig, omdat het gebruik niet voorafgaat aan de declarator. 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 omgevingen overlappen door nesten en wanneer omgevingen overlappen door overerving. De kenmerken van de twee typen verbergen worden beschreven in de volgende subclauses.
7.7.2.2 Verbergen door nesten
Naamverberging kan optreden als gevolg van het nesten van naamruimten of typen binnen naamruimten, als gevolg van typen die genest zijn binnen klassen of structs, als gevolg van een lokale functie of lambda, en als gevolg van parameter-, lokale variabele- en lokale constanten-declaraties.
Voorbeeld: In de volgende code
class A { int i = 0; void F() { int i = 1; void M1() { float i = 1.0f; Func<double, double> doubler = (double i) => i * 2.0; } } void G() { i = 1; } }
binnen de
F
methode wordt de exemplaarvariabelei
verborgen door de lokale variabelei
, maar binnen deG
methodei
verwijst deze nog steeds naar de instantievariabele. Binnen de lokale functieM1
verbergt defloat i
de 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 oproep
F(1)
roept deF
gedeclareerd inInner
aan omdat alle buitenste voorkomens vanF
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 verbergen van namen neemt een van de volgende vormen aan:
- 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 buitenste scope, zorgt het verbergen van een zichtbare naam uit een geërfde scope ervoor dat er een waarschuwing wordt gerapporteerd.
Voorbeeld: In de volgende code
class Base { public void F() {} } class Derived : Base { public void F() {} // Warning, hiding an inherited name }
de verklaring van
F
inDerived
veroorzaakt dat er een waarschuwing wordt gemeld. Het verbergen van een overgenomen naam is specifiek geen fout, omdat hierdoor afzonderlijke evolutie van basisklassen wordt uitgesloten. De bovenstaande situatie kan bijvoorbeeld ontstaan omdat 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
deF
die werd geërfd vanBase
, maar aangezien de nieuweF
inDerived
privétoegang heeft, wordt het bereik niet uitgebreid naarMoreDerived
. De aanroepF()
inMoreDerived.G
is dus geldig en zalBase.F
oproepen.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? ('.' identifier type_argument_list?)*
| qualified_alias_member ('.' identifier type_argument_list?)*
;
Een namespace_name is een namespace_or_type_name die verwijst naar een naamruimte.
Na resolutie zoals hieronder beschreven is, moet de namespace_or_type_name van een namespace_name verwijzen naar een naamruimte, of anders treedt er een compilatietijdfout op. Er kunnen geen typeargumenten (§8.4.2) aanwezig zijn in een namespace_name (alleen typen kunnen typeargumenten hebben).
Een type_name is een namespace_or_type_name die verwijst naar een type.
Na de hieronder beschreven resolutie, moet de namespace_or_type_name van een type_name naar een type verwijzen, anders treedt er een compilatiefout op.
Een namespace_or_type_name verwijst naar een type of een naamruimte. Oplossing voor een bepaalde naamruimte of een bepaald type omvat twee stappen op basis van het delen van de grammatica in een voorlooponderdeel, een van de grammaticafragmenten:
identifier type_argument_list?
qualified_alias_member
en een volgonderdeel, het grammaticafragment:
('.' identifier type_argument_list?)*
Eerst wordt het voorlooponderdeel opgelost om de beginnende naamruimte of het type te bepalen R₀
.
Als het voorloopgedeelte van het namespace_or_type_name een qualified_alias_member is, is R₀
de naamruimte of het type dat wordt geïdentificeerd door het op te lossen, zoals beschreven in §14.8.1.
Anders heeft het voorlooponderdeel, de id van het grammaticafragment type_argument_list?, een van de volgende vormen:
I
I<A₁, ..., Aₓ>
waarbij geldt:
-
I
is een enkele identificator; en -
<A₁, ..., Aₓ>
is een type_argument_list; wanneer er geen type_argument_list is opgegeven, beschouwx
als nul.
R₀
wordt als volgt bepaald:
- Als
x
nul is en de namespace_or_type_name wordt weergegeven in een algemene methodedeclaratie (§15.6), maar buiten de kenmerken van de methode-header, en als die declaratie een typeparameter (§15.2.3) met de naamI
bevat, verwijst danR₀
naar die typeparameter. - Als de namespace_or_type_name in een typedeclaratie wordt weergegeven, start dan voor elk exemplaartype
T
(§15.3.2) met het exemplaartype van die typedeclaratie en ga verder met het exemplaartype van elke omhullende klasse- of structdeclaratie (indien van toepassing):- Als
x
nul is en de declaratie vanT
een typeparameter met de naamI
bevat,R₀
verwijst u naar die typeparameter. - Als de namespace_or_type_name wordt weergegeven in de hoofdtekst van de typedeclaratie en
T
of een van de basistypen een genest toegankelijk type met naamI
enx
typeparameters bevat, verwijst u danR₀
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, voor elke naamruimte
N
, beginnend met de naamruimte waarin de namespace_or_type_name voorkomt, vervolgens verder te gaan met elke omsluitende naamruimte (indien van toepassing) en eindigend met de globale naamruimte, worden de volgende stappen geëvalueerd totdat een entiteit wordt gevonden.- Als
x
nul is 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. -
R₀
Anders verwijst u naar de naamruimte met de naamI
inN
.
- Als de locatie waar de namespace_or_type_name plaatsvindt, wordt ingesloten door een naamruimtedeclaratie voor
- Anders, als
N
een toegankelijk type met de 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. -
R₀
Anders verwijst u naar het type dat is samengesteld met de opgegeven typeargumenten.
- Als
- Anders, als de locatie waar de namespace_or_type_name zich voordoet, wordt omsloten door een naamruimtedeclaratie voor
N
:- Als
x
nul is en de declaratie van de naamruimte een extern_alias_directive of using_alias_directive bevat die de naamI
aan een geïmporteerde naamruimte of -type koppelt, verwijst u naarR₀
die naamruimte of het type. - Als de naamruimten die door de using_namespace_directives van de naamruimtedeclaratie zijn geïmporteerd, precies één type met de naam
I
en de type-parametersx
bevatten, verwijstR₀
naar dat type dat wordt samengesteld met de gegeven typeargumenten. - Anders, als de naamruimten die zijn geïmporteerd door de using_namespace_directives van de naamruimtedeclaratie meer dan één type hebben met de naam
I
enx
typeparameters, is de namespace_or_type_name ambigu en treedt er een compilatiefout op.
- Als
- Als
- Anders is de namespace_or_type_name niet gedefinieerd en treedt er een compilatietijdfout op.
- Als
Als R₀
succesvol is opgelost, wordt het achterste deel van de namespace_or_type_name opgelost. Het afsluitende grammaticafragment bestaat uit k ≥ 0
herhalingen, waarbij elke herhaling de naamruimte of het type waarnaar wordt verwezen verder oplost.
Als k
nul is, is er dus geen volggedeelte, dan wordt de namespace_or_type_name omgezet in R₀
.
Anders heeft elke herhaling een van de vormen:
.I
.I<A₁, ..., Aₓ>
waarbij I
, A
en x
zijn gedefinieerd zoals hierboven.
Voor elke herhaling n
, waarbij 1 ≤ n ≤ k
, wordt de resolutie Rₙ
bepaald. Dit omvat Rₚ
, waarbij p = n - 1
, de resolutie van de voorgaande herhaling, wordt vastgesteld als volgt:
- Als
x
nul is enRₚ
verwijst naar een naamruimte enRₚ
een geneste naamruimte met een naamI
bevat, verwijst u naarRₙ
die geneste naamruimte. - Anders, indien
Rₚ
verwijst naar een naamruimte enRₚ
een toegankelijk type bevat met de naamI
en typeparametersx
, dan verwijstRₙ
naar dat type, samengesteld met de opgegeven typeargumenten. - Anders, als
Rₚ
verwijst naar een (mogelijk samengesteld) klasse- of structtype enRₚ
of een van de basisklassen een genest toegankelijk type met de naamI
enx
typeparameters bevat, dan verwijstRₙ
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 van
T.I
, voor een bepaald typeT
, wordt bepaald als onderdeel van het omzetten van de basisklassespecificatie vanT
dan wordt de directe basisklasse beschouwdT
alsobject
(§15.2.4.2). eindnotitie - Anders is de namespace_or_type_name ongeldig en treedt er een compilatiefout op.
De resolutie van de namespace_or_type_name is de resolutie van de laatste herhaling, Rₖ
.
Een namespace_or_type_name mag alleen verwijzen naar een statische klasse (§15.2.2.4) als
- De namespace_or_type_name is het
T
in een namespace_or_type_name van de vormT.I
, of - De namespace_or_type_name is de
T
in een typeof_expression (§12.8.18) van de vormtypeof(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 identifier die is opgegeven in de declaratie, gevolgd door de generieke_afmetingspecificatie (§12.8.18) 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
een lid is van de globale naamruimte, is de volledig gekwalificeerde naamN
. - 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 van N
is het volledige hiërarchische pad van identifiers en generic_dimension_specifiers die leiden tot N
, beginnend vanuit de globale naamruimte. Omdat elk lid van een naamruimte of type een unieke naam heeft, is de volledig gekwalificeerde naam van een naamruimte of typedeclaratie altijd uniek. Het is een compilatiefout voor dezelfde volledig gekwalificeerde naam om te verwijzen naar twee afzonderlijke entiteiten. Met name:
- Het is een fout voor zowel een naamruimtedeclaratie als een typedeclaratie om dezelfde volledig gekwalificeerde naam te hebben.
- Het is een fout voor twee verschillende typen declaraties om dezelfde volledig gekwalificeerde naam te hebben (bijvoorbeeld als zowel een struct- als klassedeclaratie dezelfde volledig gekwalificeerde naam hebben).
- Het is een fout als een typedeclaratie zonder partial-modifier dezelfde volledig gekwalificeerde naam heeft als een andere typedeclaratie (§15.2.7).
Voorbeeld: In het onderstaande voorbeeld ziet u verschillende naamruimte- en typedeclaraties, samen met de bijbehorende volledig gekwalificeerde namen.
class A {} // A namespace X // X { class B // X.B { class C {} // X.B.C } namespace Y // X.Y { class D {} // X.Y.D } } namespace X.Y // X.Y { class E {} // X.Y.E class G<T> // X.Y.G<> { class H {} // X.Y.G<>.H } class G<S,T> // X.Y.G<,> { class H<U> {} // X.Y.G<,>.H<> } }
eindvoorbeeld
7.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 vuilnisophaler. 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 noch het object, noch een van zijn exemplaarvelden toegankelijk is door een mogelijke voortzetting van de uitvoering, met uitzondering van het uitvoeren van finalizers, wordt het object beschouwd als niet meer in gebruik en komt het in aanmerking voor finalisatie.
Opmerking: De C#-compiler en de garbagecollector kunnen ervoor kiezen om code te analyseren om te bepalen welke verwijzingen naar een object in de toekomst kunnen worden gebruikt. Als een lokale variabele die binnen het bereik valt bijvoorbeeld de enige bestaande verwijzing naar een object is, maar die lokale variabele nooit wordt verwezen in een mogelijke voortzetting van de uitvoering vanaf het huidige uitvoeringspunt in de procedure, kan de garbagecollector het object behandelen als niet meer in gebruik. eindnotitie
- Zodra het object in aanmerking komt voor finalisatie, wordt op een later tijdstip de finalizer (§15.13) voor het object uitgevoerd, indien van toepassing. Onder normale omstandigheden wordt de finalizer voor het object slechts één keer uitgevoerd, hoewel implementatiegedefinieerde API's dit gedrag kunnen toestaan om te worden overschreven.
- Zodra de finalizer voor een object wordt uitgevoerd, wordt het object, als het object of een van zijn exemplaarvelden niet toegankelijk is door een mogelijke voortzetting van de uitvoering, met inbegrip van het uitvoeren van finalizers, als ontoegankelijk beschouwd en komt het in aanmerking voor verzameling.
Opmerking: Een object dat eerder niet kon worden geopend, is mogelijk opnieuw toegankelijk vanwege de finalizer. Hieronder ziet u een voorbeeld hiervan. eindnotitie
- 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 te verzoeken dat er een verzameling plaatsvindt, dat finalizers worden uitgevoerd (of niet worden uitgevoerd), enzovoort.
Voorbeeld: Omdat de garbagecollector brede breedtegraad heeft bij het bepalen wanneer objecten moeten worden verzameld en finalizers moeten worden uitgevoerd, kan een conforme implementatie uitvoer produceren die verschilt van de uitvoer die wordt weergegeven door de volgende code. Het programma
class A { ~A() { Console.WriteLine("Finalize instance of A"); } } class B { object Ref; public B(object o) { Ref = o; } ~B() { Console.WriteLine("Finalize instance of B"); } } class Test { static void Main() { B b = new B(new A()); b = null; GC.Collect(); GC.WaitForPendingFinalizers(); } }
maakt een exemplaar van klasse
A
en een exemplaar van 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
of
Finalize instance of B Finalize instance of A
omdat de taal geen beperkingen oplegt voor de volgorde waarin objecten afval worden verzameld.
In subtiele gevallen kan het onderscheid tussen 'in aanmerking komen voor finalisatie' en 'in aanmerking komen voor verzameling' belangrijk zijn. Bijvoorbeeld:
class A { ~A() { Console.WriteLine("Finalize instance of A"); } public void F() { Console.WriteLine("A.F"); Test.RefA = this; } } class B { public A Ref; ~B() { Console.WriteLine("Finalize instance of B"); Ref.F(); } } class Test { public static A RefA; public static B RefB; static void Main() { RefB = new B(); RefA = new A(); RefB.Ref = RefA; RefB = null; RefA = null; // A and B now eligible for finalization GC.Collect(); GC.WaitForPendingFinalizers(); // B now eligible for collection, but A is not if (RefA != null) { Console.WriteLine("RefA is not null"); } } }
In het bovenstaande programma, als de garbagecollector ervoor kiest om de finalizer van
A
vóór de finalizer 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
Merk op dat hoewel het exemplaar van
A
niet in gebruik was en de finalizer vanA
werd uitgevoerd, het nog steeds mogelijk is 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 gegevensafhankelijkheid blijft behouden binnen een uitvoeringsdraad. Dat wil gezegd, de waarde van elke variabele wordt berekend alsof alle instructies in de thread in de oorspronkelijke programmavolgorde zijn uitgevoerd.
- Regels voor initialisatievolgorde blijven behouden (§15.5.5, §15.5.6).
- De volgorde van bijwerkingen blijft behouden met betrekking tot vluchtige lees- en schrijfbewerkingen (§15.5.4). Daarnaast hoeft de uitvoeringsomgeving geen deel van een expressie te evalueren als deze kan afleiden dat de waarde van die expressie niet wordt gebruikt en dat er geen benodigde bijwerkingen worden geproduceerd (inclusief eventuele oorzaken die worden veroorzaakt door het aanroepen van een methode of het openen van een vluchtig veld). Wanneer de uitvoering van het programma wordt onderbroken door een asynchrone gebeurtenis (zoals een uitzondering die door een andere thread wordt gegenereerd), is het niet gegarandeerd dat de waarneembare bijwerkingen zichtbaar zijn in de oorspronkelijke programmavolgorde.
ECMA C# draft specification