Dela via


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 är System.Threading.Tasks.Task eller System.Threading.Tasks.Task<int>.
  • Returtypen ska vara void, int, System.Threading.Tasks.Taskeller System.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. En async void metod, eller en async metod som returnerar en annan väntande typ, till ValueTask exempel eller ValueTask<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 är System.Threading.Tasks.Taskär returtypen för den syntetiserade metoden void
  • Om returtypen för Main metoden är System.Threading.Tasks.Task<int>är returtypen för den syntetiserade metoden int

Körningen av den syntetiserade metoden fortsätter enligt följande:

  • Den syntetiserade metoden anropar Main metoden och skickar dess string[] parametervärde som ett argument om Main 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 misslyckas GetResult() utlöser ett undantag och det här undantaget sprids av den syntetiserade metoden.
  • För en Main metod med returtypen System.Threading.Tasks.Task<int>, returneras värdet som returneras av int 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 intanvä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 0statuskoden 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 variabeln z 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 och Megacorp.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 och G därför i ett kompileringsfel eftersom namnet i deklareras i det yttre blocket och inte kan redeclared i det inre blocket. Metoderna och H är dock I giltiga eftersom de två ideklareras 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 klassen object ä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 av public är "åtkomsten är inte begränsad".
  • Skyddad, som väljs genom att inkludera en protected modifierare i medlemsdeklarationen. Den intuitiva innebörden av protected ä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 av internal är "åtkomst begränsad till den här sammansättningen".
  • Skyddat internt, som väljs genom att inkludera både en protected och en internal modifierare i medlemsdeklarationen. Den intuitiva innebörden av protected 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 en protected modifierare i medlemsdeklarationen. Den intuitiva innebörden av private 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 av private ä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 eller internal deklarerat tillgänglighet och standardvärdet för internal 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 eller internal deklarerat tillgänglighet. slutkommentar

  • Struct-medlemmar kan ha public, internaleller private deklarerad tillgänglighet och standardvärdet private för deklarerad tillgänglighet eftersom structs är implicit förseglade. Struct-medlemmar som introduceras i en struct (dvs. inte ärvd av den structen) kan inte ha protected, protected internaleller private protected deklarerad tillgänglighet.

    Obs! En typ som deklareras som medlem i en struct kan ha public, internaleller private deklarerad tillgänglighet, medan en typ som deklareras som medlem i ett namnområde bara public kan ha eller internal 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, inteller 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änen T för programtexten P för och alla program som refererar till P.
  • Om den deklarerade tillgängligheten T för är intern är tillgänglighetsdomänen T för programtexten P.

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 Pdefinieras på följande sätt (notera att M det kan vara en typ):

  • Om den deklarerade tillgängligheten M för är publicär tillgänglighetsdomänen M för tillgänglighetsdomänen Tför .
  • Om den deklarerade tillgängligheten för är Mska vi protected internal vara en union av programtexten för D och programtexten av alla typer som härletts från P, som deklareras utanför T.P Tillgänglighetsdomänen M för är skärningspunkten för tillgänglighetsdomänen T för med D.
  • Om den deklarerade tillgängligheten för är Mska vi private protected vara skärningspunkten mellan programtexten D i och programtexten för P och alla typer som härletts från T.T Tillgänglighetsdomänen M för är skärningspunkten för tillgänglighetsdomänen T för med D.
  • Om den deklarerade tillgängligheten för är Mska vi protected vara en union av programtexten för Doch programtexten av alla typer som härletts från T.T Tillgänglighetsdomänen M för är skärningspunkten för tillgänglighetsdomänen T för med D.
  • Om den deklarerade tillgängligheten M för är internalär tillgänglighetsdomänen M i skärningspunkten för tillgänglighetsdomänen T för med programtexten Pi .
  • Om den deklarerade tillgängligheten M för är privateär tillgänglighetsdomänen M för programtexten Ti .

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 är publictillåts åtkomsten sedan.
  • I annat fall, om M är protected internal, tillåts åtkomsten om den sker inom det program där M den deklareras, eller om den sker inom en klass som härleds från klassen där M deklareras och sker via den härledda klasstypen (§7.5.4).
  • I annat fall, om M är protected, tillåts åtkomsten om den sker inom den klass där M den deklareras, eller om den sker inom en klass som härleds från den klass där M deklareras och sker via den härledda klasstypen (§7.5.4).
  • I annat fall, om M är internal, tillåts åtkomsten om den inträffar i programmet där M deklareras.
  • Om M är privatetillåts annars åtkomsten om den inträffar inom den typ som M 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 och A.X är obegränsad.
  • Tillgänglighetsdomänen A.Yför , B, B.X, B.Y, B.C, B.C.Xoch B.C.Y är programtexten för det innehållande programmet.
  • Tillgänglighetsdomänen A.Z för är programtexten Ai .
  • Tillgänglighetsdomänen för och B.Z är programtexten B.Di , inklusive programtexten B för och B.C.B.D
  • Tillgänglighetsdomänen B.C.Z för är programtexten B.Ci .
  • Tillgänglighetsdomänen för och B.D.X är programtexten B.D.Yi , inklusive programtexten B för och B.C.B.D
  • Tillgänglighetsdomänen B.D.Z för är programtexten B.Di . 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 alla X medlemmar har offentligt deklarerad tillgänglighet, har alla utom A.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 medlemmen x från A klassen. Eftersom medlemmen är privat är den endast tillgänglig i class_body av A. Därmed lyckas åtkomsten till b.x metoden A.F , men misslyckas i B.F -metoden.

