Condividi tramite


7 Concetti di base

7.1 Avvio dell'applicazione

Un programma può essere compilato come libreria di classi da usare come parte di altre applicazioni o come applicazione che può essere avviata direttamente. Il meccanismo per determinare questa modalità di compilazione è definito dall'implementazione ed esterno a questa specifica.

Un programma compilato come applicazione deve contenere almeno un metodo idoneo come punto di ingresso soddisfacendo i requisiti seguenti:

  • Deve avere il nome Main.
  • Deve essere static.
  • Non deve essere generico.
  • Deve essere dichiarato in un tipo non generico. Se il tipo che dichiara il metodo è un tipo annidato, nessuno dei relativi tipi di inclusione può essere generico.
  • Può avere il async modificatore a condizione che il tipo restituito del metodo sia System.Threading.Tasks.Task o System.Threading.Tasks.Task<int>.
  • Il tipo restituito deve essere void, int, System.Threading.Tasks.Tasko System.Threading.Tasks.Task<int>.
  • Non deve essere un metodo parziale (§15.6.9) senza un'implementazione.
  • L'elenco di parametri deve essere vuoto oppure avere un singolo parametro di valore di tipo string[].

Nota: i metodi con il async modificatore devono avere esattamente uno dei due tipi restituiti specificati in precedenza per qualificarsi come punto di ingresso. Un async void metodo o un async metodo che restituisce un tipo awaitable diverso, ValueTask ad esempio o ValueTask<int> non è qualificato come punto di ingresso. nota finale

Se più di un metodo qualificato come punto di ingresso viene dichiarato all'interno di un programma, è possibile usare un meccanismo esterno per specificare quale metodo viene considerato il punto di ingresso effettivo per l'applicazione. Se viene trovato un metodo idoneo con un tipo restituito o int void viene trovato, qualsiasi metodo idoneo con un tipo restituito di System.Threading.Tasks.Task o System.Threading.Tasks.Task<int> non viene considerato un metodo del punto di ingresso. Si tratta di un errore in fase di compilazione per la compilazione di un programma come applicazione senza un solo punto di ingresso. Un programma compilato come libreria di classi può contenere metodi che si qualificano come punti di ingresso dell'applicazione, ma la libreria risultante non dispone di un punto di ingresso.

In genere, l'accessibilità dichiarata (§7.5.2) di un metodo è determinata dai modificatori di accesso (§15.3.6) specificati nella dichiarazione e analogamente l'accessibilità dichiarata di un tipo è determinata dai modificatori di accesso specificati nella relativa dichiarazione. Affinché un determinato metodo di un determinato tipo sia chiamabile, sia il tipo che il membro devono essere accessibili. Tuttavia, il punto di ingresso dell'applicazione è un caso speciale. In particolare, l'ambiente di esecuzione può accedere al punto di ingresso dell'applicazione indipendentemente dall'accessibilità dichiarata e indipendentemente dall'accessibilità dichiarata delle dichiarazioni di tipo contenitore.

Quando il metodo del punto di ingresso ha un tipo restituito di System.Threading.Tasks.Task o System.Threading.Tasks.Task<int>, il compilatore sintetizza un metodo punto di ingresso sincrono che chiama il metodo corrispondente Main . Il metodo sintetizzato ha parametri e tipi restituiti in base al Main metodo :

  • L'elenco di parametri del metodo sintetizzato è uguale all'elenco di parametri del Main metodo
  • Se il tipo restituito del Main metodo è System.Threading.Tasks.Task, il tipo restituito del metodo sintetizzato è void
  • Se il tipo restituito del Main metodo è System.Threading.Tasks.Task<int>, il tipo restituito del metodo sintetizzato è int

L'esecuzione del metodo sintetizzato procede come segue:

  • Il metodo sintetizzato chiama il Main metodo , passando il relativo string[] valore di parametro come argomento se il Main metodo ha un parametro di questo tipo.
  • Se il Main metodo genera un'eccezione, l'eccezione viene propagata dal metodo sintetizzato.
  • In caso contrario, il punto di ingresso sintetizzato attende il completamento dell'attività restituita, chiamando GetAwaiter().GetResult() sull'attività, usando il metodo di istanza senza parametri o il metodo di estensione descritto da §C.3. Se l'attività non riesce, GetResult() genererà un'eccezione e questa eccezione viene propagata dal metodo sintetizzato.
  • Per un Main metodo con un tipo restituito di System.Threading.Tasks.Task<int>, se l'attività viene completata correttamente, il int valore restituito da GetResult() viene restituito dal metodo sintetizzato.

Il punto di ingresso effettivo di un'applicazione è il punto di ingresso dichiarato all'interno del programma o il metodo sintetizzato, se necessario, come descritto in precedenza. Il tipo restituito del punto di ingresso effettivo è quindi sempre void o int.

Quando un'applicazione viene eseguita, viene creato un nuovo dominio applicazione. Diverse istanze di un'applicazione possono esistere nello stesso computer contemporaneamente e ognuna ha il proprio dominio applicazione. Un dominio applicazione abilita l'isolamento dell'applicazione fungendo da contenitore per lo stato dell'applicazione. Un dominio applicazione funge da contenitore e limite per i tipi definiti nell'applicazione e le librerie di classi usate. I tipi caricati in un dominio applicazione sono distinti dagli stessi tipi caricati in un altro dominio applicazione e le istanze di oggetti non vengono condivise direttamente tra i domini applicazione. Ad esempio, ogni dominio applicazione ha una propria copia di variabili statiche per questi tipi e un costruttore statico per un tipo viene eseguito al massimo una volta per ogni dominio applicazione. Le implementazioni sono libere di fornire meccanismi o criteri definiti dall'implementazione per la creazione e la distruzione dei domini applicazione.

L'avvio dell'applicazione si verifica quando l'ambiente di esecuzione chiama il punto di ingresso effettivo dell'applicazione. Se il punto di ingresso effettivo dichiara un parametro, durante l'avvio dell'applicazione, l'implementazione garantisce che il valore iniziale di tale parametro sia un riferimento non Null a una matrice di stringhe. Questa matrice deve essere costituita da riferimenti non Null alle stringhe, denominati parametri dell'applicazione, che vengono specificati valori definiti dall'implementazione dall'ambiente host prima dell'avvio dell'applicazione. Lo scopo è fornire alle informazioni sull'applicazione determinate prima dell'avvio dell'applicazione da un'altra posizione nell'ambiente ospitato.

Nota: nei sistemi che supportano una riga di comando, i parametri dell'applicazione corrispondono a quelli generalmente noti come argomenti della riga di comando. nota finale

Se il tipo restituito del punto di ingresso effettivo è int, il valore restituito dalla chiamata al metodo dall'ambiente di esecuzione viene utilizzato nella terminazione dell'applicazione (§7.2).

Oltre alle situazioni elencate in precedenza, i metodi del punto di ingresso si comportano come quelli che non sono punti di ingresso in ogni senso. In particolare, se il punto di ingresso viene richiamato in qualsiasi altro punto durante la durata dell'applicazione, ad esempio tramite chiamata al metodo regolare, non esiste una gestione speciale del metodo: se è presente un parametro, può avere un valore iniziale di nullo unnull valore non che fa riferimento a una matrice che contiene riferimenti Null. Analogamente, il valore restituito del punto di ingresso non ha un significato speciale diverso da quello della chiamata dall'ambiente di esecuzione.

7.2 Terminazione dell'applicazione

La terminazione dell'applicazione restituisce il controllo all'ambiente di esecuzione.

Se il tipo restituito del metodo del punto di ingresso effettivo dell'applicazione è e l'esecuzione viene int completata senza generare un'eccezione, il valore dell'oggetto int restituito funge da codice di stato di terminazione dell'applicazione. Lo scopo di questo codice è consentire la comunicazione dell'esito positivo o negativo dell'ambiente di esecuzione. Se il tipo restituito del metodo del punto di ingresso effettivo è e l'esecuzione viene void completata senza generare un'eccezione, il codice di stato di terminazione è 0.

Se il metodo del punto di ingresso effettivo termina a causa di un'eccezione (§21.4), il codice di uscita viene definito dall'implementazione. Inoltre, l'implementazione può fornire API alternative per specificare il codice di uscita.

Indica se i finalizzatori (§15.13) vengono eseguiti come parte della terminazione dell'applicazione è definito dall'implementazione.

Nota: l'implementazione di .NET Framework esegue ogni ragionevole sforzo per chiamare finalizzatori (§15.13) per tutti i relativi oggetti che non sono ancora stati sottoposti a Garbage Collection, a meno che tale pulizia non sia stata eliminata (ad esempio, da una chiamata al metodo GC.SuppressFinalizedi libreria). nota finale

7.3 Dichiarazioni

