Megosztás a következőn keresztül:


Legfelső szintű állítások

Jegyzet

Ez a cikk egy funkcióspecifikáció. A specifikáció a funkció tervezési dokumentumaként szolgál. Tartalmazza a specifikáció javasolt módosításait, valamint a funkció tervezése és fejlesztése során szükséges információkat. Ezeket a cikkeket mindaddig közzéteszik, amíg a javasolt specifikációmódosításokat nem véglegesítik, és be nem építik a jelenlegi ECMA-specifikációba.

A szolgáltatás specifikációja és a befejezett implementáció között eltérések lehetnek. Ezeket a különbségeket a vonatkozó nyelvi tervezési értekezlet (LDM) megjegyzései rögzítik.

A funkcióspektusok C# nyelvi szabványba való bevezetésének folyamatáról a specifikációkcímű cikkben olvashat bővebben.

Bajnok kérdés: https://github.com/dotnet/csharplang/issues/2765

Összefoglalás

Tegyük lehetővé, hogy egy utasítások sorozata közvetlenül egy namespace_member_declarations compilation_unit (például forrásfájl) előtt forduljon elő.

A lényege az, hogy ha utasítások ilyen sorozata megjelenik, a következő típusdeklaráció lenne kibocsátva, modulo a tényleges metódus neve:

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

Lásd még: https://github.com/dotnet/csharplang/issues/3117.

Motiváció

Van egy bizonyos mennyiségű sablon szöveg még a legegyszerűbb programok körül, mert szükséges egy explicit Main módszer. Ez akadályozza a nyelvtanulást és a program átláthatóságát. A funkció elsődleges célja tehát az, hogy a C# programok körül felesleges sablonkód nélkül legyenek, a tanulók érdekében és a kód tisztaságának biztosítása céljából.

Részletes kialakítás

Szintaxis

Az egyetlen további szintaxis az, hogy engedélyezzük a utasításoksorozatát egy fordítási egységben, közvetlenül a namespace_member_declarationelőtt.

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

Csak egy compilation_unit rendelkezhet utasításokkal.

Példa:

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

Szemantika

Ha bármelyik legfelső szintű utasítás megtalálható a program bármely fordítási egységében, a jelentése olyan, mintha a globális névtérben lévő Main osztály Program metódusának blokktörzsében lennének egyesítve, az alábbiak szerint:

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

A típus neve "Program", ezért a forráskódból származó név alapján lehet hivatkozni. Részleges típus, ezért a forráskódban a "Program" nevű típust is részlegesként kell deklarálni.
A "Main" metódusnév azonban csak illusztrációs célokra használható, a fordító által használt tényleges név implementációfüggő, és a metódus nem hivatkozható a forráskódból származó név alapján.

A metódus a program belépési pontjaként van kijelölve. A kifejezetten deklarált módszereket, amelyek konvenció szerint belépési pont jelöltjeinek tekinthetők, figyelmen kívül hagyják. A rendszer figyelmeztetést ad ki, amikor ez történik. A fordítókapcsolóval -main:<type> másik belépési pontot is megadhat.

A belépési pont metódusának mindig van egy formális paramétere, string[] args. A végrehajtási környezet létrehoz és átad egy string[] argumentumot, amely tartalmazza az alkalmazás indításakor megadott parancssori argumentumokat. A string[] argumentum soha nem null értékű, de lehet, hogy nulla hosszúságú, ha nem adott meg parancssori argumentumokat. Az "args" paraméter a legfelső szintű utasítások hatókörében található, és nem tartozik rajtuk kívülre. A normál névütközési/árnyékolási szabályok érvényesek.

Az aszinkron műveletek a felső szintű utasításokban engedélyezettek olyan mértékben, hogy a normál aszinkron belépésipont-metódusban lévő utasításokban engedélyezettek legyenek. Ezekre azonban nincs szükség, ha await kifejezések és egyéb aszinkron műveletek kimaradnak, a rendszer nem hoz létre figyelmeztetést.

A létrehozott belépési pont metódus aláírását a legfelső szintű utasítások által használt műveletek alapján határozzuk meg az alábbiak szerint:

Aszinkron-műveletek\Visszatérés kifejezéssel Bemutató Hiányzó
Bemutató static Task<int> Main(string[] args) static Task Main(string[] args)
Hiányzó static int Main(string[] args) static void Main(string[] args)

A fenti példa a következő $Main metódusdeklarációt eredményezné:

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

Ugyanakkor egy ilyen példa:

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

hozam:

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

Egy ilyen példa:

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

hozam:

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

És egy ilyen példa:

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

hozam:

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

Felső szintű helyi változók és helyi függvények hatóköre

Annak ellenére, hogy a legfelső szintű helyi változók és függvények "be vannak csomagolva" a létrehozott belépési pont metódusába, a teljes program hatókörében kell lenniük minden fordítási egységben. Az egyszerű név kiértékelése céljából a globális névtér elérése után:

  • Először megkísérli kiértékelni a nevet a létrehozott belépési pont metódusán belül, és csak akkor, ha ez a kísérlet meghiúsul
  • A globális névtér-deklaráción belül a "reguláris" kiértékelés történik.

Ez a globális névtérben deklarált névterek és típusok névárnyékolásához, valamint az importált nevek árnyékolásához vezethet.

Ha az egyszerű névkiértékelés a legfelső szintű utasításokon kívül történik, és az értékelés egy legfelső szintű helyi változót vagy függvényt eredményez, amely hibát okozhat.

Ily módon megvédjük a jövőbeli képességünket a "Legfelső szintű függvények" jobb kezeléséhez (https://github.com/dotnet/csharplang/issues/31172. forgatókönyv), és hasznos diagnosztikát tudunk adni azoknak a felhasználóknak, akik tévesen úgy vélik, hogy támogatottak.