slutexempel

7.5.4 Skyddad åtkomst

När en protectedprivate 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 Moch låter vara D en klass som härleds från B. I class_body av Dkan å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 åt x via instanser av både A och B, eftersom åtkomsten i båda fallen sker via en instans av A eller en klass som härletts från A. Inom är det dock Binte möjligt att komma åt x via en instans av A, eftersom A inte härleds från B.

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 medlem C<int>.x i D är giltig även om klassen D härleds från C<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 Mfö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 eftersom A den inte är minst lika tillgänglig som B.

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 i B ett kompileringsfel eftersom returtypen A 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 eller this 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, outoch 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, outoch 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, outoch ref. 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, outoch ref parametermodifierare (§15.6.2) ingår i en signatur. F(int)Därför är , F(in int), F(out int) och F(ref int) alla unika signaturer. Men , F(in int)F(out int) , och F(ref int) kan inte deklareras i samma gränssnitt eftersom deras signaturer skiljer sig enbart inefter , outoch ref. Observera också att returtypen och params modifieraren inte ingår i en signatur, så det går inte att överbelasta enbart baserat på returtyp eller på inkludering eller uteslutning av params modifieraren. Därför resulterar deklarationerna av metoderna F(int) och F(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 är N eller börjar med N, 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 till i 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 till i 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 av j i initiatorn för deklarationen av j 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 variabeln A och i en typkontext för att referera till klassen A.

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 instansvariabeln i av den lokala variabeln i, men inom G metoden i refererar den fortfarande till instansvariabeln. Inuti den lokala funktionen M1float i döljer den omedelbara yttre i. Lambda-parametern i döljer float 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 den F deklarerade i Inner eftersom alla yttre förekomster av F är dolda av den inre deklarationen. Av samma anledning resulterar anropet F("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 i Derived 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 av Base introducerade en F 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 att F in Derived ä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 nya Derived in F har privat åtkomst utökas inte dess omfång till Base.FDerivedMoreDerived Därför är anropet F() i MoreDerived.G giltigt och anropar Base.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äret I<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 namnet I, 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 av T innehåller en typparameter med namnet Irefererar 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 namn I och x 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

    • I annat fall utvärderas följande steg tills en entitet finns för varje namnområde Nsom 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 och I är namnet på ett namnområde i N, 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 namnet I 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 i N.
      • Annars, om N innehåller en tillgänglig typ med namn I och x typparametrar, så:
        • Om x är noll och 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 namnet I 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.
      • 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 namnet I 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 och x 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 - och x typparametrar , är annars namespace_or_type_name tvetydig och ett fel inträffar.
    • Annars är namespace_or_type_name odefinierat och ett kompileringsfel inträffar.
  • Annars är namespace_or_type_name av formuläret N.I eller formuläret N.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 eller N.IN.I<A₁, ..., Aₓ> löses på följande sätt:
    • Om x är noll och N refererar till ett namnområde och N innehåller ett kapslat namnområde med namnet Irefererar namespace_or_type_name till det kapslade namnområdet.
    • Annars, om N refererar till ett namnområde och N innehåller en tillgänglig typ med namn I - och x 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 och N eller någon av dess basklasser innehåller en kapslad tillgänglig typ med namn I och x 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 av N basklassspecifikationen för anses den direkta basklassen för N vara object (§15.2.4.2). slutkommentar

    • Annars N.I är ett ogiltigt namespace_or_type_name och ett kompileringsfel inträffar.

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äret T.I, eller
  • namespace_or_type_name är T i en typeof_expression (§12.8.18) av formuläret typeof(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 är Ndess fullständigt kvalificerade namn .
  • Annars är S.Ndess fullständigt kvalificerade namn , där S är det fullständigt kvalificerade namnet på namnområdet eller typdeklarationen som N 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:

  1. När objektet skapas allokeras minne för det, konstruktorn körs och objektet betraktas som live.
  2. 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

  3. 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.
  4. 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

  5. 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 klassen B. Dessa objekt blir berättigade till skräpinsamling när variabeln b tilldelas värdet null, eftersom det efter den här tiden är omöjligt för någon användarskriven kod att komma åt dem. Utdata kan vara antingen

Finalize 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 av Bkan 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 och A"s finalizer kördes, är det fortfarande möjligt att metoder A för (i det här fallet F) 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 av B's finalizer en instans av A som tidigare inte användes, att bli tillgänglig från livereferensen Test.RefA. Efter anropet till är instansen av berättigad till WaitForPendingFinalizerssamling, men instansen av B är inte det på grund av referensen A.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.