Le dichiarazioni in un programma C# definiscono gli elementi costitutivi del programma. I programmi C# sono organizzati usando spazi dei nomi. Questi vengono introdotti usando le dichiarazioni dello spazio dei nomi (§14), che possono contenere dichiarazioni di tipo e dichiarazioni di spazio dei nomi annidati. Le dichiarazioni di tipo (§14.7) vengono usate per definire classi (§15), struct (§16), interfacce (§18), enumerazioni (§19) e delegati (§20). I tipi di membri consentiti in una dichiarazione di tipo dipendono dal formato della dichiarazione di tipo. Ad esempio, Le dichiarazioni di classe possono contenere dichiarazioni per costanti (§15.4), campi (§15.5), metodi (§15.6), proprietà (§15.7), eventi (§15.8), indicizzatori (§15.9) operatori (§15.10), costruttori di istanza (§15.11), costruttori statici (§15.12), finalizzatori (§15.13) e tipi annidati (§15.3.9).

Una dichiarazione definisce un nome nello spazio di dichiarazione a cui appartiene la dichiarazione. Si tratta di un errore in fase di compilazione per avere due o più dichiarazioni che introducono membri con lo stesso nome in uno spazio di dichiarazione, tranne nei casi seguenti:

  • Due o più dichiarazioni dello spazio dei nomi con lo stesso nome sono consentite nello stesso spazio di dichiarazione. Tali dichiarazioni di spazio dei nomi vengono aggregate per formare un singolo spazio dei nomi logico e condividere uno spazio di dichiarazione singolo.
  • Le dichiarazioni in programmi separati ma nello stesso spazio dei nomi possono condividere lo stesso nome.

    Nota: tuttavia, queste dichiarazioni potrebbero introdurre ambiguità se incluse nella stessa applicazione. nota finale

  • Due o più metodi con lo stesso nome ma firme distinte sono consentiti nello stesso spazio di dichiarazione (§7.6).
  • Due o più dichiarazioni di tipo con lo stesso nome ma numeri distinti di parametri di tipo sono consentiti nello stesso spazio di dichiarazione (§7.8.2).
  • Due o più dichiarazioni di tipo con il modificatore parziale nello stesso spazio di dichiarazione possono condividere lo stesso nome, lo stesso numero di parametri di tipo e la stessa classificazione (classe, struct o interfaccia). In questo caso, le dichiarazioni di tipo contribuiscono a un singolo tipo e vengono aggregate per formare un singolo spazio di dichiarazione (§15.2.7).
  • Una dichiarazione dello spazio dei nomi e una dichiarazione di tipo nello stesso spazio di dichiarazione possono condividere lo stesso nome purché la dichiarazione di tipo abbia almeno un parametro di tipo (§7.8.2).

Esistono diversi tipi di spazi di dichiarazione, come descritto di seguito.

  • All'interno di tutte le unità di compilazione di un programma, namespace_member_declarationsenza inclusione namespace_declaration sono membri di un singolo spazio di dichiarazione combinato denominato spazio di dichiarazione globale.
  • All'interno di tutte le unità di compilazione di un programma, namespace_member_declarationall'interno di namespace_declarationche hanno lo stesso nome completo dello spazio dei nomi sono membri di un singolo spazio di dichiarazione combinato.
  • Ogni compilation_unit e namespace_body ha uno spazio di dichiarazione alias. Ogni extern_alias_directive e using_alias_directive del compilation_unit o namespace_body contribuisce a un membro dello spazio di dichiarazione alias (§14.5.2).
  • Ogni dichiarazione di classe, struct o interfaccia non parziale crea un nuovo spazio di dichiarazione. Ogni dichiarazione parziale di classe, struct o interfaccia contribuisce a uno spazio di dichiarazione condiviso da tutte le parti corrispondenti nello stesso programma (§16.2.4). I nomi vengono introdotti in questo spazio di dichiarazione tramite class_member_declarations, struct_member_declaration, interface_member_declarationo type_parameters. Ad eccezione delle dichiarazioni del costruttore di istanza di overload e delle dichiarazioni del costruttore statico, una classe o uno struct non può contenere una dichiarazione membro con lo stesso nome della classe o dello struct. Una classe, uno struct o un'interfaccia consente la dichiarazione di metodi e indicizzatori di overload. Inoltre, una classe o uno struct consente la dichiarazione di costruttori e operatori di istanza di overload. Ad esempio, una classe, uno struct o un'interfaccia possono contenere più dichiarazioni di metodo con lo stesso nome, a condizione che queste dichiarazioni di metodo differiscano nella firma (§7.6). Si noti che le classi di base non contribuiscono allo spazio di dichiarazione di una classe e le interfacce di base non contribuiscono allo spazio di dichiarazione di un'interfaccia. Pertanto, una classe o un'interfaccia derivata può dichiarare un membro con lo stesso nome di un membro ereditato. Tale membro viene detto di nascondere il membro ereditato.
  • Ogni dichiarazione di delegato crea un nuovo spazio di dichiarazione. I nomi vengono introdotti in questo spazio di dichiarazione tramite parametri (fixed_parameters e parameter_arrays) e type_parameters.
  • Ogni dichiarazione di enumerazione crea un nuovo spazio di dichiarazione. I nomi vengono introdotti in questo spazio di dichiarazione tramite enum_member_declarations.
  • Ogni dichiarazione di metodo, dichiarazione di proprietà, dichiarazione della funzione di accesso alle proprietà, dichiarazione dell'indicizzatore, dichiarazione della funzione di accesso dell'indicizzatore, dichiarazione dell'operatore, dichiarazione del costruttore dell'istanza, funzione anonima e funzione locale crea un nuovo spazio di dichiarazione di dichiarazione di variabile locale. I nomi vengono introdotti in questo spazio di dichiarazione tramite parametri (fixed_parameters e parameter_arrays) e type_parameters. La funzione di accesso set per una proprietà o un indicizzatore introduce il nome value come parametro. Il corpo del membro della funzione, della funzione anonima o della funzione locale, se presente, viene considerato annidato all'interno dello spazio di dichiarazione della variabile locale. Quando uno spazio di dichiarazione di variabile locale e uno spazio di dichiarazione di variabile locale annidato contengono elementi con lo stesso nome, all'interno dell'ambito del nome locale annidato, il nome locale esterno è nascosto (§7.7.1) dal nome locale annidato.
  • Altri spazi di dichiarazione di variabili locali possono verificarsi all'interno di dichiarazioni di membri, funzioni anonime e funzioni locali. I nomi vengono introdotti in questi spazi di dichiarazione tramite pattern s, declaration_expressions, declaration_statements e exception_specifiers. Gli spazi di dichiarazione delle variabili locali possono essere annidati, ma si tratta di un errore per uno spazio di dichiarazione di variabile locale locale e uno spazio di dichiarazione di variabile locale annidato per contenere elementi con lo stesso nome. Pertanto, all'interno di uno spazio di dichiarazione annidato non è possibile dichiarare una variabile locale, una funzione locale o una costante con lo stesso nome di un parametro, un parametro di tipo, una variabile locale, una funzione locale o una costante in uno spazio di dichiarazione contenitore. È possibile che due spazi di dichiarazione contengano elementi con lo stesso nome, purché nessuno degli spazi di dichiarazione contenga l'altro. Gli spazi di dichiarazione locale vengono creati dai costrutti seguenti:
    • Ogni variable_initializer in una dichiarazione di campo e proprietà introduce il proprio spazio di dichiarazione di variabile locale, che non è annidato all'interno di qualsiasi altro spazio di dichiarazione di variabile locale.
    • Il corpo di un membro di funzione, una funzione anonima o una funzione locale, se presente, crea uno spazio di dichiarazione di variabile locale considerato annidato all'interno dello spazio di dichiarazione di variabile locale della funzione.
    • Ogni constructor_initializer crea uno spazio di dichiarazione di variabile locale annidato all'interno della dichiarazione del costruttore dell'istanza. Lo spazio di dichiarazione della variabile locale per il corpo del costruttore è a sua volta annidato all'interno di questo spazio di dichiarazione di variabile locale.
    • Ogni blocco, switch_block, specific_catch_clause, iteration_statement e using_statement crea uno spazio di dichiarazione di variabile locale annidato.
    • Ogni embedded_statement che non fa parte direttamente di un statement_list crea uno spazio di dichiarazione di variabile locale annidato.
    • Ogni switch_section crea uno spazio di dichiarazione di variabile locale annidato. Tuttavia, le variabili dichiarate direttamente all'interno del statement_list del switch_section (ma non all'interno di uno spazio di dichiarazione di variabile locale annidato all'interno del statement_list) vengono aggiunte direttamente allo spazio di dichiarazione della variabile locale del switch_block contenitore, anziché quello del switch_section.
    • La traduzione sintattica di una query_expression (§12.20.3) può introdurre una o più espressioni lambda. Come funzioni anonime, ognuna di queste crea uno spazio di dichiarazione di variabile locale come descritto in precedenza.
  • Ogni blocco o switch_block crea uno spazio di dichiarazione separato per le etichette. I nomi vengono introdotti in questo spazio di dichiarazione tramite labeled_statements e i nomi vengono referenziati tramite goto_statements. Lo spazio di dichiarazione di etichetta di un blocco include tutti i blocchi annidati. Pertanto, all'interno di un blocco annidato non è possibile dichiarare un'etichetta con lo stesso nome di un'etichetta in un blocco di inclusione.

