7 Grundläggande begrepp
7.1 Programstart
Ett program kan kompileras antingen som ett klassbibliotek som ska användas som en del av andra program eller som ett program som kan startas direkt. Mekanismen för att fastställa det här kompileringsläget är implementeringsdefinierad och extern med den här specifikationen.
Ett program som sammanställts som en ansökan ska innehålla minst en metod som kvalificerar sig som startpunkt genom att uppfylla följande krav:
- Den ska ha namnet
Main
. - Det ska vara
static
. - Den får inte vara generisk.
- Den skall deklareras i en icke-generisk typ. Om den typ som deklarerar metoden är en kapslad typ kan ingen av dess omslutande typer vara generiska.
- Det kan ha
async
modifieraren förutsatt att metodens returtyp ärSystem.Threading.Tasks.Task
ellerSystem.Threading.Tasks.Task<int>
. - Returtypen ska vara
void
,int
,System.Threading.Tasks.Task
ellerSystem.Threading.Tasks.Task<int>
. - Det får inte vara en partiell metod (§15.6.9) utan genomförande.
- Parameterlistan ska antingen vara tom eller ha en enda värdeparameter av typen
string[]
.
Obs! Metoder med
async
modifieraren måste ha exakt en av de två returtyper som anges ovan för att kvalificeras som en startpunkt. Enasync void
metod, eller enasync
metod som returnerar en annan väntande typ, tillValueTask
exempel ellerValueTask<int>
inte kvalificerar sig som en startpunkt. slutkommentar
Om mer än en metod som kvalificerar sig som startpunkt deklareras inom ett program, kan en extern mekanism användas för att ange vilken metod som anses vara den faktiska startpunkten för programmet. Om en kvalificerande metod som har en returtyp av int
eller void
hittas, anses alla kvalificerande metoder som har en returtyp av System.Threading.Tasks.Task
eller System.Threading.Tasks.Task<int>
inte vara en startpunktsmetod. Det är ett kompileringsfel för ett program som ska kompileras som ett program utan exakt en startpunkt. Ett program som kompilerats som ett klassbibliotek kan innehålla metoder som skulle kvalificera sig som startpunkter för programmet, men det resulterande biblioteket har ingen startpunkt.
Normalt bestäms den deklarerade tillgängligheten (§7.5.2) av en metod av de åtkomstmodifierare (§15.3.6) som anges i deklarationen, och på samma sätt bestäms den deklarerade tillgängligheten av en typ av åtkomstmodifierare som anges i deklarationen. För att en viss metod av en viss typ ska kunna anropas ska både typen och medlemmen vara tillgängliga. Startpunkten för programmet är dock ett specialfall. Mer specifikt kan körningsmiljön komma åt programmets startpunkt oavsett dess deklarerade tillgänglighet och oavsett den deklarerade tillgängligheten för dess omslutande typdeklarationer.
När startpunktsmetoden har en returtyp av System.Threading.Tasks.Task
eller System.Threading.Tasks.Task<int>
ska en kompilator syntetisera en synkron startpunktsmetod som anropar motsvarande Main
metod. Den syntetiserade metoden har parametrar och returtyper baserat på Main
metoden:
- Parameterlistan för den syntetiserade metoden är densamma som parameterlistan för
Main
metoden - Om returtypen för
Main
metoden ärSystem.Threading.Tasks.Task
är returtypen för den syntetiserade metodenvoid
- Om returtypen för
Main
metoden ärSystem.Threading.Tasks.Task<int>
är returtypen för den syntetiserade metodenint
Körningen av den syntetiserade metoden fortsätter enligt följande:
- Den syntetiserade metoden anropar
Main
metoden och skickar dessstring[]
parametervärde som ett argument omMain
metoden har en sådan parameter. -
Main
Om metoden utlöser ett undantag sprids undantaget av den syntetiserade metoden. - Annars väntar den syntetiserade startpunkten på att den returnerade aktiviteten ska slutföras, anropar
GetAwaiter().GetResult()
uppgiften med hjälp av antingen metoden parameterlös instans eller tilläggsmetoden som beskrivs av §C.3. Om uppgiften misslyckasGetResult()
utlöser ett undantag och det här undantaget sprids av den syntetiserade metoden. - För en
Main
metod med returtypenSystem.Threading.Tasks.Task<int>
, returneras värdet som returneras avint
från den syntetiserade metoden om aktiviteten har slutförtsGetResult()
.
Den effektiva startpunkten för ett program är startpunkten som deklareras i programmet, eller den syntetiserade metoden om en krävs enligt beskrivningen ovan. Returtypen för den effektiva startpunkten är därför alltid void
eller int
.
När ett program körs skapas en ny programdomän. Flera olika instansieringar av ett program kan finnas på samma dator samtidigt och var och en har en egen programdomän. En programdomän möjliggör programisolering genom att fungera som en container för programtillstånd. En programdomän fungerar som en container och gräns för de typer som definierats i programmet och de klassbibliotek som används. Typer som läses in i en programdomän skiljer sig från samma typer som läses in i en annan programdomän, och instanser av objekt delas inte direkt mellan programdomäner. Varje programdomän har till exempel en egen kopia av statiska variabler för dessa typer och en statisk konstruktor för en typ körs högst en gång per programdomän. Implementeringar kan tillhandahålla implementeringsdefinierade principer eller mekanismer för att skapa och förstöra programdomäner.
Programstart inträffar när körningsmiljön anropar programmets effektiva startpunkt. Om den effektiva startpunkten deklarerar en parameter ska implementeringen under programstarten se till att det ursprungliga värdet för parametern är en icke-null-referens till en strängmatris. Den här matrisen ska bestå av icke-null-referenser till strängar, så kallade programparametrar, som ges implementeringsdefinierade värden av värdmiljön innan programmet startas. Avsikten är att tillhandahålla den programinformation som fastställdes innan programmet startas från någon annanstans i den värdbaserade miljön.
Obs! På system som stöder en kommandorad motsvarar programparametrar vad som vanligtvis kallas kommandoradsargument. slutkommentar
Om den effektiva startpunktens returtyp är int
används returvärdet från metodanropet av körningsmiljön i programavslutning (§7.2).
Förutom de situationer som anges ovan beter sig startpunktsmetoder som de som inte är startpunkter i alla avseenden. I synnerhet om startpunkten anropas vid någon annan tidpunkt under programmets livslängd, till exempel med vanlig metodanrop, finns det ingen särskild hantering av metoden: om det finns en parameter kan den ha ett initialt värde på null
, eller ett icke-värdenull
som refererar till en matris som innehåller null-referenser. På samma sätt har returvärdet för startpunkten ingen särskild betydelse förutom i anropet från körningsmiljön.
7.2 Programavslut
Programavslut returnerar kontrollen till körningsmiljön.
Om returtypen för programmets effektiva startpunktsmetod är int
och körningen slutförs utan att resultera i ett undantag, fungerar värdet för den int
returnerade som programmets avslutningsstatuskod. Syftet med den här koden är att tillåta kommunikation av lyckad eller misslyckad körningsmiljö. Om returtypen för den effektiva startpunktsmetoden är void
och körningen slutförs utan att resultera i ett undantag är 0
statuskoden för avslutning .
Om den effektiva startpunktsmetoden avslutas på grund av ett undantag (§21.4) är slutkoden implementeringsdefinierad. Dessutom kan implementeringen tillhandahålla alternativa API:er för att ange slutkoden.
Huruvida finalizers (§15.13) körs som en del av programavslutet är implementeringsdefinierat.
Obs! .NET Framework-implementeringen gör allt för att anropa slutförare (§15.13) för alla objekt som ännu inte har skräpinsamling, såvida inte sådan rensning har undertryckts (till exempel genom ett anrop till biblioteksmetoden
GC.SuppressFinalize
). slutkommentar
7.3 Förklaringar
Deklarationer i ett C#-program definierar programmets element. C#-program ordnas med hjälp av namnområden. Dessa introduceras med hjälp av namnområdesdeklarationer (§14), som kan innehålla typdeklarationer och kapslade namnområdesdeklarationer. Typdeklarationer (§14.7) används för att definiera klasser (§15), structs (§16), gränssnitt (§18), uppräkningar (§19) och ombud (§20). Vilka typer av medlemmar som tillåts i en typdeklaration beror på typdeklarationens form. Till exempel klassdeklarationer kan innehålla deklarationer för konstanter (§15.4), fält (§15.5), metoder (§15.6), egenskaper (§15.7), händelser (§15.8), indexerare (§15.9 ), operatorer (§15.10), instanskonstruktorer (§15.11), statiska konstruktorer (§15.12), finalizers (§15.13) och kapslade typer (§15.3.9).
En deklaration definierar ett namn i det deklarationsutrymme som deklarationen tillhör. Det är ett kompileringsfel att ha två eller flera deklarationer som introducerar medlemmar med samma namn i ett deklarationsutrymme, förutom i följande fall:
- Två eller flera namnområdesdeklarationer med samma namn tillåts i samma deklarationsutrymme. Sådana namnområdesdeklarationer aggregeras för att bilda ett enda logiskt namnområde och dela ett enda deklarationsutrymme.
- Deklarationer i separata program men i samma namnområdesdeklarationsutrymme tillåts dela samma namn.
Obs! Dessa deklarationer kan dock medföra tvetydigheter om de ingår i samma program. slutkommentar
- Två eller flera metoder med samma namn men distinkta signaturer tillåts i samma deklarationsutrymme (§7.6).
- Två eller flera typdeklarationer med samma namn men distinkta tal av typparametrar tillåts i samma deklarationsutrymme (§7.8.2).
- Två eller flera typdeklarationer med den partiella modifieraren i samma deklarationsutrymme kan dela samma namn, samma antal typparametrar och samma klassificering (klass, struct eller gränssnitt). I det här fallet bidrar typdeklarationerna till en enda typ och aggregeras själva för att bilda ett enda deklarationsutrymme (§15.2.7).
- En namnområdesdeklaration och en typdeklaration i samma deklarationsutrymme kan dela samma namn så länge typdeklarationen har minst en typparameter (§7.8.2).
Det finns flera olika typer av deklarationsutrymmen, enligt beskrivningen i följande.
- Inom alla kompileringsenheter i ett program är namespace_member_declaration utan omslutande namespace_declaration medlemmar i ett enda kombinerat deklarationsutrymme som kallas globalt deklarationsutrymme.
- Inom alla kompileringsenheter i ett program är namespace_member_declarations inom namespace_declarationsom har samma fullständigt kvalificerade namnområdesnamn medlemmar i ett enda kombinerat deklarationsutrymme.
- Varje compilation_unit och namespace_body har ett aliasdeklarationsutrymme. Varje extern_alias_directive och using_alias_directive av compilation_unit eller namespace_body bidrar en medlem till aliasdeklarationsutrymmet (§14.5.2).
- Varje icke-partiell klass-, struct- eller gränssnittsdeklaration skapar ett nytt deklarationsutrymme. Varje partiell klass, struct eller gränssnittsdeklaration bidrar till ett deklarationsutrymme som delas av alla matchande delar i samma program (§16.2.4). Namn introduceras i det här deklarationsutrymmet via class_member_declarations, struct_member_declarations, interface_member_declarations eller type_parameters. Med undantag för överlagrade instanskonstruktordeklarationer och statiska konstruktordeklarationer kan en klass eller struct inte innehålla en medlemsdeklaration med samma namn som klassen eller structen. En klass, struct eller ett gränssnitt tillåter deklaration av överlagrade metoder och indexerare. Dessutom tillåter en klass eller struct deklaration av överlagrade instanskonstruktorer och operatorer. Till exempel kan en klass, struct eller ett gränssnitt innehålla flera metoddeklarationer med samma namn, förutsatt att dessa metoddeklarationer skiljer sig åt i signaturen (§7.6). Observera att basklasser inte bidrar till deklarationsutrymmet för en klass, och basgränssnitt bidrar inte till deklarationsutrymmet i ett gränssnitt. Därför kan en härledd klass eller ett gränssnitt deklarera en medlem med samma namn som en ärvd medlem. En sådan medlem sägs dölja den ärvda medlemmen.
- Varje ombudsdeklaration skapar ett nytt deklarationsutrymme. Namn introduceras i det här deklarationsutrymmet via parametrar (fixed_parameters och parameter_arrays) och type_parameters.
- Varje uppräkningsdeklaration skapar ett nytt deklarationsutrymme. Namn introduceras i det här deklarationsutrymmet via enum_member_declarations.
- Varje metoddeklaration, egenskapsdeklaration, egenskapsåtkomstdeklaration, indexeringsdeklaration, deklaration av indexerare, operatordeklaration, instanskonstruktordeklaration, anonym funktion och lokal funktion skapar ett nytt deklarationsutrymme som kallas för ett lokalt variabeldeklarationsutrymme. Namn introduceras i det här deklarationsutrymmet via parametrar (fixed_parameters och parameter_arrays) och type_parameters. Set-accessorn för en egenskap eller en indexerare introducerar namnet
value
som en parameter. Brödtexten för funktionsmedlemmen, den anonyma funktionen eller den lokala funktionen anses vara kapslad i det lokala variabeldeklarationsutrymmet. När ett lokalt variabeldeklarationsutrymme och ett kapslat lokalt variabeldeklarationsutrymme innehåller element med samma namn, inom omfånget för det kapslade lokala namnet, döljs det yttre lokala namnet (§7.7.1) med det kapslade lokala namnet. - Ytterligare deklarationsutrymmen för lokala variabler kan förekomma i medlemsdeklarationer, anonyma funktioner och lokala funktioner. Namn introduceras i dessa deklarationsutrymmen genom mönster s, declaration_expressions, declaration_statements och exception_specifiers. Lokala variabeldeklarationsutrymmen kan kapslas, men det är ett fel för ett lokalt variabeldeklarationsutrymme och ett kapslat lokalt variabeldeklarationsutrymme som innehåller element med samma namn. Inom ett kapslat deklarationsutrymme går det därför inte att deklarera en lokal variabel, lokal funktion eller konstant med samma namn som en parameter, typparameter, lokal variabel, lokal funktion eller konstant i ett omslutande deklarationsutrymme. Det är möjligt att två deklarationsutrymmen innehåller element med samma namn så länge inget av deklarationsutrymmena innehåller det andra. Lokala deklarationsutrymmen skapas av följande konstruktioner:
- Varje variable_initializer i ett fält och en egenskapsdeklaration introducerar ett eget lokalt variabeldeklarationsutrymme som inte är kapslat i något annat lokalt variabeldeklarationsutrymme.
- Brödtexten för en funktionsmedlem, anonym funktion eller lokal funktion, om någon, skapar ett lokalt variabeldeklarationsutrymme som anses vara kapslat i funktionens lokala variabeldeklarationsutrymme.
- Varje constructor_initializer skapar ett lokalt variabeldeklarationsutrymme kapslat i instanskonstruktordeklarationen. Det lokala variabeldeklarationsutrymmet för konstruktorns brödtext är i sin tur kapslat i det här lokala variabeldeklarationsutrymmet.
- Varje block, switch_block, specific_catch_clause, iteration_statement och using_statement skapar ett kapslat lokalt variabeldeklarationsutrymme.
- Varje embedded_statement som inte är direkt en del av en statement_list skapar ett kapslat lokalt variabeldeklarationsutrymme.
- Varje switch_section skapar ett kapslat lokalt variabeldeklarationsutrymme. Variabler som deklareras direkt inom statement_list i switch_section (men inte inom ett kapslat lokalt variabeldeklarationsutrymme i statement_list) läggs dock direkt till i det lokala variabeldeklarationsutrymmet för den omslutande switch_block i stället för switch_section.
- Den syntaktiska översättningen av en query_expression (§12.20.3) kan introducera ett eller flera lambda-uttryck. Som anonyma funktioner skapar var och en av dessa ett lokalt variabeldeklarationsutrymme enligt beskrivningen ovan.
- Varje block eller switch_block skapar ett separat deklarationsutrymme för etiketter. Namn introduceras i det här deklarationsutrymmet via labeled_statements och namnen refereras via goto_statements. Etikettdeklarationsutrymmet för ett block innehåller alla kapslade block. Inom ett kapslat block går det därför inte att deklarera en etikett med samma namn som en etikett i ett omslutande block.
Obs! Det faktum att variabler som deklareras direkt inom en switch_section läggs till i det lokala variabeldeklarationsutrymmet för switch_block i stället för switch_section kan leda till överraskande kod. I exemplet nedan finns den lokala variabeln
y
i omfånget i växelavsnittet för standardfallet, trots att deklarationen visas i växelavsnittet för fall 0. Den lokala variabelnz
finns inte i omfånget i switchavsnittet för standardfallet, eftersom den introduceras i det lokala variabeldeklarationsutrymmet för växelavsnittet där deklarationen inträffar.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; }
slutkommentar
Den textordning i vilken namn deklareras har i allmänhet ingen betydelse. I synnerhet är textordningen inte betydande för deklarationen och användningen av namnrymder, konstanter, metoder, egenskaper, händelser, indexerare, operatorer, instanskonstruktorer, finalizers, statiska konstruktorer och typer. Deklarationsordningen är betydande på följande sätt:
- Deklarationsordning för fältdeklarationer bestämmer i vilken ordning deras initialiserare (om några) körs (§15.5.6.2, §15.5.6.3).
- Lokala variabler ska definieras innan de används (§7.7).
- Deklarationsordningen för uppräkningsmedlemsdeklarationer (§19.4) är betydande när constant_expression värden utelämnas.
Exempel: Deklarationsutrymmet för ett namnområde är "öppet avslutat" och två namnområdesdeklarationer med samma fullständigt kvalificerade namn bidrar till samma deklarationsutrymme. Till exempel
namespace Megacorp.Data { class Customer { ... } } namespace Megacorp.Data { class Order { ... } }
De två namnområdesdeklarationerna ovan bidrar till samma deklarationsutrymme, i det här fallet deklarerar du två klasser med de fullständigt kvalificerade namnen
Megacorp.Data.Customer
ochMegacorp.Data.Order
. Eftersom de två deklarationerna bidrar till samma deklarationsutrymme skulle det ha orsakat ett kompileringsfel om var och en innehöll en deklaration av en klass med samma namn.slutexempel
Obs! Som anges ovan innehåller deklarationsutrymmet för ett block alla kapslade block. I följande exempel
F
resulterar metoderna ochG
därför i ett kompileringsfel eftersom namneti
deklareras i det yttre blocket och inte kan redeclared i det inre blocket. Metoderna ochH
är dockI
giltiga eftersom de tvåi
deklareras i separata icke-kapslade block.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(); } } }
slutkommentar
7.4 Medlemmar
7.4.1 Allmänt
Namnområden och typer har medlemmar.
Obs! Medlemmarna i en entitet är allmänt tillgängliga med hjälp av ett kvalificerat namn som börjar med en referens till entiteten, följt av en "
.
" token, följt av namnet på medlemmen. slutkommentar
Medlemmar av en typ deklareras antingen i typdeklarationen eller ärvs från basklassen av typen. När en typ ärver från en basklass blir alla medlemmar i basklassen, förutom instanskonstruktorer, finalizers och statiska konstruktorer medlemmar av den härledda typen. Den deklarerade tillgängligheten för en basklassmedlem styr inte om medlemmen ärvs – arv utökas till alla medlemmar som inte är en instanskonstruktor, statisk konstruktor eller slutförare.
Obs! En ärvd medlem kan dock inte vara tillgänglig i en härledd typ, till exempel på grund av dess deklarerade tillgänglighet (§7.5.2). slutkommentar
7.4.2 Namnområdesmedlemmar
Namnområden och typer som inte har någon omslutande namnrymd är medlemmar i det globala namnområdet. Detta motsvarar direkt de namn som deklarerats i det globala deklarationsutrymmet.
Namnområden och typer som deklareras i ett namnområde är medlemmar i det namnområdet. Detta motsvarar direkt de namn som deklarerats i namnområdets deklarationsutrymme.
Namnområden har inga åtkomstbegränsningar. Det går inte att deklarera privata, skyddade eller interna namnområden och namnområdesnamn är alltid offentligt tillgängliga.
7.4.3 Struct medlemmar
Medlemmarna i en struct är de medlemmar som deklareras i structen och de medlemmar som ärvts från structens direkta basklass System.ValueType
och den indirekta basklassen object
.
Medlemmarna av en enkel typ motsvarar direkt medlemmarna av den structtyp som aliaseras av den enkla typen (§8.3.5).
7.4.4 Uppräkningsmedlemmar
Medlemmarna i en uppräkning är de konstanter som deklareras i uppräkningen och de medlemmar som ärvts från uppräkningens direkta basklass System.Enum
och de indirekta basklasserna System.ValueType
och object
.
7.4.5 Klassmedlemmar
Medlemmarna i en klass är de medlemmar som deklareras i klassen och de medlemmar som ärvts från basklassen (förutom klassen object
som inte har någon basklass). Medlemmarna som ärvs från basklassen inkluderar konstanter, fält, metoder, egenskaper, händelser, indexerare, operatorer och typer av basklassen, men inte instanskonstruktorer, finalizers och statiska konstruktorer för basklassen. Basklassmedlemmar ärvs utan hänsyn till deras tillgänglighet.
En klassdeklaration kan innehålla deklarationer av konstanter, fält, metoder, egenskaper, händelser, indexerare, operatorer, instanskonstruktorer, finalizers, statiska konstruktorer och typer.
Medlemmarna av object
(§8.2.3) och string
(§8.2.5) motsvarar direkt till medlemmarna av klassificeratyperna som de alias.
7.4.6 Gränssnittsmedlemmar
Medlemmarna i ett gränssnitt är de medlemmar som deklareras i gränssnittet och i alla basgränssnitt i gränssnittet.
Obs! Medlemmarna i klassen
object
är inte, strängt taget, medlemmar i något gränssnitt (§18.4). Medlemmarna i klassenobject
är dock tillgängliga via medlemssökning i alla gränssnittstyper (§12.5). slutkommentar
7.4.7 Matrismedlemmar
Medlemmarna i en matris är de medlemmar som ärvs från klassen System.Array
.
7.4.8 Delegera medlemmar
Ett ombud ärver medlemmar från klassen System.Delegate
. Dessutom innehåller den en metod med namnet Invoke
med samma returtyp och parameterlista som anges i deklarationen (§20.2). En anrop av denna metod ska bete sig identiskt med ett ombudsanrop (§20.6) på samma delegatinstans.
En implementering kan ge ytterligare medlemmar, antingen genom arv eller direkt i själva ombudet.
7.5 Medlemsåtkomst
7.5.1 Allmänt
Medlemsdeklarationer tillåter kontroll över medlemsåtkomst. Tillgängligheten för en medlem upprättas genom den deklarerade tillgängligheten (§7.5.2) av medlemmen i kombination med tillgängligheten av den omedelbart innehållande typen, om någon.
När åtkomst till en viss medlem tillåts sägs medlemmen vara tillgänglig. Omvänt, när åtkomst till en viss medlem är otillåten, sägs medlemmen vara otillgänglig. Åtkomst till en medlem är tillåten när den textplats där åtkomsten äger rum ingår i tillgänglighetsdomänen (§7.5.3) för medlemmen.
7.5.2 Deklarerad tillgänglighet
Den deklarerade tillgängligheten för en medlem kan vara något av följande:
- Offentlig, som väljs genom att inkludera en
public
modifierare i medlemsdeklarationen. Den intuitiva innebörden avpublic
är "åtkomsten är inte begränsad". - Skyddad, som väljs genom att inkludera en
protected
modifierare i medlemsdeklarationen. Den intuitiva innebörden avprotected
är "åtkomst begränsad till den innehållande klassen eller typer som härleds från den innehållande klassen". - Intern, som väljs genom att inkludera en
internal
modifierare i medlemsdeklarationen. Den intuitiva innebörden avinternal
är "åtkomst begränsad till den här sammansättningen". - Skyddat internt, som väljs genom att inkludera både en
protected
och eninternal
modifierare i medlemsdeklarationen. Den intuitiva innebörden avprotected internal
är "tillgänglig i den här sammansättningen samt typer som härleds från den innehållande klassen". - Privat skyddat, som väljs genom att inkludera både en
private
och enprotected
modifierare i medlemsdeklarationen. Den intuitiva innebörden avprivate protected
är "tillgänglig i den här sammansättningen av den innehållande klassen och typer som härleds från den innehållande klassen". - Privat, som väljs genom att inkludera en
private
modifierare i medlemsdeklarationen. Den intuitiva innebörden avprivate
är "åtkomst begränsad till den innehållande typen".
Beroende på i vilken kontext en medlemsdeklaration sker tillåts endast vissa typer av deklarerad tillgänglighet. När en medlemsdeklaration inte innehåller några åtkomstmodifierare avgör dessutom den kontext i vilken deklarationen äger rum standardvärdet för deklarerad tillgänglighet.
- Namnrymder har
public
implicit deklarerat tillgänglighet. Inga åtkomstmodifierare tillåts i namnområdesdeklarationer. - Typer som deklareras direkt i kompileringsenheter eller namnområden (till skillnad från inom andra typer) kan ha
public
ellerinternal
deklarerat tillgänglighet och standardvärdet förinternal
deklarerad tillgänglighet. - Klassmedlemmar kan ha någon av de tillåtna typerna av deklarerad tillgänglighet och standard för
private
deklarerad tillgänglighet.Obs! En typ som deklareras som medlem i en klass kan ha någon av de tillåtna typerna av deklarerad tillgänglighet, medan en typ som deklareras som medlem i ett namnområde bara
public
kan ha ellerinternal
deklarerat tillgänglighet. slutkommentar - Struct-medlemmar kan ha
public
,internal
ellerprivate
deklarerad tillgänglighet och standardvärdetprivate
för deklarerad tillgänglighet eftersom structs är implicit förseglade. Struct-medlemmar som introduceras i enstruct
(dvs. inte ärvd av den structen) kan inte haprotected
,protected internal
ellerprivate protected
deklarerad tillgänglighet.Obs! En typ som deklareras som medlem i en struct kan ha
public
,internal
ellerprivate
deklarerad tillgänglighet, medan en typ som deklareras som medlem i ett namnområde barapublic
kan ha ellerinternal
deklarerat tillgänglighet. slutkommentar - Gränssnittsmedlemmar har
public
implicit deklarerat tillgänglighet. Inga åtkomstmodifierare tillåts i medlemsdeklarationer för gränssnittet. - Uppräkningsmedlemmar har
public
implicit deklarerat tillgänglighet. Inga åtkomstmodifierare tillåts i medlemsdeklarationer för uppräkning.
7.5.3 Tillgänglighetsdomäner
Tillgänglighetsdomänen för en medlem består av de (eventuellt åtskilda) avsnitten i programtexten där åtkomst till medlemmen tillåts. För att definiera tillgänglighetsdomänen för en medlem sägs en medlem vara på toppnivå om den inte deklareras inom en typ och en medlem sägs vara kapslad om den deklareras inom en annan typ. Dessutom definieras programtexten i ett program som all text som finns i alla kompileringsenheter i programmet, och programtexten av en typ definieras som all text som finns i type_declarationav den typen (inklusive eventuellt typer som är kapslade inom typen).
Tillgänglighetsdomänen för en fördefinierad typ (till exempel object
, int
eller double
) är obegränsad.
Tillgänglighetsdomänen för en obunden toppnivå (T
§8.4.4) som deklareras i ett program P
definieras på följande sätt:
- Om den deklarerade tillgängligheten
T
för är offentlig är tillgänglighetsdomänenT
för programtextenP
för och alla program som refererar tillP
. - Om den deklarerade tillgängligheten
T
för är intern är tillgänglighetsdomänenT
för programtextenP
.
Obs! Av dessa definitioner följer att tillgänglighetsdomänen för en obunden toppnivå alltid är minst programtexten för programmet där den typen deklareras. slutkommentar
Tillgänglighetsdomänen för en konstruerad typ T<A₁, ..., Aₑ>
är skärningspunkten för tillgänglighetsdomänen för den obundna generiska typen T
och tillgänglighetsdomänerna för typargumenten A₁, ..., Aₑ
.
Tillgänglighetsdomänen för en kapslad medlem M
som deklareras i en typ T
i ett program P
definieras på följande sätt (notera att M
det kan vara en typ):
- Om den deklarerade tillgängligheten
M
för ärpublic
är tillgänglighetsdomänenM
för tillgänglighetsdomänenT
för . - Om den deklarerade tillgängligheten för är
M
ska viprotected internal
vara en union av programtexten förD
och programtexten av alla typer som härletts frånP
, som deklareras utanförT
.P
TillgänglighetsdomänenM
för är skärningspunkten för tillgänglighetsdomänenT
för medD
. - Om den deklarerade tillgängligheten för är
M
ska viprivate protected
vara skärningspunkten mellan programtextenD
i och programtexten förP
och alla typer som härletts frånT
.T
TillgänglighetsdomänenM
för är skärningspunkten för tillgänglighetsdomänenT
för medD
. - Om den deklarerade tillgängligheten för är
M
ska viprotected
vara en union av programtexten förD
och programtexten av alla typer som härletts frånT
.T
TillgänglighetsdomänenM
för är skärningspunkten för tillgänglighetsdomänenT
för medD
. - Om den deklarerade tillgängligheten
M
för ärinternal
är tillgänglighetsdomänenM
i skärningspunkten för tillgänglighetsdomänenT
för med programtextenP
i . - Om den deklarerade tillgängligheten
M
för ärprivate
är tillgänglighetsdomänenM
för programtextenT
i .
Obs! Av dessa definitioner följer att tillgänglighetsdomänen för en kapslad medlem alltid är minst programtexten av den typ som medlemmen deklareras i. Dessutom följer att tillgänglighetsdomänen för en medlem aldrig är mer inkluderande än tillgänglighetsdomänen för den typ där medlemmen deklareras. slutkommentar
Obs! När en typ eller medlem
M
används intuitivt utvärderas följande steg för att säkerställa att åtkomsten tillåts:
M
Om deklareras inom en typ (till skillnad från en kompileringsenhet eller ett namnområde) uppstår ett kompileringsfel om den typen inte är tillgänglig.M
Om ärpublic
tillåts åtkomsten sedan.- I annat fall, om
M
ärprotected internal
, tillåts åtkomsten om den sker inom det program därM
den deklareras, eller om den sker inom en klass som härleds från klassen därM
deklareras och sker via den härledda klasstypen (§7.5.4).- I annat fall, om
M
ärprotected
, tillåts åtkomsten om den sker inom den klass därM
den deklareras, eller om den sker inom en klass som härleds från den klass därM
deklareras och sker via den härledda klasstypen (§7.5.4).- I annat fall, om
M
ärinternal
, tillåts åtkomsten om den inträffar i programmet därM
deklareras.- Om
M
ärprivate
tillåts annars åtkomsten om den inträffar inom den typ somM
deklareras.- Annars är typen eller medlemmen otillgänglig och ett kompileringsfel inträffar. slutkommentar
Exempel: I följande kod
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; } }
klasserna och medlemmarna har följande tillgänglighetsdomäner:
- Tillgänglighetsdomänen
A
för ochA.X
är obegränsad.- Tillgänglighetsdomänen
A.Y
för ,B
,B.X
,B.Y
,B.C
,B.C.X
ochB.C.Y
är programtexten för det innehållande programmet.- Tillgänglighetsdomänen
A.Z
för är programtextenA
i .- Tillgänglighetsdomänen för och
B.Z
är programtextenB.D
i , inklusive programtextenB
för ochB.C
.B.D
- Tillgänglighetsdomänen
B.C.Z
för är programtextenB.C
i .- Tillgänglighetsdomänen för och
B.D.X
är programtextenB.D.Y
i , inklusive programtextenB
för ochB.C
.B.D
- Tillgänglighetsdomänen
B.D.Z
för är programtextenB.D
i . Som exemplet visar är tillgänglighetsdomänen för en medlem aldrig större än för en innehållande typ. Till exempel, även om allaX
medlemmar har offentligt deklarerad tillgänglighet, har alla utomA.X
tillgänglighetsdomäner som begränsas av en innehållande typ.slutexempel
Enligt beskrivningen i §7.4 ärvs alla medlemmar i en basklass, förutom instanskonstruktorer, finalizers och statiska konstruktorer, av härledda typer. Detta inkluderar även privata medlemmar i en basklass. Tillgänglighetsdomänen för en privat medlem innehåller dock endast programtexten för den typ där medlemmen deklareras.
Exempel: I följande kod
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 } }
klassen
B
ärver den privata medlemmenx
frånA
klassen. Eftersom medlemmen är privat är den endast tillgänglig i class_body avA
. Därmed lyckas åtkomsten tillb.x
metodenA.F
, men misslyckas iB.F
-metoden.slutexempel
7.5.4 Skyddad åtkomst
När en protected
private protected
instansmedlem används utanför programtexten för klassen där den deklareras och när en protected internal
instansmedlem nås utanför programtexten i programmet där den deklareras, ska åtkomsten ske inom en klassdeklaration som härleds från klassen där den deklareras. Dessutom måste åtkomsten ske via en instans av den härledda klasstypen eller en klasstyp som skapats från den. Den här begränsningen hindrar en härledd klass från att komma åt skyddade medlemmar i andra härledda klasser, även när medlemmarna ärvs från samma basklass.
Låt oss B
vara en basklass som deklarerar en skyddad instansmedlem M
och låter vara D
en klass som härleds från B
. I class_body av D
kan åtkomsten till M
ha något av följande formulär:
- En okvalificerad type_name eller primary_expression av formuläret
M
. - En
D
. - En primary_expression av formuläret
base.M
. - En
]
Utöver dessa former av åtkomst kan en härledd klass komma åt en skyddad instanskonstruktor för en basklass i en constructor_initializer (§15.11.2).
Exempel: I följande kod
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 } }
inom
A
är det möjligt att komma åtx
via instanser av bådeA
ochB
, eftersom åtkomsten i båda fallen sker via en instans avA
eller en klass som härletts frånA
. Inom är det dockB
inte möjligt att komma åtx
via en instans avA
, eftersomA
inte härleds frånB
.slutexempel
Exempel:
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"; } }
Här tillåts de tre tilldelningar
x
som ska utföras eftersom de alla sker via instanser av klasstyper som konstruerats från den generiska typen.slutexempel
Obs! Tillgänglighetsdomänen (§7.5.3) för en skyddad medlem som deklareras i en generisk klass innehåller programtexten för alla klassdeklarationer som härleds från alla typer som skapats från den generiska klassen. I exemplet:
class C<T> { protected static T x; } class D : C<string> { static void Main() { C<int>.x = 5; } }
referensen till
protected
medlemC<int>.x
iD
är giltig även om klassenD
härleds frånC<string>
. slutkommentar
7.5.5 Hjälpmedelsbegränsningar
Flera konstruktioner på C#-språket kräver att en typ är minst lika tillgänglig som en medlem eller en annan typ. En typ T
sägs vara minst lika tillgänglig som en medlem eller typ M
om tillgänglighetsdomänen T
för är en superuppsättning av tillgänglighetsdomänen M
för . Med andra ord T
, är minst lika tillgänglig som M
om T
är tillgänglig i alla sammanhang där M
är tillgänglig.
Följande hjälpmedelsbegränsningar finns:
- Den direkta basklassen för en klasstyp ska vara minst lika tillgänglig som själva klasstypen.
- De explicita basgränssnitten av en gränssnittstyp ska vara minst lika tillgängliga som själva gränssnittstypen.
- Returtypen och parametertyperna för en ombudstyp ska vara minst lika tillgängliga som själva ombudstypen.
- En konstants typ ska vara minst lika tillgänglig som konstanten själv.
- Typen av fält ska vara minst lika tillgänglig som själva fältet.
- Returtypen och parametertyperna för en metod ska vara minst lika tillgängliga som själva metoden.
- Egendomens typ ska vara minst lika tillgänglig som själva fastigheten.
- En händelsetyp ska vara minst lika tillgänglig som själva händelsen.
- Typ- och parametertyperna för en indexerare ska vara minst lika tillgängliga som indexeraren själv.
- En operatörs returtyp och parametertyper ska vara minst lika tillgängliga som operatören själv.
- Parametertyperna för en instanskonstruktor ska vara minst lika tillgängliga som själva instanskonstruktorn.
- Ett gränssnitts- eller klasstypsvillkor för en typparameter ska vara minst lika tillgängligt som den medlem som deklarerar villkoret.
Exempel: I följande kod
class A {...} public class B: A {...}
-
B
klassen resulterar i ett kompileringsfel eftersomA
den inte är minst lika tillgänglig somB
.slutexempel
Exempel: På samma sätt i följande kod
class A {...} public class B { A F() {...} internal A G() {...} public A H() {...} }
metoden
H
i resulterar iB
ett kompileringsfel eftersom returtypenA
inte är minst lika tillgänglig som metoden.slutexempel
7.6 Signaturer och överlagring
Metoder, instanskonstruktorer, indexerare och operatorer kännetecknas av sina signaturer:
- Signaturen för en metod består av namnet på metoden, antalet typparametrar och typ- och parameteröverföringsläget för var och en av dess parametrar, i den ordning som anges från vänster till höger. För dessa ändamål identifieras alla typparametrar för metoden som inträffar i typen av en parameter inte med dess namn, utan av dess ordningstalsposition i typparameterlistan för metoden. Signaturen för en metod inkluderar specifikt inte returtyp, parameternamn, typparameternamn, typparameterbegränsningar,
params
parametermodifierare ellerthis
om parametrar krävs eller är valfria. - Signaturen för en instanskonstruktor består av typ- och parameteröverföringsläget för var och en av dess parametrar, som beaktas i ordningen från vänster till höger. Signaturen för en instanskonstruktor innehåller inte den
params
modifierare som kan anges för parametern höger mest, eller om parametrar krävs eller är valfria. - Signaturen för en indexerare består av typen av var och en av dess parametrar, som beaktas i ordningen från vänster till höger. Signaturen för en indexerare inkluderar inte elementtypen, och den innehåller inte heller den
params
modifierare som kan anges för den högra parametern, eller om parametrar krävs eller är valfria. - Signaturen för en operator består av namnet på operatorn och typen av var och en av dess parametrar, som beaktas i ordningen från vänster till höger. Signaturen för en operator innehåller inte resultattypen.
- Signaturen för en konverteringsoperator består av källtypen och måltypen. Den implicita eller explicita klassificeringen av en konverteringsoperator ingår inte i signaturen.
- Två signaturer av samma medlemstyp (metod, instanskonstruktor, indexerare eller operator) anses vara samma signaturer om de har samma namn, antal typparametrar, antal parametrar och parameteröverföringslägen och en identitetskonvertering finns mellan typerna av motsvarande parametrar (§10.2.2).
Signaturer är den aktiverande mekanismen för överlagring av medlemmar i klasser, structs och gränssnitt:
- Överlagring av metoder gör det möjligt för en klass, struct eller ett gränssnitt att deklarera flera metoder med samma namn, förutsatt att deras signaturer är unika inom den klassen, struct eller gränssnittet.
- Överlagring av instanskonstruktorer tillåter en klass eller struct att deklarera flera instanskonstruktorer, förutsatt att deras signaturer är unika inom den klassen eller structen.
- Överlagring av indexerare tillåter att en klass, struct eller ett gränssnitt deklarerar flera indexerare, förutsatt att deras signaturer är unika inom den klassen, structen eller gränssnittet.
- Överlagring av operatorer tillåter en klass eller struct att deklarera flera operatorer med samma namn, förutsatt att deras signaturer är unika inom den klassen eller structen.
Även om in
, out
och ref
parametermodifierare anses vara en del av en signatur, kan medlemmar som deklarerats i en enda typ inte skilja sig åt i signaturen enbart efter in
, out
och ref
. Ett kompileringsfel uppstår om två medlemmar deklareras i samma typ med signaturer som skulle vara desamma om alla parametrar i båda metoderna med out
eller in
modifierare ändrades till ref
modifierare. För andra syften med signaturmatchning (t.ex. döljande eller åsidosättande), in
, out
, och ref
betraktas som en del av signaturen och matchar inte varandra.
Obs! Den här begränsningen är att tillåta att C#-program enkelt kan översättas till att köras på Common Language Infrastructure (CLI), vilket inte ger ett sätt att definiera metoder som skiljer sig enbart i
in
,out
ochref
. slutkommentar
Typerna object
och dynamic
särskiljs inte vid jämförelse av signaturer. Därför tillåts inte medlemmar som deklareras i en enda typ vars signaturer skiljer sig åt genom att object
ersätta med dynamic
.
Exempel: I följande exempel visas en uppsättning överlagrade metoddeklarationer tillsammans med deras signaturer.
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 }
Observera att alla
in
,out
ochref
parametermodifierare (§15.6.2) ingår i en signatur.F(int)
Därför är ,F(in int)
,F(out int)
ochF(ref int)
alla unika signaturer. Men ,F(in int)
F(out int)
, ochF(ref int)
kan inte deklareras i samma gränssnitt eftersom deras signaturer skiljer sig enbartin
efter ,out
ochref
. Observera också att returtypen ochparams
modifieraren inte ingår i en signatur, så det går inte att överbelasta enbart baserat på returtyp eller på inkludering eller uteslutning avparams
modifieraren. Därför resulterar deklarationerna av metodernaF(int)
ochF(params string[])
som identifieras ovan i ett kompileringsfel. slutexempel
7.7 Omfattningar
7.7.1 Allmänt
Omfånget för ett namn är den region i programtexten där det är möjligt att referera till den entitet som deklareras med namnet utan att namnet har kvalificerats. Omfång kan kapslas och ett inre omfång kan omdeklareras innebörden av ett namn från ett yttre omfång. (Detta tar dock inte bort den begränsning som föreskrivs i §7.3 att det inom ett kapslat block inte går att deklarera en lokal variabel eller lokal konstant med samma namn som en lokal variabel eller lokal konstant i ett omslutande block.) Namnet från det yttre omfånget sägs sedan vara dolt i området för programtext som omfattas av det inre omfånget, och åtkomst till det yttre namnet är endast möjligt genom att kvalificera namnet.
Omfånget för en namnområdesmedlem som deklareras av en namespace_member_declaration (§14.6) utan att omsluta namespace_declaration är hela programtexten.
Omfånget för en namnområdesmedlem som deklareras av en namespace_member_declaration inom en namespace_declaration vars fullständigt kvalificerade namn är
N
, är namespace_body för varje namespace_declaration vars fullständigt kvalificerade namn ärN
eller börjar medN
, följt av en period.Omfånget för ett namn som definieras av en extern_alias_directive (§14.4) sträcker sig över using_directives, global_attributes och namespace_member_declarationav dess omedelbart innehållande compilation_unit eller namespace_body. En extern_alias_directive bidrar inte med några nya medlemmar till det underliggande deklarationsutrymmet. Med andra ord är en extern_alias_directive inte transitiv, utan påverkar snarare bara de compilation_unit eller namespace_body där den inträffar.
Omfånget för ett namn som definierats eller importerats av en using_directive (§14.5) sträcker sig över global_attributes och namespace_member_declarationi compilation_unit eller namespace_body där using_directive inträffar. En using_directive kan göra noll eller fler namnområden eller typnamn tillgängliga inom en viss compilation_unit eller namespace_body, men bidrar inte med några nya medlemmar till det underliggande deklarationsutrymmet. Med andra ord är en using_directive inte transitiv utan påverkar bara compilation_uniteller namespace_body där den inträffar.
Omfånget för en typparameter som deklareras av en type_parameter_list på en class_declaration (§15.2) är class_base, type_parameter_constraints_clauses och class_body för den class_declaration.
Obs! Till skillnad från medlemmar i en klass utökas inte det här omfånget till härledda klasser. slutkommentar
Omfånget för en typparameter som deklareras av en type_parameter_list på en struct_declaration (§16.2) är struct_interfaces, type_parameter_constraints_clauses och struct_body för den struct_declaration.
Omfånget för en typparameter som deklareras av en type_parameter_list på en interface_declaration (§18.2) är interface_base, type_parameter_constraints_clauses och interface_body av den interface_declaration.
Omfånget för en typparameter som deklareras av en type_parameter_list på en delegate_declaration (§20.2) är return_type, parameter_list och type_parameter_constraints_clausei den delegate_declaration.
Omfånget för en typparameter som deklareras av en type_parameter_list på en method_declaration (§15.6.1) är method_declaration.
Omfånget för en medlem som deklareras av en class_member_declaration (§15.3.1) är den class_body i vilken deklarationen sker. Dessutom omfattar omfattningen för en klassmedlem class_body av de härledda klasserna som ingår i medlemmens tillgänglighetsdomän (§7.5.3).
Omfånget för en medlem som deklareras av en struct_member_declaration (§16.3) är den struct_body i vilken deklarationen sker.
Omfånget för en medlem som deklareras av en enum_member_declaration (§19.4) är den enum_body i vilken deklarationen inträffar.
Omfånget för en parameter som deklareras i en method_declaration (§15.6) är method_bodyeller ref_method_body för den method_declaration.
Omfånget för en parameter som deklareras i en indexer_declaration (§15.9) är indexer_bodyför den indexer_declaration.
Omfånget för en parameter som deklareras i en operator_declaration (§15.10) är operator_body för den operator_declaration.
Omfånget för en parameter som deklareras i en constructor_declaration (§15.11) är constructor_initializer och blocket för den constructor_declaration.
Omfånget för en parameter som deklareras i en lambda_expression (§12.19) är lambda_expression_body för den lambda_expression.
Omfånget för en parameter som deklareras i en anonymous_method_expression (§12.19) är blocket för den anonymous_method_expression.
Omfånget för en etikett som deklareras i en labeled_statement (§13.5) är det block där deklarationen inträffar.
Omfånget för en lokal variabel som deklareras i en local_variable_declaration (§13.6.2) är det block där deklarationen sker.
Omfånget för en lokal variabel som deklareras i en switch_block av en
switch
instruktion (§13.8.3) är switch_block.Omfånget för en lokal variabel som deklareras i en for_initializer av en
for
instruktion (§13.9.4) är for_initializer, for_condition, for_iterator och embedded_statement av -instruktionenfor
.Omfånget för en lokal konstant som deklareras i en local_constant_declaration (§13.6.3) är det block där deklarationen inträffar. Det är ett kompileringsfel att referera till en lokal konstant i en textposition som föregår dess constant_declarator.
Omfånget för en variabel som deklareras som en del av en foreach_statement, using_statement, lock_statement eller query_expression bestäms av expansionen av den angivna konstruktionen.
Inom omfånget för en namnrymd, klass, struct eller uppräkningsmedlem är det möjligt att referera till medlemmen i en textposition som föregår medlemmens deklaration.
Exempel:
class A { void F() { i = 1; } int i = 0; }
Här är det giltigt för
F
att referera tilli
innan det deklareras.slutexempel
Inom omfånget för en lokal variabel är det ett kompileringsfel att referera till den lokala variabeln i en textposition som föregår dess deklarator.
Exempel:
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 } }
F
I metoden ovan refererar den första tilldelningen tilli
specifikt inte till det fält som deklarerats i det yttre omfånget. I stället refererar den till den lokala variabeln och resulterar i ett kompileringsfel eftersom den textmässigt föregår deklarationen av variabeln.G
I metoden är användningen avj
i initiatorn för deklarationen avj
giltig eftersom användningen inte föregår deklaratorn.H
I metoden refererar en efterföljande deklarator korrekt till en lokal variabel som deklarerats i en tidigare deklarator inom samma local_variable_declaration.slutexempel
Obs! Omfångsreglerna för lokala variabler och lokala konstanter är utformade för att garantera att innebörden av ett namn som används i en uttryckskontext alltid är densamma inom ett block. Om omfånget för en lokal variabel endast skulle utökas från deklarationen till slutet av blocket, skulle den första tilldelningen i exemplet ovan tilldela till instansvariabeln och den andra tilldelningen skulle tilldela till den lokala variabeln, vilket kan leda till kompileringsfel om blockinstruktionerna senare skulle ordnas om.)
Innebörden av ett namn i ett block kan variera beroende på i vilken kontext namnet används. I exemplet
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" } }
namnet
A
används i en uttryckskontext för att referera till den lokala variabelnA
och i en typkontext för att referera till klassenA
.slutkommentar
7.7.2 Namn som döljs
7.7.2.1 Allmänt
Omfånget för en entitet omfattar vanligtvis mer programtext än entitetens deklarationsutrymme. I synnerhet kan en entitets omfattning innehålla deklarationer som introducerar nya deklarationsutrymmen som innehåller entiteter med samma namn. Sådana deklarationer gör att den ursprungliga entiteten döljs. Omvänt sägs en entitet vara synlig när den inte är dold.
Namn som döljs uppstår när omfång överlappar genom kapsling och när omfång överlappar genom arv. Egenskaperna för de två typerna av döljande beskrivs i följande underetiketter.
7.7.2.2 Dölja genom kapsling
Namn som döljs genom kapsling kan inträffa som ett resultat av kapsling av namnområden eller typer i namnområden, till följd av kapslingstyper inom klasser eller structs, som ett resultat av en lokal funktion eller en lambda, och som ett resultat av parameter, lokal variabel och lokala konstanta deklarationer.
Exempel: I följande kod
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; } }
F
i metoden döljs instansvariabelni
av den lokala variabelni
, men inomG
metodeni
refererar den fortfarande till instansvariabeln. Inuti den lokala funktionenM1
float i
döljer den omedelbara yttrei
. Lambda-parameterni
döljerfloat i
insidan av lambdakroppen.slutexempel
När ett namn i ett inre omfång döljer ett namn i ett yttre omfång döljer det alla överlagrade förekomster av det namnet.
Exempel: I följande kod
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 } } }
anropet
F(1)
anropar denF
deklarerade iInner
eftersom alla yttre förekomster avF
är dolda av den inre deklarationen. Av samma anledning resulterar anropetF("Hello")
i ett kompileringsfel.slutexempel
7.7.2.3 Gömma sig genom arv
Namn som döljs genom arv inträffar när klasser eller structs omdeklarerat namn som ärvts från basklasser. Den här typen av namn som döljs har något av följande formulär:
- En konstant, fält, egenskap, händelse eller typ som introduceras i en klass eller struct döljer alla basklassmedlemmar med samma namn.
- En metod som introduceras i en klass eller struct döljer alla icke-metodbasklassmedlemmar med samma namn och alla basklassmetoder med samma signatur (§7.6).
- En indexerare som introduceras i en klass eller struct döljer alla basklassindexerare med samma signatur (§7.6) .
Reglerna för operatordeklarationer (§15.10) gör det omöjligt för en härledd klass att deklarera en operator med samma signatur som en operatör i en basklass. Därför döljer operatorerna aldrig varandra.
I motsats till att dölja ett namn från ett yttre omfång, orsakar döljande av ett synligt namn från ett ärvt omfång en varning rapporteras.
Exempel: I följande kod
class Base { public void F() {} } class Derived : Base { public void F() {} // Warning, hiding an inherited name }
deklarationen av
F
iDerived
gör att en varning rapporteras. Att dölja ett ärvt namn är specifikt inte ett fel, eftersom det skulle utesluta en separat utveckling av basklasser. Ovanstående situation kan till exempel ha uppkommit eftersom en senare version avBase
introducerade enF
metod som inte fanns i en tidigare version av klassen.slutexempel
Varningen som orsakas av att ett ärvt namn döljs kan elimineras med hjälp av new
modifieraren:
Exempel:
class Base { public void F() {} } class Derived : Base { public new void F() {} }
Modifieraren
new
anger attF
inDerived
är "ny" och att den verkligen är avsedd att dölja den ärvda medlemmen.slutexempel
En deklaration av en ny medlem döljer endast en ärvd medlem inom den nya medlemmens omfång.
Exempel:
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 } }
I exemplet ovan döljer indeklarationen som ärvts från
F
, men eftersom den nyaDerived
inF
har privat åtkomst utökas inte dess omfång tillBase
.F
Derived
MoreDerived
Därför är anropetF()
iMoreDerived.G
giltigt och anroparBase.F
.slutexempel
7.8 Namnområde och typnamn
7.8.1 Allmänt
Flera kontexter i ett C#-program kräver att en namespace_name eller en type_name anges.
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
;
En namespace_name är en namespace_or_type_name som refererar till ett namnområde.
Efter en lösning enligt beskrivningen nedan ska namespace_or_type_name för en namespace_name referera till ett namnområde, eller på annat sätt uppstår ett kompileringstidsfel. Inga typargument (§8.4.2) kan finnas i en namespace_name (endast typer kan ha typargument).
En type_name är en namespace_or_type_name som refererar till en typ. Efter en lösning enligt beskrivningen nedan ska namespace_or_type_name av en type_name referera till en typ, eller på annat sätt uppstår ett kompileringsfel.
Om namespace_or_type_name är en qualified_alias_member är dess innebörd enligt beskrivningen i §14.8.1. I annat fall har en namespace_or_type_name ett av fyra formulär:
I
I<A₁, ..., Aₓ>
N.I
N.I<A₁, ..., Aₓ>
där I
är en enda identifierare, N
är en namespace_or_type_name och <A₁, ..., Aₓ>
är en valfri type_argument_list. När ingen type_argument_list har angetts bör du överväga x
att vara noll.
Innebörden av en namespace_or_type_name bestäms på följande sätt:
- Om namespace_or_type_name är en qualified_alias_member är innebörden enligt §14.8.1.
- Annars, om namespace_or_type_name är av formuläret
I
eller formuläretI<A₁, ..., Aₓ>
:- Om
x
är noll och namespace_or_type_name visas inom en allmän metoddeklaration (§15.6) men utanför attributenför dess metodhuvud, och om den deklarationen innehåller en typparameter (§15.2.3) med namnetI
, refererar namespace_or_type_name till den typparametern. - Annars, om namespace_or_type_name visas inom en typdeklaration, sedan för varje instanstyp
T
(§15.3.2), börjar med instanstypen för den typdeklarationen och fortsätter med instanstypen för varje omslutande klass eller structdeklaration (om någon):- Om
x
är noll och deklarationen avT
innehåller en typparameter med namnetI
refererar namespace_or_type_name till den typparametern. - Annars, om namespace_or_type_name visas i brödtexten i typdeklarationen, eller
T
någon av dess bastyper innehåller en kapslad tillgänglig typ med namnI
ochx
typparametrar, refererar namespace_or_type_name till den typ som skapats med de angivna typargumenten. Om det finns fler än en sådan typ väljs den typ som deklarerats inom den mer härledda typen.
Obs! Icke-typmedlemmar (konstanter, fält, metoder, egenskaper, indexerare, operatorer, instanskonstruktorer, finalizers och statiska konstruktorer) och typmedlemmar med ett annat antal typparametrar ignoreras när innebörden av namespace_or_type_name fastställs. slutkommentar
- Om
- I annat fall utvärderas följande steg tills en entitet finns för varje namnområde
N
som börjar med namnområdet där namespace_or_type_name inträffar, fortsätter med varje omslutande namnområde (om det finns) och slutar med det globala namnområdet:- Om
x
är noll ochI
är namnet på ett namnområde iN
, så:- Om platsen där namespace_or_type_name inträffar omges av en namnområdesdeklaration för
N
och namnområdesdeklarationen innehåller en extern_alias_directive eller using_alias_directive som associerar namnetI
med ett namnområde eller typ, är namespace_or_type_name tvetydig och ett kompileringsfel inträffar. - Annars refererar namespace_or_type_name till namnområdet med namnet
I
iN
.
- Om platsen där namespace_or_type_name inträffar omges av en namnområdesdeklaration för
- Annars, om
N
innehåller en tillgänglig typ med namnI
ochx
typparametrar, så:- Om
x
är noll och platsen där namespace_or_type_name inträffar omges av en namnområdesdeklaration förN
och namnområdesdeklarationen innehåller en extern_alias_directive eller using_alias_directive som associerar namnetI
med ett namnområde eller typ, är namespace_or_type_name tvetydig och ett kompileringsfel inträffar. - Annars refererar namespace_or_type_name till den typ som skapats med de angivna typargumenten.
- Om
- Annars om platsen där namespace_or_type_name inträffar omges av en namnområdesdeklaration för
N
:- Om
x
är noll och namnområdesdeklarationen innehåller en extern_alias_directive eller using_alias_directive som associerar namnetI
med ett importerat namnområde eller typ refererar namespace_or_type_name till namnområdet eller typen. - Annars, om namnrymderna som importeras av using_namespace_directivei namnområdesdeklarationen innehåller exakt en typ med namn
I
ochx
typparametrar, refererar namespace_or_type_name till den typen som skapats med de angivna typargumenten. - Om namnrymderna som importeras av using_namespace_directivei namnområdesdeklarationen innehåller mer än en typ med namn
I
- ochx
typparametrar , är annars namespace_or_type_name tvetydig och ett fel inträffar.
- Om
- Om
- Annars är namespace_or_type_name odefinierat och ett kompileringsfel inträffar.
- Om
- Annars är namespace_or_type_name av formuläret
N.I
eller formuläretN.I<A₁, ..., Aₓ>
.N
löses först som en namespace_or_type_name. Om lösningen påN
inte lyckas uppstår ett kompileringsfel. Annars ellerN.I
N.I<A₁, ..., Aₓ>
löses på följande sätt:- Om
x
är noll ochN
refererar till ett namnområde ochN
innehåller ett kapslat namnområde med namnetI
refererar namespace_or_type_name till det kapslade namnområdet. - Annars, om
N
refererar till ett namnområde ochN
innehåller en tillgänglig typ med namnI
- ochx
typparametrar, refererar namespace_or_type_name till den typen som skapats med de angivna typargumenten. - Annars, om
N
refererar till en (eventuellt konstruerad) klass eller structtyp ochN
eller någon av dess basklasser innehåller en kapslad tillgänglig typ med namnI
ochx
typparametrar, refererar namespace_or_type_name till den typ som skapats med de angivna typargumenten. Om det finns fler än en sådan typ väljs den typ som deklarerats inom den mer härledda typen.Obs! Om innebörden av
N.I
bestäms som en del av lösningen avN
basklassspecifikationen för anses den direkta basklassen förN
varaobject
(§15.2.4.2). slutkommentar - Annars
N.I
är ett ogiltigt namespace_or_type_name och ett kompileringsfel inträffar.
- Om
En namespace_or_type_name får endast referera till en statisk klass (§15.2.2.4) om
- Namespace_or_type_name är
T
i ett namespace_or_type_name i formuläretT.I
, eller -
namespace_or_type_name är
T
i en typeof_expression (§12.8.18) av formulärettypeof(T)
7.8.2 Okvalificerade namn
Varje namnområdesdeklaration och typdeklaration har ett okvalificerat namn som fastställs på följande sätt:
- För en namnområdesdeklaration är det okvalificerade namnet den qualified_identifier som anges i deklarationen.
- För en typdeklaration utan type_parameter_list är det okvalificerade namnet den identifierare som anges i deklarationen.
- För en typdeklaration med K-typparametrar är det okvalificerade namnet den identifierare som anges i deklarationen, följt av generisk dimensionsspecificerare (§12.8.18) för K-typparametrar.
7.8.3 Fullständigt kvalificerade namn
Varje namnområde och typdeklaration har ett fullständigt kvalificerat namn som unikt identifierar namnområdet eller typdeklarationen bland alla andra i programmet. Det fullständigt kvalificerade namnet på ett namnområde eller en typdeklaration med okvalificerat namn N
bestäms på följande sätt:
- Om
N
är medlem i det globala namnområdet ärN
dess fullständigt kvalificerade namn . - Annars är
S.N
dess fullständigt kvalificerade namn , därS
är det fullständigt kvalificerade namnet på namnområdet eller typdeklarationen somN
deklareras i.
Med andra ord är det fullständigt kvalificerade namnet N
på den fullständiga hierarkiska sökvägen för identifierare och generic_dimension_specifiersom leder till N
, med början från det globala namnområdet. Eftersom varje medlem i ett namnområde eller en typ ska ha ett unikt namn, följer det att det fullständigt kvalificerade namnet på ett namnområde eller typdeklaration alltid är unikt. Det är ett kompileringsfel för samma fullständigt kvalificerade namn för att referera till två distinkta entiteter. Framför allt:
- Det är ett fel för både en namnområdesdeklaration och en typdeklaration att ha samma fullständigt kvalificerade namn.
- Det är ett fel att två olika typer av typdeklarationer har samma fullständigt kvalificerade namn (till exempel om både en struct- och klassdeklaration har samma fullständigt kvalificerade namn).
- Det är ett fel för en typdeklaration utan den partiella modifieraren att ha samma fullständigt kvalificerade namn som en annan typdeklaration (§15.2.7).
Exempel: Exemplet nedan visar flera namnområden och typdeklarationer tillsammans med deras associerade fullständigt kvalificerade namn.
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<> } }
slutexempel
7.9 Automatisk minneshantering
C# använder automatisk minneshantering, vilket frigör utvecklare från att manuellt allokera och frigöra minne som upptas av objekt. Principer för automatisk minneshantering implementeras av en skräpinsamlare. Livscykeln för minneshantering för ett objekt är följande:
- När objektet skapas allokeras minne för det, konstruktorn körs och objektet betraktas som live.
- Om varken objektet eller något av dess instansfält kan nås genom någon eventuell fortsättning av körningen, förutom körningen av finalizers, anses objektet inte längre vara i bruk och det blir berättigat till slutförande.
Obs! C#-kompilatorn och skräpinsamlaren kan välja att analysera kod för att avgöra vilka referenser till ett objekt som kan användas i framtiden. Om till exempel en lokal variabel som finns i omfånget är den enda befintliga referensen till ett objekt, men den lokala variabeln aldrig refereras till i någon eventuell fortsättning av körningen från den aktuella körningspunkten i proceduren, kan skräpinsamlaren (men krävs inte) behandla objektet som inte längre används. slutkommentar
- När objektet är berättigat till slutförande körs slutaktivatorn (§15.13) (om det finns någon) för objektet vid något ospecificerat senare tillfälle. Under normala omständigheter körs slutföraren för objektet endast en gång, även om implementeringsdefinierade API:er kan tillåta att det här beteendet åsidosätts.
- När slutföraren för ett objekt har körts, om varken objektet eller något av dess instansfält kan nås genom någon eventuell fortsättning av körningen, inklusive körning av finalizers, anses objektet vara otillgängligt och objektet blir berättigat till samling.
Obs! Ett objekt som tidigare inte kunde nås kan bli tillgängligt igen på grund av dess finalizer. Ett exempel på detta finns nedan. slutkommentar
- När objektet har kvalificerats för insamling frigör skräpinsamlaren slutligen det minne som är associerat med objektet.
Skräpinsamlaren underhåller information om objektanvändning och använder den här informationen för att fatta beslut om minneshantering, till exempel var i minnet ett nyligen skapat objekt ska hittas, när ett objekt ska flyttas och när ett objekt inte längre används eller är otillgängligt.
Precis som andra språk som förutsätter att det finns en skräpinsamlare är C# utformat så att skräpinsamlaren kan implementera en mängd olika principer för minneshantering. C# anger varken en tidsbegränsning inom det intervallet eller en ordning i vilken finalizers körs. Huruvida slutförare körs som en del av programavslut är implementeringsdefinierat (§7.2).
Beteendet för skräpinsamlaren kan till viss del styras via statiska metoder i klassen System.GC
. Den här klassen kan användas för att begära att en samling ska ske, slutförare som ska köras (eller inte köras) och så vidare.
Exempel: Eftersom skräpinsamlaren tillåts bred latitud för att bestämma när objekt ska samlas in och köra finalizers kan en implementering som överensstämmer generera utdata som skiljer sig från den som visas i följande kod. Programmet
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(); } }
skapar en instans av klassen
A
och en instans av klassenB
. Dessa objekt blir berättigade till skräpinsamling när variabelnb
tilldelas värdetnull
, eftersom det efter den här tiden är omöjligt för någon användarskriven kod att komma åt dem. Utdata kan vara antingenFinalize instance of A Finalize instance of B
eller
Finalize instance of B Finalize instance of A
eftersom språket inte medför några begränsningar för i vilken ordning objekt samlas in.
I subtila fall kan skillnaden mellan "berättigande till slutförande" och "berättigad till insamling" vara viktig. Ett exempel:
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"); } } }
Om skräpinsamlaren i programmet ovan väljer att köra slutversionen av
A
före slutföraren avB
kan utdata från det här programmet vara:Finalize instance of A Finalize instance of B A.F RefA is not null
Observera att även om instansen av
A
inte användes ochA
"s finalizer kördes, är det fortfarande möjligt att metoderA
för (i det här falletF
) anropas från en annan finalator. Observera också att körningen av en finalator kan leda till att ett objekt kan användas från huvudlinjeprogrammet igen. I det här fallet orsakade körningen avB
's finalizer en instans avA
som tidigare inte användes, att bli tillgänglig från livereferensenTest.RefA
. Efter anropet till är instansen av berättigad tillWaitForPendingFinalizers
samling, men instansen avB
är inte det på grund av referensenA
.Test.RefA
slutexempel
7.10 Körningsordning
Körningen av ett C#-program fortsätter så att biverkningarna av varje körningstråd bevaras vid kritiska körningsplatser. En bieffekt definieras som en läsning eller skrivning av ett flyktigt fält, en skrivning till en icke-flyktig variabel, en skrivning till en extern resurs och genererar ett undantag. De kritiska körningspunkter där ordningen på dessa biverkningar ska bevaras är referenser till flyktiga fält (§15.5.4), lock
instruktioner (§13.13) och trådskapande och avslutning. Körningsmiljön kan ändra körningsordningen för ett C#-program, med följande begränsningar:
- Databeroende bevaras i en körningstråd. Värdet för varje variabel beräknas som om alla instruktioner i tråden kördes i ursprunglig programordning.
- Regler för initieringsbeställning bevaras (§15.5.5, §15.5.6).
- Ordningen på biverkningar bevaras med avseende på flyktiga läsningar och skrivningar (§15.5.4). Dessutom behöver körningsmiljön inte utvärdera en del av ett uttryck om den kan dra slutsatsen att uttryckets värde inte används och att inga nödvändiga biverkningar skapas (inklusive eventuella som orsakas av att anropa en metod eller komma åt ett flyktigt fält). När programkörningen avbryts av en asynkron händelse (till exempel ett undantag som utlöses av en annan tråd) är det inte säkert att de observerbara biverkningarna visas i den ursprungliga programordningen.
ECMA C# draft specification