Partage via


Instructions de niveau supérieur

Remarque

Cet article est une spécification de fonctionnalité. La spécification sert de document de conception pour la fonctionnalité. Il inclut les modifications de spécification proposées, ainsi que les informations nécessaires pendant la conception et le développement de la fonctionnalité. Ces articles sont publiés jusqu’à ce que les modifications de spécification proposées soient finalisées et incorporées dans la spécification ECMA actuelle.

Il peut y avoir des différences entre la spécification de la fonctionnalité et l’implémentation terminée. Ces différences sont consignées dans les notes pertinentes de la réunion de conception linguistique (LDM).

Vous pouvez en savoir plus sur le processus d’adoption des speclets de fonctionnalités dans la norme de langage C# dans l’article sur les spécifications .

Problème de champion : https://github.com/dotnet/csharplang/issues/2765

Résumé

Permettre à une séquence d'instructions de se produire juste avant les namespace_member_declaration d'une compilation_unit (c'est-à-dire un fichier source).

La sémantique est que si une telle séquence d’instructions est présente, la déclaration de type suivante, modulo le nom de la méthode réelle, est émise :

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

Voir aussi https://github.com/dotnet/csharplang/issues/3117.

Motivation

Il y a une certaine quantité de code standard qui entoure même les programmes les plus simples, en raison de la nécessité d'une méthode Main explicite. Cela semble entraver l'apprentissage des langues et la clarté du programme. L’objectif principal de la fonctionnalité est donc de permettre aux programmes C# d'exister sans code superflu qui les entoure, dans l’intérêt des apprenants et de la clarté du code.

Conception détaillée

Syntaxe

La seule syntaxe supplémentaire autorise une séquence d'instructions dans une unité de compilation, juste avant les namespace_member_declaration :

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

Une seule compilation_unit est autorisée à avoir des instructions.

Exemple:

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émantique

Si des instructions de niveau supérieur sont présentes dans une unité de compilation du programme, la signification est comme si elles étaient combinées dans le corps de bloc d’une méthode Main d’une classe Program dans l’espace de noms global, comme suit :

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

Le type est nommé « Program », de sorte qu’il peut être référencé par un nom à partir du code source. Il s’agit d’un type partiel. Par conséquent, un type nommé « Program » dans le code source doit également être déclaré comme partiel.
Mais le nom de méthode « Main » est utilisé uniquement à des fins d’illustration, le nom réel utilisé par le compilateur dépend de l’implémentation et la méthode ne peut pas être référencée par un nom à partir du code source.

La méthode est désignée comme point d’entrée du programme. Les méthodes qui sont explicitement déclarées et qui pourraient conventionnellement être considérées comme des candidats de point d'entrée sont ignorées. Un avertissement est signalé quand cela se produit. C'est une erreur de spécifier l'option de compilation -main:<type> lorsqu'il existe des instructions de niveau supérieur.

La méthode de point d’entrée a toujours un paramètre formel, string[] args. L’environnement d’exécution crée et transmet un argument string[] contenant les arguments de ligne de commande spécifiés lors du démarrage de l’application. L’argument string[] n’est jamais null, mais il peut avoir une longueur égale à zéro si aucun argument de ligne de commande n’a été spécifié. Le paramètre « args » est accessible au sein des instructions de niveau supérieur et n’est pas accessible en dehors d'elles. Les règles standard de conflit et de masquage des noms s’appliquent.

Les opérations asynchrones sont autorisées dans les instructions de niveau supérieur dans la même mesure qu'elles le sont dans les instructions d'une méthode de point d'entrée asynchrone standard. Toutefois, elles ne sont pas requises. Si les expressions await et d'autres opérations asynchrones sont omises, cela ne génère aucun avertissement.

La signature de la méthode de point d’entrée générée est déterminée en fonction des opérations utilisées par les instructions de niveau supérieur comme suit :

Opérations asynchrones\Retour avec expression Présent Absent
Présent static Task<int> Main(string[] args) static Task Main(string[] args)
Absent static int Main(string[] args) static void Main(string[] args)

L’exemple ci-dessus génère la déclaration de méthode $Main suivante :

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

En même temps, un exemple comme celui-ci :

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

générerait :

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

Voici un exemple semblable à ceci :

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

générerait :

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

Et un exemple semblable à ceci :

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

générerait :

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

Étendue des variables locales de niveau supérieur et des fonctions locales

Même si les variables et fonctions locales de niveau supérieur sont « encapsulées » dans la méthode de point d’entrée générée, elles doivent toujours être dans l’étendue du programme dans chaque unité de compilation. Pour les besoins de l’évaluation des noms simples, une fois que l’espace de noms global est atteint :

  • Tout d’abord, une tentative est effectuée pour évaluer le nom dans la méthode de point d’entrée générée, et c’est seulement si cette tentative échoue que...
  • L’évaluation « régulière » dans la déclaration d’espace de noms global est effectuée.

Cela peut entraîner le masquage de noms et de types déclarés dans l'espace de noms global, ainsi que le masquage de noms importés.

Si l’évaluation de nom simple se produit en dehors des instructions de niveau supérieur et que l’évaluation génère une variable ou une fonction locale de niveau supérieur, cela doit entraîner une erreur.

De cette façon, nous protégeons notre capacité future à mieux répondre aux « fonctions de niveau supérieur » (scénario 2 dans https://github.com/dotnet/csharplang/issues/3117), et sommes en mesure de fournir des diagnostics utiles aux utilisateurs qui croient qu’ils sont pris en charge par erreur.