Sdílet prostřednictvím


Příkazy nejvyšší úrovně

Poznámka

Tento článek je specifikace funkce. Specifikace slouží jako návrhový dokument pro funkci. Zahrnuje navrhované změny specifikace spolu s informacemi potřebnými při návrhu a vývoji funkce. Tyto články se publikují, dokud nebudou navrhované změny specifikace finalizovány a začleněny do aktuální specifikace ECMA.

Mezi specifikací funkce a dokončenou implementací může docházet k nějakým nesrovnalostem. Tyto rozdíly jsou zachyceny v poznámkách ze schůzky návrhu jazyka (LDM) .

Další informace o procesu přijetí specifikací funkcí do jazyka C# najdete v článku o specifikacích .

Problém šampiona: https://github.com/dotnet/csharplang/issues/2765

Shrnutí

Umožnit, aby došlo k posloupnosti příkazů těsně před namespace_member_declarationv compilation_unit (tj. zdrojový soubor).

Sémantika je taková, že pokud je k dispozici taková posloupnost příkazů , bude vyvolána následující deklarace typu, modulo název skutečné metody:

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

Viz také https://github.com/dotnet/csharplang/issues/3117.

Motivace

Existuje určité množství šablonového kódu i pro ty nejjednodušší programy, protože je potřeba explicitní Main metody. Zdá se, že to překáží ve výuce jazyků a srozumitelnosti programu. Hlavním cílem této funkce je tedy umožnit programům v jazyce C# obejít se bez zbytečného omílání kódu kvůli srozumitelnosti a lepšímu učení.

Podrobný návrh

Syntax

Jediný další syntaktický prvek umožňuje umístit posloupnost příkazů s v kompilační jednotce těsně před namespace_member_declarations:

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

Pouze jeden compilation_unit může mít příkazs.

Příklad:

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

Sémantika

Pokud jsou některé příkazy nejvyšší úrovně přítomny v jakékoli kompilační jednotce programu, význam je, jako by byly sloučeny v bloku těla Main metody Program třídy v globálním oboru názvů následujícím způsobem:

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

Typ má název "Program", takže lze na tento typ odkazovat podle názvu ze zdrojového kódu. Je to částečný typ, takže typ s názvem "Program" ve zdrojovém kódu musí být také deklarován jako částečný.
Ale název metody "Main" se používá pouze pro ilustrace, skutečný název používaný kompilátorem je závislý na implementaci a metoda nemůže být odkazována podle názvu ze zdrojového kódu.

Metoda je určena jako vstupní bod programu. Explicitně deklarované metody, které by se podle konvence mohly považovat za kandidáty vstupních bodů, jsou ignorovány. V takovém případě se zobrazí upozornění. Jedná se o chybu při zadání přepínače kompilátoru -main:<type>, pokud existují příkazy nejvyšší úrovně.

Metoda vstupního bodu má vždy jeden formální parametr, string[] args. Spouštěcí prostředí vytvoří a předá string[] argument obsahující argumenty příkazového řádku, které byly zadány při spuštění aplikace. Argument string[] nemá hodnotu null, ale pokud nebyly zadány žádné argumenty příkazového řádku, může mít délku nuly. Parametr args je v platnosti v rámci příkazů nejvyšší úrovně a není dostupný mimo ně. Pravidla pro konflikt/stínování běžných názvů platí.

Asynchronní operace jsou povolené v příkazech nejvyšší úrovně do té míry, do jaké jsou povoleny v příkazech v běžné asynchronní metodě vstupního bodu. Nejsou však vyžadovány, pokud jsou vynechány výrazy await a jiné asynchronní operace, nevygeneruje se žádné upozornění.

Podpis vygenerované metody vstupního bodu je určen na základě operací používaných příkazy nejvyšší úrovně následujícím způsobem:

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

Výše uvedený příklad by přinesl následující deklaraci metody $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);
        }
    }
}

Současně příklad podobný tomuto:

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

by přineslo:

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

Příklad podobný tomuto:

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

by přineslo

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

A příklad podobný tomuto:

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

by přineslo:

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

Rozsah místních proměnných nejvyšší úrovně a místních funkcí

Přestože jsou místní proměnné a funkce nejvyšší úrovně "zabalené" do vygenerované metody vstupního bodu, měly by být stále ve viditelnosti v celém programu a každé kompilační jednotce. Pro účely vyhodnocení jednoduchých názvů po dosažení globálního oboru názvů:

  • Nejprve se provede pokus o vyhodnocení názvu v rámci vygenerované metody vstupního bodu a pouze v případě, že tento pokus selže.
  • Provádí se "pravidelné" vyhodnocení v rámci globální deklarace oboru názvů.

To může vést ke stínování názvů a typů deklarovaných v rámci globálního oboru názvů a stínování importovaných názvů.

Pokud k vyhodnocení jednoduchého názvu dojde mimo příkazy nejvyšší úrovně a hodnocení odkazuje na místní proměnnou nebo funkci nejvyšší úrovně, mělo by to vést k chybě.

Tímto způsobem chráníme naši budoucí schopnost lépe řešit "funkce nejvyšší úrovně" (scénář 2 v https://github.com/dotnet/csharplang/issues/3117) a dokážeme poskytnout užitečnou diagnostiku uživatelům, kteří se omylem domnívají, že jsou podporovány.