Condividi tramite


Istruzioni di livello superiore

Annotazioni

Questo articolo è una specifica delle funzionalità. La specifica funge da documento di progettazione per la funzionalità. Include le modifiche specifiche proposte, insieme alle informazioni necessarie durante la progettazione e lo sviluppo della funzionalità. Questi articoli vengono pubblicati fino a quando le modifiche specifiche proposte non vengono completate e incorporate nella specifica ECMA corrente.

Potrebbero verificarsi alcune discrepanze tra la specifica di funzionalità e l'implementazione completata. Tali differenze vengono riportate nelle note pertinenti della riunione di progettazione linguistica (LDM) .

Ulteriori dettagli sul processo di adozione delle specifiche di funzionalità nello standard del linguaggio C# sono disponibili nell'articolo sulle specifiche .

Questione prioritaria: https://github.com/dotnet/csharplang/issues/2765

Riassunto

Consentire l'esecuzione di una sequenza di istruzioni subito prima dell'namespace_member_declaration di un compilation_unit (ad esempio un file di origine).

La semantica è che se è presente una sequenza di istruzioni di questo tipo, verrà generata la dichiarazione di tipo seguente, modulo il nome effettivo del metodo:

partial class Program
{
    static async Task Main(string[] args)
    {
        // statements
    }
}

Vedi anche https://github.com/dotnet/csharplang/issues/3117.

Motivazione

C'è una certa quantità di boilerplate che circonda anche il più semplice dei programmi, a causa della necessità di un metodo esplicito Main . Questo sembra entrare nel modo in cui l'apprendimento linguistico e la chiarezza del programma. L'obiettivo principale della funzionalità è quindi consentire programmi C# senza boilerplate non necessari, per il bene degli studenti e la chiarezza del codice.

Progettazione dettagliata

Sintassi

L'unica sintassi aggiuntiva consente una sequenza di istruzioniin un'unità di compilazione, subito prima della namespace_member_declaration:

compilation_unit
    : extern_alias_directive* using_directive* global_attributes? statement* namespace_member_declaration*
    ;

Solo un compilation_unit può avere istruzionis.

Esempio:

if (args.Length == 0
    || !int.TryParse(args[0], out int n)
    || n < 0) return;
Console.WriteLine(Fib(n).curr);

(int curr, int prev) Fib(int i)
{
    if (i == 0) return (1, 0);
    var (curr, prev) = Fib(i - 1);
    return (curr + prev, curr);
}

Semantica

Se in qualsiasi unità di compilazione del programma sono presenti istruzioni di primo livello, il significato è come se fossero combinate nel corpo del blocco di un Main metodo di una Program classe nello spazio dei nomi globale, come indicato di seguito:

partial class Program
{
    static async Task Main(string[] args)
    {
        // statements
    }
}

Il tipo è denominato "Program", quindi può essere fatto riferimento in base al nome dal codice sorgente. Si tratta di un tipo parziale, quindi un tipo denominato "Program" nel codice sorgente deve anche essere dichiarato come parziale.
Ma il nome del metodo "Main" viene usato solo a scopo illustrativo, il nome effettivo usato dal compilatore dipende dall'implementazione e il metodo non può essere fatto riferimento per nome dal codice sorgente.

Il metodo viene designato come punto di ingresso del programma. I metodi dichiarati in modo esplicito che per convenzione possono essere considerati come candidati al punto di ingresso vengono ignorati. Quando si verifica un avviso, viene segnalato un avviso. È possibile specificare un punto di ingresso diverso tramite l'opzione del -main:<type> compilatore.

Il metodo del punto di ingresso ha sempre un parametro formale, string[] args. L'ambiente di esecuzione crea e passa un string[] argomento contenente gli argomenti della riga di comando specificati all'avvio dell'applicazione. L'argomento string[] non è mai Null, ma può avere una lunghezza pari a zero se non sono stati specificati argomenti della riga di comando. Il parametro 'args' si trova nell'ambito all'interno di istruzioni di primo livello e non rientra nell'ambito all'esterno di tali istruzioni. Si applicano le regole di conflitto/shadowing del nome regolare.

Le operazioni asincrone sono consentite nelle istruzioni di primo livello per quanto siano consentite nelle istruzioni all'interno di un normale metodo del punto di ingresso asincrono. Non sono tuttavia necessari, se await le espressioni e altre operazioni asincrone vengono omesse, non viene generato alcun avviso.

La firma del metodo del punto di ingresso generato viene determinata in base alle operazioni usate dalle istruzioni di primo livello come indicato di seguito:

Async-operations\Return-with-expression Presente Assente
Presente static Task<int> Main(string[] args) static Task Main(string[] args)
Assente static int Main(string[] args) static void Main(string[] args)

Nell'esempio precedente viene restituita la dichiarazione di metodo seguente $Main :

partial class Program
{
    static void $Main(string[] args)
    {
        if (args.Length == 0
            || !int.TryParse(args[0], out int n)
            || n < 0) return;
        Console.WriteLine(Fib(n).curr);
        
        (int curr, int prev) Fib(int i)
        {
            if (i == 0) return (1, 0);
            var (curr, prev) = Fib(i - 1);
            return (curr + prev, curr);
        }
    }
}

Allo stesso tempo, un esempio simile al seguente:

await System.Threading.Tasks.Task.Delay(1000);
System.Console.WriteLine("Hi!");

produrrebbe:

partial class Program
{
    static async Task $Main(string[] args)
    {
        await System.Threading.Tasks.Task.Delay(1000);
        System.Console.WriteLine("Hi!");
    }
}

Un esempio simile al seguente:

await System.Threading.Tasks.Task.Delay(1000);
System.Console.WriteLine("Hi!");
return 0;

produrrebbe:

partial class Program
{
    static async Task<int> $Main(string[] args)
    {
        await System.Threading.Tasks.Task.Delay(1000);
        System.Console.WriteLine("Hi!");
        return 0;
    }
}

Un esempio simile al seguente:

System.Console.WriteLine("Hi!");
return 2;

produrrebbe:

partial class Program
{
    static int $Main(string[] args)
    {
        System.Console.WriteLine("Hi!");
        return 2;
    }
}

Ambito delle variabili locali di primo livello e delle funzioni locali

Anche se le variabili e le funzioni locali di primo livello vengono "incapsulate" nel metodo del punto di ingresso generato, devono comunque trovarsi nell'ambito in tutto il programma in ogni unità di compilazione. Ai fini della valutazione con nome semplice, una volta raggiunto lo spazio dei nomi globale:

  • In primo luogo, viene effettuato un tentativo di valutare il nome all'interno del metodo del punto di ingresso generato e solo se questo tentativo ha esito negativo
  • Viene eseguita la valutazione "regolare" all'interno della dichiarazione dello spazio dei nomi globale.

Ciò potrebbe causare l'ombreggiatura dei nomi e dei tipi dichiarati all'interno dello spazio dei nomi globale, nonché l'ombreggiatura dei nomi importati.

Se la valutazione del nome semplice viene eseguita al di fuori delle istruzioni di primo livello e la valutazione restituisce una variabile o una funzione locale di primo livello, che dovrebbe causare un errore.

In questo modo si protegge la capacità futura di gestire meglio le "funzioni di primo livello" (scenario 2 in https://github.com/dotnet/csharplang/issues/3117) e sono in grado di fornire diagnostica utile agli utenti che credono erroneamente che siano supportati.