Nota: il fatto che le variabili dichiarate direttamente all'interno di un switch_section vengano aggiunte allo spazio di dichiarazione della variabile locale del switch_block anziché il switch_section può causare codice sorprendente. Nell'esempio seguente, la variabile y locale si trova nell'ambito all'interno della sezione switch per il caso predefinito, nonostante la dichiarazione venga visualizzata nella sezione switch per il caso 0. La variabile locale non rientra nell'ambito all'interno della sezione switch per il caso predefinito, perché viene introdotta nello spazio di dichiarazione della variabile z locale per la sezione switch in cui si verifica la dichiarazione.

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;
}

nota finale

L'ordine testuale in cui i nomi vengono dichiarati è in genere privo di significato. In particolare, l'ordine testuale non è significativo per la dichiarazione e l'uso di spazi dei nomi, costanti, metodi, proprietà, eventi, indicizzatori, operatori, costruttori di istanza, finalizzatori, costruttori statici e tipi. L'ordine di dichiarazione è significativo nei modi seguenti:

  • L'ordine di dichiarazione per le dichiarazioni di campo determina l'ordine in cui i relativi inizializzatori (se presenti) vengono eseguiti (§15.5.6.2, §15.5.6.3).
  • Le variabili locali devono essere definite prima di essere utilizzate (§7.7).
  • L'ordine di dichiarazione per le dichiarazioni di membri enumerazione (§19.4) è significativo quando constant_expression valori vengono omessi.

Esempio: lo spazio di dichiarazione di uno spazio dei nomi è "aperto terminato" e due dichiarazioni dello spazio dei nomi con lo stesso nome completo contribuiscono allo stesso spazio di dichiarazione. Ad esempio:

namespace Megacorp.Data
{
    class Customer
    {
        ...
    }
}

namespace Megacorp.Data
{
    class Order
    {
        ...
    }
}

Le due dichiarazioni dello spazio dei nomi precedenti contribuiscono allo stesso spazio di dichiarazione, in questo caso dichiarando due classi con i nomi Megacorp.Data.Customer completi e Megacorp.Data.Order. Poiché le due dichiarazioni contribuiscono allo stesso spazio di dichiarazione, avrebbe causato un errore in fase di compilazione se ognuna conteneva una dichiarazione di una classe con lo stesso nome.

esempio finale

Nota: come specificato in precedenza, lo spazio di dichiarazione di un blocco include tutti i blocchi annidati. Nell'esempio F seguente i metodi e G generano quindi un errore in fase di compilazione perché il nome i viene dichiarato nel blocco esterno e non può essere dichiarato nuovamente nel blocco interno. Tuttavia, i H metodi e I sono validi perché i due ivengono dichiarati in blocchi non annidati separati.

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();
        }
    }
}

nota finale

7.4 Membri

7.4.1 Generale

Gli spazi dei nomi e i tipi hanno membri.

Nota: i membri di un'entità sono generalmente disponibili tramite l'uso di un nome completo che inizia con un riferimento all'entità, seguito da un token ".", seguito dal nome del membro. nota finale

I membri di un tipo vengono dichiarati nella dichiarazione di tipo o ereditati dalla classe base del tipo. Quando un tipo eredita da una classe base, tutti i membri della classe base, ad eccezione di costruttori di istanza, finalizzatori e costruttori statici diventano membri del tipo derivato. L'accessibilità dichiarata di un membro della classe di base non controlla se il membro viene ereditato. L'ereditarietà si estende a qualsiasi membro che non sia un costruttore di istanza, un costruttore statico o un finalizzatore.

Nota: tuttavia, un membro ereditato potrebbe non essere accessibile in un tipo derivato, ad esempio a causa dell'accessibilità dichiarata (§7.5.2). nota finale

7.4.2 Membri dello spazio dei nomi

Gli spazi dei nomi e i tipi senza spazio dei nomi che non racchiudono sono membri dello spazio dei nomi globale. Corrisponde direttamente ai nomi dichiarati nello spazio di dichiarazione globale.

Gli spazi dei nomi e i tipi dichiarati all'interno di uno spazio dei nomi sono membri di tale spazio dei nomi. Corrisponde direttamente ai nomi dichiarati nello spazio delle dichiarazioni dello spazio dei nomi.

Gli spazi dei nomi non hanno restrizioni di accesso. Non è possibile dichiarare spazi dei nomi privati, protetti o interni e i nomi degli spazi dei nomi sono sempre accessibili pubblicamente.

7.4.3 Membri Struct

I membri di uno struct sono i membri dichiarati nello struct e i membri ereditati dalla classe System.ValueType base diretta dello struct e dalla classe objectbase indiretta .

I membri di un tipo semplice corrispondono direttamente ai membri del tipo struct aliasato dal tipo semplice (§8.3.5).

7.4.4 Membri dell'enumerazione

I membri di un'enumerazione sono le costanti dichiarate nell'enumerazione e i membri ereditati dalla classe System.Enum base diretta dell'enumerazione e dalle classi base indirette System.ValueType e object.

7.4.5 Membri della classe

I membri di una classe sono i membri dichiarati nella classe e i membri ereditati dalla classe base (ad eccezione della classe che non ha una classe object base). I membri ereditati dalla classe base includono le costanti, i campi, i metodi, le proprietà, gli eventi, gli indicizzatori, gli operatori e i tipi della classe base, ma non i costruttori di istanza, i finalizzatori e i costruttori statici della classe base. I membri della classe base vengono ereditati senza considerare l'accessibilità.

Una dichiarazione di classe può contenere dichiarazioni di costanti, campi, metodi, proprietà, eventi, indicizzatori, operatori, costruttori di istanza, finalizzatori, costruttori statici e tipi.

I membri di object (§8.2.3) e string (§8.2.5) corrispondono direttamente ai membri dei tipi di classe che aliasano.

7.4.6 Membri dell'interfaccia

I membri di un'interfaccia sono i membri dichiarati nell'interfaccia e in tutte le interfacce di base dell'interfaccia.

Nota: i membri della classe object non sono, in senso stretto, membri di qualsiasi interfaccia (§18.4). Tuttavia, i membri della classe object sono disponibili tramite la ricerca dei membri in qualsiasi tipo di interfaccia (§12.5). nota finale

7.4.7 Membri della matrice

I membri di una matrice sono i membri ereditati dalla classe System.Array.

7.4.8 Membri delegati

Un delegato eredita i membri dalla classe System.Delegate. Contiene inoltre un metodo denominato Invoke con lo stesso tipo restituito e lo stesso elenco di parametri specificato nella dichiarazione (§20.2). Una chiamata di questo metodo si comporta in modo identico a una chiamata di delegato (§20.6) nella stessa istanza del delegato.

Un'implementazione può fornire membri aggiuntivi, tramite ereditarietà o direttamente nel delegato stesso.

7.5 Accesso ai membri

7.5.1 Generale

Le dichiarazioni dei membri consentono il controllo sull'accesso ai membri. L'accessibilità di un membro viene stabilita dall'accessibilità dichiarata (§7.5.2) del membro combinato con l'accessibilità del tipo che lo contiene immediatamente, se presente.

Quando l'accesso a un determinato membro è consentito, il membro viene detto accessibile. Viceversa, quando l'accesso a un determinato membro non è consentito, il membro viene detto inaccessibile. L'accesso a un membro è consentito quando la posizione testuale in cui si svolge l'accesso è inclusa nel dominio di accessibilità (§7.5.3) del membro.

7.5.2 Accessibilità dichiarata

L'accessibilità dichiarata di un membro può essere una delle seguenti:

  • Public, selezionato includendo un public modificatore nella dichiarazione del membro. Il significato intuitivo di public è "accesso non limitato".
  • Protetto, selezionato includendo un protected modificatore nella dichiarazione del membro. Il significato intuitivo di protected è "accesso limitato alla classe o ai tipi contenenti derivati dalla classe contenitore".
  • Interno, selezionato includendo un internal modificatore nella dichiarazione del membro. Il significato intuitivo di internal è "accesso limitato a questo assembly".
  • Protetto interno, selezionato includendo sia un protected modificatore che un internal modificatore nella dichiarazione del membro. Il significato intuitivo di è "accessibile all'interno di protected internal questo assembly e tipi derivati dalla classe contenitore".
  • Protetto privato, selezionato includendo sia un private modificatore che un protected modificatore nella dichiarazione del membro. Il significato intuitivo di è "accessibile all'interno di private protected questo assembly dalla classe e dai tipi contenenti derivati dalla classe contenitore".
  • Privato, selezionato includendo un private modificatore nella dichiarazione del membro. Il significato intuitivo di private è "accesso limitato al tipo contenitore".

