Instruções de nível superior
Nota
Este artigo é uma especificação de recurso. A especificação serve como o documento de design para o recurso. Ele inclui alterações de especificação propostas, juntamente com as informações necessárias durante o design e o desenvolvimento do recurso. Esses artigos são publicados até que as alterações de especificação propostas sejam finalizadas e incorporadas na especificação ECMA atual.
Pode haver algumas discrepâncias entre a especificação do recurso e a implementação concluída. Essas diferenças são capturadas nas notas pertinentes da reunião de design de idioma (LDM).
Você pode saber mais sobre o processo de adoção de speclets de recursos no padrão de linguagem C# no artigo sobre as especificações de .
Problema do especialista: https://github.com/dotnet/csharplang/issues/2765
Resumo
Permitir que uma sequência de instruções ocorra exatamente antes das namespace_member_declarations de uma compilation_unit (ou seja, arquivo de origem).
A semântica é que, se essa sequência de instruções estiver presente, a seguinte declaração de tipo, modulo o nome do método real, será emitida:
partial class Program
{
static async Task Main(string[] args)
{
// statements
}
}
Consulte também https://github.com/dotnet/csharplang/issues/3117.
Motivação
Há uma certa quantidade de clichês em torno até mesmo do mais simples dos programas, devido à necessidade de um método Main
explícito. Isso parece atrapalhar o aprendizado de idiomas e a clareza do programa. O objetivo principal do recurso, portanto, é permitir programas C# sem clichês desnecessários ao seu redor, para fins de aprendizado e clareza do código.
Design detalhado
Sintaxe
A única sintaxe adicional é permitir uma sequência de instruções em uma unidade de compilação, pouco antes das namespace_member_declarations:
compilation_unit
: extern_alias_directive* using_directive* global_attributes? statement* namespace_member_declaration*
;
Apenas uma compilation_unit pode conter instruções.
Exemplo:
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ântica
Se quaisquer instruções de nível superior estiverem presentes em qualquer unidade de compilação do programa, o significado será como se fossem combinadas no corpo do bloco de um método Main
de uma classe Program
no namespace global, da seguinte maneira:
partial class Program
{
static async Task Main(string[] args)
{
// statements
}
}
O tipo é denominado "Programa", portanto, pode ser referenciado pelo nome do código-fonte. É um tipo parcial, portanto, um tipo chamado "Programa" no código-fonte também deve ser declarado como parcial.
Mas o nome do método "Main" é usado apenas para fins ilustrativos, o nome real usado pelo compilador depende da implementação e o método não pode ser referenciado pelo nome do código-fonte.
O método é designado como o ponto de entrada do programa. Métodos declarados explicitamente que, por convenção, podem ser considerados candidatos a ponto de entrada são ignorados. Um aviso é emitido quando isso acontece. É um erro especificar a opção de compilador -main:<type>
quando há instruções de nível superior.
O método de ponto de entrada sempre tem um parâmetro formal, string[] args
. O ambiente de execução cria e passa um argumento string[]
que contém os argumentos de linha de comando que foram especificados quando o aplicativo foi iniciado. O argumento string[]
nunca é nulo, mas pode ter um comprimento igual a zero se nenhum argumento de linha de comando tiver sido especificado. O parâmetro 'args' está no escopo quando se trata de instruções de nível superior, e não está no escopo fora dessas instruções. Regras comuns de conflito/sombreamento de nomes são aplicáveis.
As operações assíncronas são permitidas em instruções de nível superior até o grau em que são permitidas em instruções dentro de um método de ponto de entrada assíncrono regular. No entanto, eles não serão necessários, se expressões await
e outras operações assíncronas forem omitidas, nenhum aviso será produzido.
A assinatura do método de ponto de entrada gerado é determinada com base nas operações usadas pelas instruções de nível superior da seguinte maneira:
Async-operations\Return-with-expression | Present | Absent |
---|---|---|
Present | static Task<int> Main(string[] args) |
static Task Main(string[] args) |
Absent | static int Main(string[] args) |
static void Main(string[] args) |
O exemplo acima produziria a seguinte declaração de método $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);
}
}
}
Ao mesmo tempo, um exemplo como este:
await System.Threading.Tasks.Task.Delay(1000);
System.Console.WriteLine("Hi!");
resultaria em:
partial class Program
{
static async Task $Main(string[] args)
{
await System.Threading.Tasks.Task.Delay(1000);
System.Console.WriteLine("Hi!");
}
}
Um exemplo como este:
await System.Threading.Tasks.Task.Delay(1000);
System.Console.WriteLine("Hi!");
return 0;
resultaria em:
partial class Program
{
static async Task<int> $Main(string[] args)
{
await System.Threading.Tasks.Task.Delay(1000);
System.Console.WriteLine("Hi!");
return 0;
}
}
E um exemplo como este:
System.Console.WriteLine("Hi!");
return 2;
resultaria em:
partial class Program
{
static int $Main(string[] args)
{
System.Console.WriteLine("Hi!");
return 2;
}
}
Escopo de variáveis locais de nível superior e funções locais
Embora variáveis e funções locais de nível superior sejam "encapsuladas" no método de ponto de entrada gerado, elas ainda devem estar no escopo em todo o programa em cada unidade de compilação. Para fins de avaliação de nome simples, uma vez que o namespace global é alcançado:
- Primeiro, é feita uma tentativa de avaliar o nome no método de ponto de entrada gerado e somente se a tentativa não for bem-sucedida
- A avaliação "regular" na declaração de namespace global é executada.
Isso pode levar ao sombreamento de nomes de namespaces e tipos declarados dentro do namespace global, bem como à sombreamento de nomes importados.
Se a avaliação de um nome simples ocorrer fora das instruções de nível superior e resultar em uma variável ou função local de nível superior, isso levará a um erro.
Dessa forma, protegemos nossa capacidade futura de lidar melhor com "funções de nível superior" (cenário 2 em https://github.com/dotnet/csharplang/issues/3117) e podemos fornecer diagnósticos úteis aos usuários que acreditam erroneamente que eles têm suporte.
C# feature specifications