Instrucciones de nivel superiorTop-level statements

  • [x] propuesto[x] Proposed
  • [x] prototipo: iniciado[x] Prototype: Started
  • [x] implementación: iniciada[x] Implementation: Started
  • [] Especificación: no iniciada[ ] Specification: Not Started

ResumenSummary

Permita que una secuencia de instrucciones se produzca justo antes de la namespace_member_declaration de un compilation_unit (es decir, un archivo de código fuente).Allow a sequence of statements to occur right before the namespace_member_declaration s of a compilation_unit (i.e. source file).

La semántica es que, si dicha secuencia de instrucciones está presente, se emitirá la siguiente declaración de tipos, módulo el nombre de tipo real y el nombre del método:The semantics are that if such a sequence of statements is present, the following type declaration, modulo the actual type name and the method name, would be emitted:

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

Vea también https://github.com/dotnet/csharplang/issues/3117.See also https://github.com/dotnet/csharplang/issues/3117.

MotivaciónMotivation

Hay una determinada cantidad de reutilización que se encuentra en la parte más sencilla de los programas, debido a la necesidad de un Main método explícito.There's a certain amount of boilerplate surrounding even the simplest of programs, because of the need for an explicit Main method. Esto parece ser un aprendizaje lingüístico y una claridad de los programas.This seems to get in the way of language learning and program clarity. Por lo tanto, el objetivo principal de la característica es permitir programas de C# sin reutilizaciones innecesarias en torno a ellos, por lo que los aprendidos y la claridad del código.The primary goal of the feature therefore is to allow C# programs without unnecessary boilerplate around them, for the sake of learners and the clarity of code.

Diseño detalladoDetailed design

SintaxisSyntax

La única sintaxis adicional es permitir una secuencia de instrucciones en una unidad de compilación, justo antes de la namespace_member_declaration s:The only additional syntax is allowing a sequence of statement s in a compilation unit, just before the namespace_member_declaration s:

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

Solo una compilation_unit puede tener instrucciones.Only one compilation_unit is allowed to have statement s.

Ejemplo:Example:

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

SemánticaSemantics

Si las instrucciones de nivel superior están presentes en cualquier unidad de compilación del programa, el significado es como si se combinaran en el cuerpo del bloque de un Main método de una Program clase en el espacio de nombres global, como se indica a continuación:If any top-level statements are present in any compilation unit of the program, the meaning is as if they were combined in the block body of a Main method of a Program class in the global namespace, as follows:

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

Tenga en cuenta que los nombres "Program" y "Main" se usan solo con fines ilustrativos, los nombres reales usados por el compilador dependen de la implementación y no se puede hacer referencia al método por el nombre del código fuente.Note that the names "Program" and "Main" are used only for illustrations purposes, actual names used by compiler are implementation dependent and neither the type, nor the method can be referenced by name from source code.

El método se designa como el punto de entrada del programa.The method is designated as the entry point of the program. Los métodos declarados explícitamente que por Convención se pueden considerar como candidatos de punto de entrada se omiten.Explicitly declared methods that by convention could be considered as an entry point candidates are ignored. Cuando esto sucede, se genera una advertencia.A warning is reported when that happens. Es un error especificar el -main:<type> modificador del compilador cuando hay instrucciones de nivel superior.It is an error to specify -main:<type> compiler switch when there are top-level statements.

El método de punto de entrada siempre tiene un parámetro formal, string[] args .The entry point method always has one formal parameter, string[] args. El entorno de ejecución crea y pasa un string[] argumento que contiene los argumentos de la línea de comandos que se especificaron cuando se inició la aplicación.The execution environment creates and passes a string[] argument containing the command-line arguments that were specified when the application was started. El string[] argumento nunca es null, pero puede tener una longitud de cero si no se especificó ningún argumento de línea de comandos.The string[] argument is never null, but it may have a length of zero if no command-line arguments were specified. El parámetro ' args ' está en el ámbito dentro de las instrucciones de nivel superior y no está en el ámbito fuera de ellos.The ‘args’ parameter is in scope within top-level statements and is not in scope outside of them. Se aplican reglas de sombra o conflictos de nombres normales.Regular name conflict/shadowing rules apply.

Las operaciones asincrónicas se permiten en las instrucciones de nivel superior hasta el grado en que se permiten en las instrucciones de un método de punto de entrada asincrónico normal.Async operations are allowed in top-level statements to the degree they are allowed in statements within a regular async entry point method. Sin embargo, no son necesarios, si se await omiten las expresiones y otras operaciones asincrónicas, no se genera ninguna advertencia.However, they are not required, if await expressions and other async operations are omitted, no warning is produced.

La firma del método de punto de entrada generado se determina en función de las operaciones que utilizan las instrucciones de nivel superior de la manera siguiente:The signature of the generated entry point method is determined based on operations used by the top level statements as follows:

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

En el ejemplo anterior se produciría la siguiente $Main declaración de método:The example above would yield the following $Main method declaration:

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

Al mismo tiempo, un ejemplo como este:At the same time an example like this:

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

produciría:would yield:

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

Un ejemplo como este:An example like this:

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

produciría:would yield:

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

Y un ejemplo como este:And an example like this:

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

produciría:would yield:

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

Ámbito de las variables locales de nivel superior y las funciones localesScope of top-level local variables and local functions

Aunque las funciones y las variables locales de nivel superior se "encapsulan" en el método de punto de entrada generado, deben seguir en el ámbito de todo el programa en cada unidad de compilación.Even though top-level local variables and functions are "wrapped" into the generated entry point method, they should still be in scope throughout the program in every compilation unit. Para la evaluación de nombre simple, una vez que se alcanza el espacio de nombres global:For the purpose of simple-name evaluation, once the global namespace is reached:

  • En primer lugar, se realiza un intento de evaluar el nombre dentro del método de punto de entrada generado y solo si se produce un error en este intento.First, an attempt is made to evaluate the name within the generated entry point method and only if this attempt fails
  • Se realiza la evaluación "normal" dentro de la declaración del espacio de nombres global.The "regular" evaluation within the global namespace declaration is performed.

Esto podría dar lugar a la sombra de nombres de espacios de nombres y tipos declarados en el espacio de nombres global, así como a la sombra de nombres importados.This could lead to name shadowing of namespaces and types declared within the global namespace as well as to shadowing of imported names.

Si la evaluación del nombre simple se produce fuera de las instrucciones de nivel superior y la evaluación produce una variable o función local de nivel superior, esto debe producir un error.If the simple name evaluation occurs outside of the top-level statements and the evaluation yields a top-level local variable or function, that should lead to an error.

De esta forma, se protege nuestra futura capacidad para abordar mejor las "funciones de nivel superior" (escenario 2 en https://github.com/dotnet/csharplang/issues/3117) , y se pueden proporcionar diagnósticos útiles a los usuarios que no creen que se admitan por equivocación).In this way we protect our future ability to better address "Top-level functions" (scenario 2 in https://github.com/dotnet/csharplang/issues/3117), and are able to give useful diagnostics to users who mistakenly believe them to be supported.