A seconda del contesto in cui viene eseguita una dichiarazione di membro, sono consentiti solo determinati tipi di accessibilità dichiarati. Inoltre, quando una dichiarazione di membro non include modificatori di accesso, il contesto in cui viene eseguita la dichiarazione determina l'accessibilità dichiarata predefinita.

  • Gli spazi dei nomi hanno public dichiarato in modo implicito l'accessibilità. Nelle dichiarazioni dello spazio dei nomi non sono consentiti modificatori di accesso.
  • I tipi dichiarati direttamente nelle unità di compilazione o negli spazi dei nomi (anziché all'interno di altri tipi) possono avere public o internal dichiarare l'accessibilità e l'impostazione predefinita per internal l'accessibilità dichiarata.
  • I membri della classe possono avere uno qualsiasi dei tipi consentiti di accessibilità dichiarata e l'impostazione predefinita per dichiarare private l'accessibilità.

    Nota: un tipo dichiarato come membro di una classe può avere uno qualsiasi dei tipi consentiti di accessibilità dichiarata, mentre un tipo dichiarato come membro di uno spazio dei nomi può avere solo public o internal dichiarato accessibilità. nota finale

  • I membri dello struct possono avere public, internalo private dichiarato accessibilità e l'impostazione predefinita per private l'accessibilità dichiarata perché gli struct sono bloccati in modo implicito. I membri struct introdotti in un oggetto struct (ovvero non ereditati da tale struct) non possono avere protected, protected internalo private protected dichiarato accessibilità.

    Nota: un tipo dichiarato come membro di uno struct può avere public, internalo dichiarato private accessibilità, mentre un tipo dichiarato come membro di uno spazio dei nomi può avere solo public o internal dichiarato accessibilità. nota finale

  • I membri dell'interfaccia hanno public dichiarato in modo implicito l'accessibilità. Non sono consentiti modificatori di accesso nelle dichiarazioni dei membri dell'interfaccia.
  • I membri dell'enumerazione hanno public dichiarato in modo implicito l'accessibilità. Nelle dichiarazioni dei membri di enumerazione non sono consentiti modificatori di accesso.

7.5.3 Domini di accessibilità

Il dominio di accessibilità di un membro è costituito dalle sezioni (possibilmente contigue) del testo del programma in cui è consentito l'accesso al membro. Ai fini della definizione del dominio di accessibilità di un membro, un membro viene detto di essere di primo livello se non viene dichiarato all'interno di un tipo e un membro viene detto annidato se viene dichiarato all'interno di un altro tipo. Inoltre, il testo del programma di un programma è definito come tutto il testo contenuto in tutte le unità di compilazione del programma e il testo del programma di un tipo è definito come tutto il testo contenuto nella type_declarationdi quel tipo (inclusi, possibilmente, i tipi annidati all'interno del tipo).

Il dominio di accessibilità di un tipo predefinito , ad esempio object, into double, è illimitato.

Il dominio di accessibilità di un tipo T non associato di primo livello (§8.4.4) dichiarato in un programma P è definito come segue:

  • Se l'accessibilità dichiarata di T è pubblica, il dominio di accessibilità di è il testo del T programma di P e qualsiasi programma che fa riferimento a P.
  • Se l'accessibilità dichiarata di T è interna, il dominio di accessibilità di è il testo del T programma di P.

Nota: da queste definizioni, segue che il dominio di accessibilità di un tipo non associato di primo livello è sempre almeno il testo del programma in cui è dichiarato tale tipo. nota finale

Il dominio di accessibilità per un tipo T<A₁, ..., Aₑ> costruito è l'intersezione del dominio di accessibilità del tipo T generico non associato e dei domini di accessibilità degli argomenti A₁, ..., Aₑdi tipo .

Il dominio di accessibilità di un membro M annidato dichiarato in un tipo T all'interno di un programma Pè definito come segue (notando che M potrebbe essere un tipo):

  • Se l'accessibilità dichiarata di M è public, il dominio di accessibilità di M è il dominio di accessibilità di T.
  • Se l'accessibilità dichiarata di M è protected internal, lasciare D che sia l'unione del testo del programma di P e il testo del programma di qualsiasi tipo derivato da T, dichiarato all'esterno Pdi . Il dominio di accessibilità di M è l'intersezione del dominio di accessibilità di T con D.
  • Se l'accessibilità dichiarata di M è private protected, lasciare D che sia l'intersezione del testo del programma di P e il testo del programma di T e qualsiasi tipo derivato da T. Il dominio di accessibilità di M è l'intersezione del dominio di accessibilità di T con D.
  • Se l'accessibilità dichiarata di M è , consentire D l'unione del testo del programma di Te il testo del programma di qualsiasi tipo derivato da Tprotected. Il dominio di accessibilità di M è l'intersezione del dominio di accessibilità di T con D.
  • Se l'accessibilità dichiarata di M è internal, il dominio di accessibilità di M è l'intersezione del dominio di accessibilità di T con il testo di programma di P.
  • Se l'accessibilità dichiarata di M è private, il dominio di accessibilità di M è il testo di programma di T.

Nota: da queste definizioni segue che il dominio di accessibilità di un membro annidato è sempre almeno il testo del programma del tipo in cui viene dichiarato il membro. Inoltre, segue che il dominio di accessibilità di un membro non è mai più inclusivo del dominio di accessibilità del tipo in cui viene dichiarato il membro. nota finale

Nota: in termini intuitivi, quando si accede a un tipo o un membro M , vengono valutati i passaggi seguenti per assicurarsi che l'accesso sia consentito:

  • In primo luogo, se M viene dichiarato all'interno di un tipo (anziché un'unità di compilazione o uno spazio dei nomi), si verifica un errore in fase di compilazione se tale tipo non è accessibile.
  • Quindi, se M è public, l'accesso è consentito.
  • In caso contrario, se M è protected internal, l'accesso è consentito se si verifica all'interno del programma in cui M è dichiarato o se si verifica all'interno di una classe derivata dalla classe in cui M viene dichiarata e viene eseguita tramite il tipo di classe derivata (§7.5.4).
  • In caso contrario, se M è protected, l'accesso è consentito se si verifica all'interno della classe in cui M è dichiarata o se si verifica all'interno di una classe derivata dalla classe in cui M viene dichiarata e viene eseguita tramite il tipo di classe derivata (§7.5.4).
  • In caso contrario, se M è internal, l'accesso è consentito se si verifica all'interno del programma in cui M è dichiarato.
  • In caso contrario, se M è private, l'accesso è consentito se si verifica all'interno del tipo in cui M è dichiarato.
  • In caso contrario, il tipo o il membro non è accessibile e si verifica un errore in fase di compilazione. nota finale

Esempio: nel codice seguente

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;
    }
}

le classi e i membri hanno i domini di accessibilità seguenti:

  • Il dominio di accessibilità di A e A.X è illimitato.
  • Il dominio di accessibilità di A.Y, B, B.YB.X, B.C, , B.C.Xe B.C.Y è il testo del programma contenitore.
  • Il dominio di accessibilità di A.Z è il testo del programma di A.
  • Il dominio di accessibilità di B.Z e B.D è il testo del programma di B, incluso il testo del programma di B.C e B.D.
  • Il dominio di accessibilità di B.C.Z è il testo del programma di B.C.
  • Il dominio di accessibilità di B.D.X e B.D.Y è il testo del programma di B, incluso il testo del programma di B.C e B.D.
  • Il dominio di accessibilità di B.D.Z è il testo del programma di B.D. Come illustrato nell'esempio, il dominio di accessibilità di un membro non è mai maggiore di quello di un tipo contenitore. Ad esempio, anche se tutti i X membri hanno dichiarato l'accessibilità pubblica, tutti ma A.X hanno domini di accessibilità vincolati da un tipo contenitore.

esempio finale

Come descritto in §7.4, tutti i membri di una classe base, ad eccezione di costruttori di istanza, finalizzatori e costruttori statici, vengono ereditati dai tipi derivati. Ciò include anche membri privati di una classe base. Tuttavia, il dominio di accessibilità di un membro privato include solo il testo del programma del tipo in cui viene dichiarato il membro.

Esempio: nel codice seguente

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
    }
}

la B classe eredita il membro x privato dalla A classe . Poiché il membro è privato, è accessibile solo all'interno del class_body di A. Pertanto, l'accesso a b.x ha esito positivo nel A.F metodo , ma ha esito negativo nel B.F metodo .

esempio finale

7.5.4 Accesso protetto

Quando si accede a un protected membro dell'istanza o private protected all'esterno del testo del programma della classe in cui viene dichiarata e quando si accede a un protected internal membro dell'istanza al di fuori del testo del programma in cui viene dichiarato, l'accesso verrà eseguito all'interno di una dichiarazione di classe che deriva dalla classe in cui viene dichiarata. Inoltre, l'accesso deve essere eseguito tramite un'istanza del tipo di classe derivata o un tipo di classe costruito da esso. Questa restrizione impedisce a una classe derivata di accedere ai membri protetti di altre classi derivate, anche quando i membri vengono ereditati dalla stessa classe di base.

Si supponga di B essere una classe base che dichiara un membro Mdell'istanza protetta e che D sia una classe che deriva da B. All'interno del class_body di D, l'accesso a può accettare M una delle seguenti forme:

  • Un type_name non qualificato o primary_expression del formato M.
  • Un primary_expression del formato E.M, a condizione che il tipo di E sia T o una classe derivata da T, dove T è la classe o un tipo di classe Dcostruito da D .
  • Oggetto primary_expression del formato base.M.
  • Primary_expression del form base[argument_list].

Oltre a queste forme di accesso, una classe derivata può accedere a un costruttore di istanza protetta di una classe base in un constructor_initializer (§15.11.2).

Esempio: nel codice seguente

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
    }
}

all'interno Adi è possibile accedere x tramite istanze di A e B, poiché in entrambi i casi l'accesso avviene tramite un'istanza di A o una classe derivata da A. Tuttavia, all'interno Bdi non è possibile accedere x tramite un'istanza di A, poiché A non deriva da B.

esempio finale

Esempio:

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";
    }
}

In questo caso, le tre assegnazioni a x sono consentite perché vengono eseguite tutte tramite istanze di tipi di classe costruiti dal tipo generico.

esempio finale

Nota: il dominio di accessibilità (§7.5.3) di un membro protetto dichiarato in una classe generica include il testo del programma di tutte le dichiarazioni di classe derivate da qualsiasi tipo costruito da tale classe generica. Nell'esempio:

class C<T>
{
    protected static T x;
}

class D : C<string>
{
    static void Main()
    {
        C<int>.x = 5;
    }
}

Il riferimento al protected membro C<int>.x in D è valido anche se la classe D deriva da C<string>. nota finale

7.5.5 Vincoli di accessibilità

Diversi costrutti nel linguaggio C# richiedono che un tipo sia almeno accessibile come membro o un altro tipo. Un tipo T viene detto come accessibile almeno come membro o tipo M se il dominio di accessibilità di T è un superset del dominio di accessibilità di M. In altre parole, T è accessibile almeno come M se T fosse accessibile in tutti i contesti in cui M è accessibile.

Esistono i vincoli di accessibilità seguenti:

  • La classe base diretta di un tipo di classe deve essere almeno accessibile come il tipo di classe stesso.
  • Le interfacce di base esplicite di un tipo di interfaccia devono essere accessibili almeno quanto il tipo di interfaccia stesso.
  • Il tipo restituito e i tipi di parametro di un tipo delegato devono essere almeno accessibili come il tipo delegato stesso.
  • Il tipo di una costante deve essere almeno accessibile come la costante stessa.
  • Il tipo di un campo deve essere accessibile almeno quanto il campo stesso.
  • Il tipo restituito e i tipi di parametro di un metodo devono essere accessibili almeno quanto il metodo stesso.
  • Il tipo di una proprietà deve essere almeno accessibile come la proprietà stessa.
  • Il tipo di un evento deve essere accessibile almeno quanto l'evento stesso.
  • I tipi di tipo e di parametro di un indicizzatore devono essere accessibili almeno quanto l'indicizzatore stesso.
  • Il tipo restituito e i tipi di parametro di un operatore devono essere almeno accessibili come l'operatore stesso.
  • I tipi di parametro di un costruttore di istanza devono essere accessibili almeno quanto il costruttore dell'istanza stessa.
  • Un vincolo di tipo di interfaccia o classe su un parametro di tipo deve essere almeno accessibile come il membro che dichiara il vincolo.

Esempio: nel codice seguente

class A {...}
public class B: A {...}

la B classe restituisce un errore in fase di compilazione perché A non è almeno accessibile come B.

esempio finale

Esempio: analogamente, nel codice seguente

class A {...}

public class B
{
    A F() {...}
    internal A G() {...}
    public A H() {...}
}

Il H metodo in B restituisce un errore in fase di compilazione perché il tipo A restituito non è almeno accessibile come il metodo .

esempio finale

7.6 Firme e overload

I metodi, i costruttori di istanza, gli indicizzatori e gli operatori sono caratterizzati dalle relative firme:

  • La firma di un metodo è costituita dal nome del metodo, dal numero di parametri di tipo e dal tipo e dalla modalità di passaggio di parametri di ognuno dei relativi parametri, considerati nell'ordine da sinistra a destra. A tale scopo, qualsiasi parametro di tipo del metodo che si verifica nel tipo di un parametro viene identificato non dal nome, ma dalla posizione ordinale nell'elenco di parametri di tipo del metodo . La firma di un metodo in particolare non include il tipo restituito, i nomi dei parametri, i nomi dei parametri di tipo, i vincoli dei parametri di tipo, i params modificatori di parametri o this , né se i parametri sono obbligatori o facoltativi.
  • La firma di un costruttore di istanza è costituita dal tipo e dalla modalità di passaggio di parametri di ognuno dei relativi parametri, considerati nell'ordine da sinistra a destra. La firma di un costruttore di istanza in particolare non include il params modificatore che può essere specificato per il parametro più a destra, né se i parametri sono obbligatori o facoltativi.
  • La firma di un indicizzatore è costituita dal tipo di ognuno dei relativi parametri, considerati nell'ordine da sinistra a destra. La firma di un indicizzatore in particolare non include il tipo di elemento, né include il params modificatore che può essere specificato per il parametro più a destra, né se i parametri sono obbligatori o facoltativi.
  • La firma di un operatore è costituita dal nome dell'operatore e dal tipo di ognuno dei relativi parametri, considerati nell'ordine da sinistra a destra. La firma di un operatore in particolare non include il tipo di risultato.
  • La firma di un operatore di conversione è costituita dal tipo di origine e dal tipo di destinazione. La classificazione implicita o esplicita di un operatore di conversione non fa parte della firma.
  • Due firme dello stesso tipo di membro (metodo, costruttore di istanza, indicizzatore o operatore) sono considerate le stesse firme se hanno lo stesso nome, numero di parametri di tipo, numero di parametri e modalità di passaggio dei parametri e esiste una conversione identity tra i tipi dei parametri corrispondenti (§10.2.2).

Le firme sono il meccanismo di abilitazione per l'overload dei membri in classi, struct e interfacce:

  • L'overload dei metodi consente a una classe, uno struct o un'interfaccia di dichiarare più metodi con lo stesso nome, purché le firme siano univoche all'interno di tale classe, struct o interfaccia.
  • L'overload dei costruttori di istanze consente a una classe o a uno struct di dichiarare più costruttori di istanza, purché le firme siano univoche all'interno di tale classe o struct.
  • L'overload degli indicizzatori consente a una classe, uno struct o un'interfaccia di dichiarare più indicizzatori, purché le firme siano univoche all'interno di tale classe, struct o interfaccia.
  • L'overload degli operatori consente a una classe o a uno struct di dichiarare più operatori con lo stesso nome, purché le firme siano univoche all'interno di tale classe o struct.

Anche se ini modificatori di parametri , oute ref sono considerati parte di una firma, i membri dichiarati in un singolo tipo non possono differire in firma esclusivamente per in, oute ref. Si verifica un errore in fase di compilazione se due membri vengono dichiarati nello stesso tipo con firme uguali se tutti i parametri in entrambi i metodi con out o in modificatori sono stati modificati in ref modificatori. Per altri scopi di corrispondenza della firma (ad esempio, nascondere o eseguire l'override), in, oute ref sono considerati parte della firma e non corrispondono tra loro.

Nota: questa restrizione consiste nel consentire la conversione dei programmi C# in modo facile da eseguire nell'infrastruttura common language (CLI), che non fornisce un modo per definire metodi che differiscono esclusivamente in in, oute ref. nota finale

I tipi object e dynamic non sono distinti durante il confronto delle firme. I membri dichiarati in un singolo tipo le cui firme differiscono solo sostituendo object con dynamic non sono consentite.

Esempio: l'esempio seguente mostra un set di dichiarazioni di metodo di overload insieme alle relative firme.

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
}

Si noti che tutti i inmodificatori di parametri , oute ref (§15.6.2) fanno parte di una firma. Di conseguenza, F(int), F(out int) F(in int), e F(ref int) sono tutte firme univoche. Tuttavia, F(in int), F(out int) e F(ref int) non possono essere dichiarati all'interno della stessa interfaccia perché le relative firme differiscono esclusivamente per in, oute ref. Si noti inoltre che il tipo restituito e il params modificatore non fanno parte di una firma, pertanto non è possibile eseguire l'overload esclusivamente in base al tipo restituito o all'inclusione o all'esclusione del params modificatore. Di conseguenza, le dichiarazioni dei metodi F(int) e F(params string[]) identificate in precedenza generano un errore in fase di compilazione. esempio finale

7.7 Ambiti

7.7.1 Generale

L'ambito di un nome è l'area del testo del programma in cui è possibile fare riferimento all'entità dichiarata dal nome senza qualificare il nome. Gli ambiti possono essere annidati e un ambito interno può ripetere il significato di un nome da un ambito esterno. Ciò non comporta tuttavia la rimozione della restrizione imposta da §7.3 che all'interno di un blocco annidato non è possibile dichiarare una variabile locale o una costante locale con lo stesso nome di una variabile locale o di una costante locale in un blocco di inclusione. Il nome dell'ambito esterno viene quindi detto essere nascosto nell'area del testo del programma coperto dall'ambito interno e l'accesso al nome esterno è possibile solo qualificando il nome.

  • L'ambito di un membro dello spazio dei nomi dichiarato da un namespace_member_declaration (§14.6) senza inclusione namespace_declaration è l'intero testo del programma.

  • L'ambito di un membro dello spazio dei nomi dichiarato da un namespace_member_declaration all'interno di un namespace_declaration il cui nome completo è N, è il namespace_body di ogni namespace_declaration il cui nome completo è N o inizia con N, seguito da un punto.

  • L'ambito di un nome definito da un extern_alias_directive (§14.4) si estende sui using_directive, global_attributes e namespace_member_declarationdei relativi compilation_unit o namespace_body immediatamente contenuti. Un extern_alias_directive non contribuisce ad alcun nuovo membro allo spazio di dichiarazione sottostante. In altre parole, un extern_alias_directive non è transitivo, ma invece influisce solo sul compilation_unit o namespace_body in cui si verifica.

  • L'ambito di un nome definito o importato da un using_directive (§14.5) si estende sui global_attributes e namespace_member_declarationdel compilation_unit o namespace_body in cui si verifica il using_directive. Un using_directive può rendere disponibili zero o più nomi di spazio dei nomi o tipi all'interno di un determinato compilation_unit o namespace_body, ma non contribuisce ad alcun nuovo membro allo spazio di dichiarazione sottostante. In altre parole, un using_directive non è transitivo, ma influisce solo sul compilation_unit o namespace_body in cui si verifica.

  • L'ambito di un parametro di tipo dichiarato da un type_parameter_list su un class_declaration (§15.2) è il class_base, type_parameter_constraints_clauses e class_body di tale class_declaration.

    Nota: a differenza dei membri di una classe, questo ambito non si estende alle classi derivate. nota finale

  • L'ambito di un parametro di tipo dichiarato da un type_parameter_list in un struct_declaration (§16.2) è il struct_interfaces, type_parameter_constraints_clauses e struct_body di tale struct_declaration.

  • L'ambito di un parametro di tipo dichiarato da un type_parameter_list su un interface_declaration (§18.2) è il interface_base, type_parameter_constraints_clauses e interface_body di tale interface_declaration.

  • L'ambito di un parametro di tipo dichiarato da un type_parameter_list in un delegate_declaration (§20.2) è il return_type, parameter_list e type_parameter_constraints_clausedi tale delegate_declaration.

  • L'ambito di un parametro di tipo dichiarato da un type_parameter_list in un method_declaration (§15.6.1) è il method_declaration.

  • L'ambito di un membro dichiarato da un class_member_declaration (§15.3.1) è il class_body in cui si verifica la dichiarazione. Inoltre, l'ambito di un membro di classe si estende alla class_body di quelle classi derivate incluse nel dominio di accessibilità (§7.5.3) del membro.

  • L'ambito di un membro dichiarato da un struct_member_declaration (§16.3) è il struct_body in cui si verifica la dichiarazione.

  • L'ambito di un membro dichiarato da un enum_member_declaration (§19.4) è il enum_body in cui si verifica la dichiarazione.

  • L'ambito di un parametro dichiarato in un method_declaration (§15.6) è il method_body o il ref_method_body di tale method_declaration.

  • L'ambito di un parametro dichiarato in un indexer_declaration (§15.9) è il indexer_body di tale indexer_declaration.

  • L'ambito di un parametro dichiarato in un operator_declaration (§15.10) è il operator_body di tale operator_declaration.

  • L'ambito di un parametro dichiarato in un constructor_declaration (§15.11) è il constructor_initializer e il blocco di tale constructor_declaration.

  • L'ambito di un parametro dichiarato in un lambda_expression (§12.19) è il lambda_expression_body di tale lambda_expression.

  • L'ambito di un parametro dichiarato in un anonymous_method_expression (§12.19) è il blocco di tale anonymous_method_expression.

  • L'ambito di un'etichetta dichiarata in un labeled_statement (§13.5) è il blocco in cui si verifica la dichiarazione.

  • L'ambito di una variabile locale dichiarata in un local_variable_declaration (§13.6.2) è il blocco in cui si verifica la dichiarazione.

  • L'ambito di una variabile locale dichiarata in un switch_block di un'istruzione switch (§13.8.3) è il switch_block.

  • L'ambito di una variabile locale dichiarata in un for_initializer di un'istruzione for (§13.9.4) è il for_initializer, for_condition, for_iterator e embedded_statement dell'istruzione for .

  • L'ambito di una costante locale dichiarata in un local_constant_declaration (§13.6.3) è il blocco in cui si verifica la dichiarazione. Si tratta di un errore in fase di compilazione per fare riferimento a una costante locale in una posizione testuale che precede il relativo constant_declarator.

  • L'ambito di una variabile dichiarata come parte di un foreach_statement, using_statement, lock_statement o query_expression è determinato dall'espansione del costrutto specificato.

Nell'ambito di uno spazio dei nomi, una classe, uno struct o un membro di enumerazione è possibile fare riferimento al membro in una posizione testuale che precede la dichiarazione del membro.

Esempio:

class A
{
    void F()
    {
        i = 1;
    }

    int i = 0;
}

In questo caso, è valido per F fare riferimento a i prima di essere dichiarato.

esempio finale

Nell'ambito di una variabile locale, si tratta di un errore in fase di compilazione per fare riferimento alla variabile locale in una posizione testuale che precede il relativo dichiaratore.

Esempio:

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 Nel metodo precedente, la prima assegnazione a i specifica non fa riferimento al campo dichiarato nell'ambito esterno. Si riferisce invece alla variabile locale e genera un errore in fase di compilazione perché precede in modo testuale la dichiarazione della variabile. G Nel metodo l'uso di j nell'inizializzatore per la dichiarazione di j è valido perché l'uso non precede il dichiaratore. H Nel metodo un dichiaratore successivo fa correttamente riferimento a una variabile locale dichiarata in un dichiaratore precedente all'interno dello stesso local_variable_declaration.

esempio finale

Nota: le regole di ambito per le variabili locali e le costanti locali sono progettate per garantire che il significato di un nome usato in un contesto di espressione sia sempre lo stesso all'interno di un blocco. Se l'ambito di una variabile locale dovesse estendersi solo dalla relativa dichiarazione alla fine del blocco, nell'esempio precedente, la prima assegnazione assegnerebbe alla variabile di istanza e la seconda assegnazione assegnerebbe alla variabile locale, probabilmente causando errori di compilazione se le istruzioni del blocco sarebbero state riorganizzata in un secondo momento.

Il significato di un nome all'interno di un blocco può differire in base al contesto in cui viene usato il nome. Nell'esempio

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"
    }
}

il nome A viene usato in un contesto di espressione per fare riferimento alla variabile A locale e in un contesto di tipo per fare riferimento alla classe A.

nota finale

7.7.2 Nome nascosto

7.7.2.1 Generale

L'ambito di un'entità include in genere più testo programma rispetto allo spazio di dichiarazione dell'entità. In particolare, l'ambito di un'entità può includere dichiarazioni che introducono nuovi spazi di dichiarazione contenenti entità con lo stesso nome. Tali dichiarazioni fanno sì che l'entità originale diventi nascosta. Al contrario, un'entità viene detta visibile quando non è nascosta.

Il nascondimento dei nomi si verifica quando gli ambiti si sovrappongono tramite annidamento e quando gli ambiti si sovrappongono tramite ereditarietà. Le caratteristiche dei due tipi di nascondere sono descritte nelle sottoclause seguenti.

7.7.2.2 Nascondere l'annidamento

Il nome nascosto tramite l'annidamento può verificarsi in seguito all'annidamento di spazi dei nomi o tipi all'interno di spazi dei nomi, come risultato di tipi di annidamento all'interno di classi o struct, come risultato di una funzione locale o di un'espressione lambda e come risultato di parametri, variabili locali e dichiarazioni costanti locali.

Esempio: nel codice seguente

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;
    }
}

all'interno del F metodo, la variabile i di istanza è nascosta dalla variabile ilocale , ma all'interno del G metodo, i fa comunque riferimento alla variabile di istanza. All'interno della funzione M1 locale l'oggetto float i nasconde l'oggetto esterno immediato i. Il parametro i lambda nasconde l'oggetto float i all'interno del corpo lambda.

esempio finale

Quando un nome in un ambito interno nasconde un nome in un ambito esterno, nasconde tutte le occorrenze di overload di tale nome.

Esempio: nel codice seguente

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
        }
    }
}

la chiamata F(1) richiama l'oggetto F dichiarato in Inner perché tutte le occorrenze esterne di F sono nascoste dalla dichiarazione interna. Per lo stesso motivo, la chiamata F("Hello") genera un errore in fase di compilazione.

esempio finale

7.7.2.3 Nascondere l'ereditarietà

Il nome nascosto tramite ereditarietà si verifica quando le classi o gli struct riclare nomi ereditati dalle classi di base. Questo tipo di nome nascosto accetta una delle seguenti forme:

  • Una costante, un campo, una proprietà, un evento o un tipo introdotto in una classe o uno struct nasconde tutti i membri della classe base con lo stesso nome.
  • Un metodo introdotto in una classe o uno struct nasconde tutti i membri della classe base non metodo con lo stesso nome e tutti i metodi della classe base con la stessa firma (§7.6).
  • Un indicizzatore introdotto in una classe o uno struct nasconde tutti gli indicizzatori di classi di base con la stessa firma (§7.6).

Le regole che regolano le dichiarazioni degli operatori (§15.10) rendono impossibile per una classe derivata dichiarare un operatore con la stessa firma di un operatore in una classe base. Così, gli operatori non si nascondono mai l'uno dall'altro.

Contrariamente a nascondere un nome da un ambito esterno, nascondendo un nome visibile da un ambito ereditato, viene segnalato un avviso.

Esempio: nel codice seguente

class Base
{
    public void F() {}
}

class Derived : Base
{
    public void F() {} // Warning, hiding an inherited name
}

la dichiarazione di in Derived causa la segnalazione di F un avviso. Nascondere un nome ereditato non è in particolare un errore, perché impedirebbe un'evoluzione separata delle classi di base. Ad esempio, la situazione precedente potrebbe verificarsi perché una versione successiva di Base ha introdotto un F metodo che non era presente in una versione precedente della classe.

esempio finale

L'avviso causato dalla nascondere un nome ereditato può essere eliminato tramite l'uso del new modificatore:

Esempio:

class Base
{
    public void F() {}
}

class Derived : Base
{
    public new void F() {}
}

Il new modificatore indica che in F Derived è "nuovo" e che è effettivamente destinato a nascondere il membro ereditato.

esempio finale

Una dichiarazione di un nuovo membro nasconde un membro ereditato solo nell'ambito del nuovo membro.

Esempio:

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
    }
}

Nell'esempio precedente, la dichiarazione di F in Derived nasconde l'oggetto F ereditato da Base, ma poiché il nuovo F in Derived ha accesso privato, l'ambito non si estende a MoreDerived. Pertanto, la chiamata F() in MoreDerived.G è valida e richiamerà Base.F.

esempio finale

7.8 Nomi di spazio dei nomi e tipi

7.8.1 Generale

Per specificare diversi contesti in un programma C# è necessario specificare un namespace_name o un type_name .

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
    ;

Un namespace_name è un namespace_or_type_name che fa riferimento a uno spazio dei nomi.

La risoluzione seguente, come descritto di seguito, il namespace_or_type_name di un namespace_name fa riferimento a uno spazio dei nomi o in caso contrario si verifica un errore in fase di compilazione. Nessun argomento di tipo (§8.4.2) può essere presente in un namespace_name (solo i tipi possono avere argomenti di tipo).

Un type_name è un namespace_or_type_name che fa riferimento a un tipo. La risoluzione seguente, come descritto di seguito, il namespace_or_type_name di un type_name fa riferimento a un tipo o in caso contrario si verifica un errore in fase di compilazione.

Se il namespace_or_type_name è un qualified_alias_member il suo significato è descritto in §14.8.1. In caso contrario, un namespace_or_type_name ha una delle quattro forme seguenti:

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

dove I è un singolo identificatore, N è un namespace_or_type_name ed <A₁, ..., Aₓ> è un type_argument_list facoltativo. Quando non viene specificato alcun type_argument_list , considerare x zero.

Il significato di un namespace_or_type_name viene determinato nel modo seguente:

  • Se il namespace_or_type_name è un qualified_alias_member, il significato è specificato in §14.8.1.
  • In caso contrario, se il namespace_or_type_name è del formato I o del formato I<A₁, ..., Aₓ>:
    • Se x è zero e il namespace_or_type_name viene visualizzato all'interno di una dichiarazione di metodo generica (§15.6), ma all'esterno degli attributi dell'intestazione del metodo e se tale dichiarazione include un parametro di tipo (§15.2.3) con nome I, il namespace_or_type_name fa riferimento a tale parametro di tipo.
    • In caso contrario, se il namespace_or_type_name viene visualizzato all'interno di una dichiarazione di tipo, per ogni tipo T di istanza (§15.3.2), a partire dal tipo di istanza della dichiarazione di tipo e continuando con il tipo di istanza di ogni dichiarazione di classe o struct contenitore (se presente):
      • Se x è zero e la dichiarazione di include un parametro di T tipo con nome I, il namespace_or_type_name fa riferimento a tale parametro di tipo.
      • In caso contrario, se il namespace_or_type_name viene visualizzato all'interno del corpo della dichiarazione di tipo e T uno dei relativi tipi di base contiene un tipo accessibile annidato con parametri nome I e x tipo, il namespace_or_type_name fa riferimento a quel tipo costruito con gli argomenti di tipo specificati. Se è presente più di un tipo di questo tipo, viene selezionato il tipo dichiarato all'interno del tipo più derivato.

      Nota: membri non di tipo (costanti, campi, metodi, proprietà, indicizzatori, operatori, costruttori di istanza, finalizzatori e costruttori statici) e membri di tipo con un numero diverso di parametri di tipo vengono ignorati quando si determina il significato del namespace_or_type_name. nota finale

    • In caso contrario, per ogni spazio dei nomi N, a partire dallo spazio dei nomi in cui si verifica il namespace_or_type_name , continuando con ogni spazio dei nomi contenitore (se presente) e terminando con lo spazio dei nomi globale, i passaggi seguenti vengono valutati fino a quando non viene individuata un'entità:
      • Se x è zero e I è il nome di uno spazio dei nomi in N, allora:
        • Se il percorso in cui si verifica il namespace_or_type_name è racchiuso da una dichiarazione dello spazio dei nomi per N e la dichiarazione dello spazio dei nomi contiene un extern_alias_directive o un using_alias_directive che associa il nome I a uno spazio dei nomi o un tipo, il namespace_or_type_name è ambiguo e si verifica un errore in fase di compilazione.
        • In caso contrario, il namespace_or_type_name fa riferimento allo spazio dei nomi denominato I in N.
      • In caso contrario, se N contiene un tipo accessibile con parametri nome I e x tipo, allora:
        • Se x è zero e la posizione in cui si verifica il namespace_or_type_name è racchiusa da una dichiarazione dello spazio dei nomi per N e la dichiarazione dello spazio dei nomi contiene un extern_alias_directive o un using_alias_directive che associa il nome I a uno spazio dei nomi o un tipo, il namespace_or_type_name è ambiguo e si verifica un errore in fase di compilazione.
        • In caso contrario, il namespace_or_type_name fa riferimento al tipo costruito con gli argomenti di tipo specificati.
      • In caso contrario, se il percorso in cui si verifica il namespace_or_type_name è racchiuso da una dichiarazione dello spazio dei nomi per N:
        • Se x è zero e la dichiarazione dello spazio dei nomi contiene un extern_alias_directive o using_alias_directive che associa il nome I a uno spazio dei nomi o un tipo importato, il namespace_or_type_name fa riferimento a tale spazio dei nomi o tipo.
        • In caso contrario, se gli spazi dei nomi importati dal using_namespace_directivedella dichiarazione dello spazio dei nomi contengono esattamente un tipo con parametri nome I e x tipo, il namespace_or_type_name fa riferimento a quel tipo costruito con gli argomenti di tipo specificati.
        • In caso contrario, se gli spazi dei nomi importati dal using_namespace_directivedella dichiarazione dello spazio dei nomi contengono più di un tipo con parametri nome I e x tipo, il namespace_or_type_name è ambiguo e si verifica un errore.
    • In caso contrario, il namespace_or_type_name non è definito e si verifica un errore in fase di compilazione.
  • In caso contrario, il namespace_or_type_name è di forma N.I o del formato N.I<A₁, ..., Aₓ>. N viene prima risolto come namespace_or_type_name. Se la risoluzione di non riesce, si verifica un errore in fase di N compilazione. In caso contrario, N.I o N.I<A₁, ..., Aₓ> viene risolto come segue:
    • Se x è zero e N fa riferimento a uno spazio dei nomi e N contiene uno spazio dei nomi annidato con nome I, il namespace_or_type_name fa riferimento a tale spazio dei nomi annidato.
    • In caso contrario, se N fa riferimento a uno spazio dei nomi e N contiene un tipo accessibile con parametri nome I e x tipo, il namespace_or_type_name fa riferimento a quel tipo costruito con gli argomenti di tipo specificati.
    • In caso contrario, se N fa riferimento a una classe o a un tipo struct (possibilmente costruito) e N o a una delle relative classi di base contengono un tipo accessibile annidato con parametri nome I e x tipo, il namespace_or_type_name fa riferimento a quel tipo costruito con gli argomenti di tipo specificati. Se è presente più di un tipo di questo tipo, viene selezionato il tipo dichiarato all'interno del tipo più derivato.

      Nota: se il significato di N.I viene determinato come parte della risoluzione della specifica della classe di base di N , la classe base diretta di N viene considerata object (§15.2.4.2). nota finale

    • In caso contrario, N.I è un namespace_or_type_name non valido e si verifica un errore in fase di compilazione.

Un namespace_or_type_name è autorizzato a fare riferimento a una classe statica (§15.2.2.4) solo se

  • Il namespace_or_type_name è in T un namespace_or_type_name del formato T.I, o
  • Il namespace_or_type_name è in T un typeof_expression (§12.8.17) del modulo typeof(T)

7.8.2 Nomi non qualificati

Ogni dichiarazione dello spazio dei nomi e dichiarazione di tipo ha un nome non qualificato determinato come segue:

  • Per una dichiarazione dello spazio dei nomi, il nome non qualificato è il qualified_identifier specificato nella dichiarazione.
  • Per una dichiarazione di tipo senza type_parameter_list, il nome non qualificato è l'identificatore specificato nella dichiarazione.
  • Per una dichiarazione di tipo con parametri di tipo K, il nome non qualificato è l'identificatore specificato nella dichiarazione, seguito dal generic_dimension_specifier (§12.8.17) per i parametri di tipo K.

7.8.3 Nomi completi

Ogni spazio dei nomi e dichiarazione di tipo ha un nome completo, che identifica in modo univoco lo spazio dei nomi o la dichiarazione di tipo tra tutti gli altri all'interno del programma. Il nome completo di uno spazio dei nomi o di una dichiarazione di tipo con nome N non qualificato viene determinato nel modo seguente:

  • Se N è un membro dello spazio dei nomi globale, il nome completo è N.
  • In caso contrario, il nome completo è S.N, dove S è il nome completo dello spazio dei nomi o della dichiarazione di tipo in cui N viene dichiarata.

In altre parole, il nome completo di N è il percorso gerarchico completo degli identificatori e generic_dimension_specifierche portano a N, a partire dallo spazio dei nomi globale. Poiché ogni membro di uno spazio dei nomi o un tipo deve avere un nome univoco, è necessario che il nome completo di uno spazio dei nomi o di una dichiarazione di tipo sia sempre univoco. Si tratta di un errore in fase di compilazione per lo stesso nome completo per fare riferimento a due entità distinte. In particolare:

  • Si tratta di un errore sia per una dichiarazione dello spazio dei nomi che per una dichiarazione di tipo per avere lo stesso nome completo.
  • Si tratta di un errore per due tipi diversi di dichiarazioni di tipo con lo stesso nome completo, ad esempio se sia una dichiarazione di struct che una dichiarazione di classe hanno lo stesso nome completo.
  • Si tratta di un errore per una dichiarazione di tipo senza che il modificatore parziale abbia lo stesso nome completo di un'altra dichiarazione di tipo (§15.2.7).

Esempio: l'esempio seguente mostra diverse dichiarazioni di spazio dei nomi e tipi insieme ai nomi completi associati.

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<>
    }
}

esempio finale

7.9 Gestione automatica della memoria

C# usa la gestione automatica della memoria, che consente agli sviluppatori di allocare e liberare manualmente la memoria occupata dagli oggetti. I criteri di gestione automatica della memoria vengono implementati da un Garbage Collector. Il ciclo di vita della gestione della memoria di un oggetto è il seguente:

  1. Quando l'oggetto viene creato, la memoria viene allocata per essa, il costruttore viene eseguito e l'oggetto viene considerato attivo.
  2. Se non è possibile accedere né all'oggetto né ai relativi campi di istanza da qualsiasi possibile continuazione dell'esecuzione, ad eccezione dell'esecuzione dei finalizzatori, l'oggetto viene considerato non più in uso e diventa idoneo per la finalizzazione.

    Nota: il compilatore C# e il Garbage Collector potrebbero scegliere di analizzare il codice per determinare quali riferimenti a un oggetto potrebbero essere usati in futuro. Ad esempio, se una variabile locale nell'ambito è l'unico riferimento esistente a un oggetto, ma tale variabile locale non viene mai indicata in qualsiasi possibile continuazione dell'esecuzione dal punto di esecuzione corrente nella routine, il Garbage Collector potrebbe (ma non è necessario) considerare l'oggetto come non più in uso. nota finale

  3. Una volta che l'oggetto è idoneo per la finalizzazione, in un secondo momento non specificato il finalizzatore (§15.13) (se presente) per l'oggetto viene eseguito. In circostanze normali il finalizzatore per l'oggetto viene eseguito una sola volta, anche se le API definite dall'implementazione possono consentire l'override di questo comportamento.
  4. Dopo l'esecuzione del finalizzatore per un oggetto, se non è possibile accedere né all'oggetto né ai relativi campi di istanza da qualsiasi possibile continuazione dell'esecuzione, inclusa l'esecuzione dei finalizzatori, l'oggetto viene considerato inaccessibile e l'oggetto diventa idoneo per la raccolta.

    Nota: un oggetto a cui non è stato possibile accedere in precedenza può diventare nuovamente accessibile a causa del relativo finalizzatore. Di seguito è riportato un esempio. nota finale

  5. Infine, in un certo momento dopo che l'oggetto diventa idoneo per la raccolta, Il Garbage Collector libera la memoria associata a tale oggetto.

Il Garbage Collector gestisce informazioni sull'utilizzo degli oggetti e usa queste informazioni per prendere decisioni di gestione della memoria, ad esempio dove in memoria per individuare un oggetto appena creato, quando spostare un oggetto e quando un oggetto non è più in uso o inaccessibile.

Analogamente ad altri linguaggi che presuppongono l'esistenza di un Garbage Collector, C# è progettato in modo che il Garbage Collector possa implementare un'ampia gamma di criteri di gestione della memoria. C# specifica né un vincolo temporale all'interno di tale intervallo, né un ordine in cui vengono eseguiti i finalizzatori. Indica se i finalizzatori vengono eseguiti come parte della terminazione dell'applicazione è definito dall'implementazione (§7.2).

Il comportamento del Garbage Collector può essere controllato, in qualche modo, tramite metodi statici nella classe System.GC. Questa classe può essere usata per richiedere l'esecuzione di una raccolta, i finalizzatori da eseguire (o meno) e così via.

Esempio: poiché il Garbage Collector è consentito a una latitudine ampia per decidere quando raccogliere oggetti ed eseguire finalizzatori, un'implementazione conforme potrebbe produrre output diverso da quello mostrato dal codice seguente. Il programma

class A
{
    ~A()
    {
        Console.WriteLine("Finalize instance of A");
    }
}

class B
{
    object Ref;
    public B(object o)
    {
        Ref = o;
    }

    ~B()
    {
        Console.WriteLine("Finalize instance of B");
    }
}

class Test
{
    static void Main()
    {
        B? b = new B(new A());
        b = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
}

crea un'istanza della classe A e un'istanza della classe B. Questi oggetti diventano idonei per l'operazione di Garbage Collection quando alla variabile b viene assegnato il valore null, poiché dopo questa volta non è possibile che qualsiasi codice scritto dall'utente possa accedervi. L'output potrebbe essere uno dei due

Finalize instance of A
Finalize instance of B

or

Finalize instance of B
Finalize instance of A

poiché il linguaggio non impone vincoli sull'ordine in cui gli oggetti vengono sottoposto a Garbage Collection.

In casi sottili, la distinzione tra "idonea alla finalizzazione" e "idonea alla raccolta" può essere importante. ad esempio:

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");
        }
    }
}

Nel programma precedente, se il Garbage Collector sceglie di eseguire il finalizzatore di A prima del finalizzatore di B, l'output di questo programma potrebbe essere:

Finalize instance of A
Finalize instance of B
A.F
RefA is not null

Si noti che anche se l'istanza di A non era in uso e Ail finalizzatore è stato eseguito, è comunque possibile che i metodi di A (in questo caso , F) vengano chiamati da un altro finalizzatore. Si noti anche che l'esecuzione di un finalizzatore potrebbe causare l'uso di un oggetto dal programma mainline di nuovo. In questo caso, l'esecuzione del finalizzatore di Bha causato un'istanza di A che in precedenza non era in uso, per diventare accessibile dal riferimento Test.RefAlive . Dopo la chiamata a WaitForPendingFinalizers, l'istanza di B è idonea per la raccolta, ma l'istanza di A non è , a causa del riferimento Test.RefA.

esempio finale

7.10 Ordine di esecuzione

L'esecuzione di un programma C# procede in modo che gli effetti collaterali di ogni thread in esecuzione vengano mantenuti in punti di esecuzione critici. Un effetto collaterale viene definito come lettura o scrittura di un campo volatile, una scrittura in una variabile non volatile, una scrittura in una risorsa esterna e la generazione di un'eccezione. I punti di esecuzione critici in cui l'ordine di questi effetti collaterali devono essere conservati sono riferimenti a campi volatili (§15.5.4), lock istruzioni (§13.13) e creazione e terminazione del thread. L'ambiente di esecuzione è libero di modificare l'ordine di esecuzione di un programma C#, soggetto ai vincoli seguenti:

  • La dipendenza dei dati viene mantenuta all'interno di un thread di esecuzione. Ovvero, il valore di ogni variabile viene calcolato come se tutte le istruzioni nel thread siano state eseguite nell'ordine di programma originale.
  • Le regole di ordinamento di inizializzazione vengono mantenute (§15.5.5, §15.5.6).
  • L'ordinamento degli effetti collaterali viene mantenuto in relazione alle letture e alle scritture volatili (§15.5.4). Inoltre, l'ambiente di esecuzione non deve valutare parte di un'espressione se può dedurre che il valore dell'espressione non viene usato e che non vengono generati effetti collaterali necessari (inclusi quelli causati dalla chiamata di un metodo o dall'accesso a un campo volatile). Quando l'esecuzione del programma viene interrotta da un evento asincrono (ad esempio un'eccezione generata da un altro thread), non è garantito che gli effetti collaterali osservabili siano visibili nell'ordine di programma originale.