Instrukcje najwyższego poziomu
Notatka
Ten artykuł jest specyfikacją funkcji. Specyfikacja służy jako dokument projektowy dla funkcji. Zawiera proponowane zmiany specyfikacji wraz z informacjami wymaganymi podczas projektowania i opracowywania funkcji. Te artykuły są publikowane do momentu sfinalizowania proponowanych zmian specyfikacji i włączenia ich do obecnej specyfikacji ECMA.
Mogą wystąpić pewne rozbieżności między specyfikacją funkcji a ukończoną implementacją. Te różnice są przechwytywane w odpowiednich spotkania projektowego języka (LDM).
Więcej informacji na temat procesu wdrażania specyfikacji funkcji można znaleźć w standardzie języka C# w artykule dotyczącym specyfikacji .
Kwestia mistrzowska: https://github.com/dotnet/csharplang/issues/2765
Streszczenie
Zezwalaj na sekwencję instrukcji ,, aby mogły pojawić się bezpośrednio przed namespace_member_declarationw compilation_unit (tj. pliku źródłowym).
Semantyka polega na tym, że jeśli taka sekwencja instrukcji jest obecna, następująca deklaracja typu, z pominięciem rzeczywistej nazwy metody, zostanie wygenerowana:
partial class Program
{
static async Task Main(string[] args)
{
// statements
}
}
Zobacz również https://github.com/dotnet/csharplang/issues/3117.
Motywacja
Istnieje pewna ilość standardowego kodu nawet w najprostszych programach, ze względu na potrzebę jawnego zastosowania metody Main
. Wydaje się, że to przeszkadza w nauce języka i przejrzystości programu. Podstawowym celem tej funkcji jest umożliwienie pisania programów w języku C# bez zbędnego kodu szablonowego, z myślą o uczniach i przejrzystości kodu.
Szczegółowy projekt
Składnia
Jedyną dodatkową składnią jest umożliwienie sekwencji instrukcji s w jednostce kompilacji tuż przed namespace_member_declarations:
compilation_unit
: extern_alias_directive* using_directive* global_attributes? statement* namespace_member_declaration*
;
Tylko jedna jednostka kompilacji może zawierać instrukcje s.
Przykład:
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);
}
Semantyka
Jeśli jakiekolwiek instrukcje najwyższego poziomu są obecne w dowolnej jednostce kompilacji programu, znaczenie jest tak, jakby zostały połączone w treści bloku metody Main
klasy Program
w globalnej przestrzeni nazw w następujący sposób:
partial class Program
{
static async Task Main(string[] args)
{
// statements
}
}
Typ ma nazwę "Program", więc można odwoływać się do nazwy z kodu źródłowego. Jest to typ częściowy, więc typ o nazwie "Program" w kodzie źródłowym musi być również zadeklarowany jako częściowy.
Jednak nazwa metody "Main" jest używana tylko do celów ilustracyjnych, rzeczywista nazwa używana przez kompilator jest zależna od implementacji i nie można odwoływać się do metody według nazwy z kodu źródłowego.
Metoda jest wyznaczona jako punkt wejścia programu. Jawnie zadeklarowane metody, które zgodnie z konwencją można traktować jako kandydatów do punktu wejścia, są ignorowane. W takim przypadku zostanie zgłoszone ostrzeżenie. Podanie przełącznika kompilatora -main:<type>
w przypadku występowania instrukcji najwyższego poziomu jest błędem.
Metoda punktu wejścia zawsze ma jeden parametr formalny, string[] args
. Środowisko wykonywania tworzy i przekazuje argument string[]
zawierający argumenty wiersza polecenia określone podczas uruchamiania aplikacji. Argument string[]
nigdy nie ma wartości null, ale może mieć długość zero, jeśli nie określono argumentów wiersza polecenia. Parametr "args" znajduje się w zakresie w instrukcjach najwyższego poziomu i nie znajduje się poza nimi. Mają zastosowanie reguły regularnego konfliktu nazw/cieniowania.
Operacje asynchroniczne są dozwolone w instrukcjach na najwyższym poziomie w takim stopniu, w jakim są dozwolone w instrukcjach w ramach regularnej metody punktu wejścia asynchronicznego. Nie są one jednak wymagane, jeśli await
wyrażenia i inne operacje asynchroniczne zostaną pominięte, żadne ostrzeżenie nie zostanie wygenerowane.
Podpis metody wygenerowanego punktu wejścia jest określany na podstawie operacji używanych przez instrukcje najwyższego poziomu w następujący sposób:
Asynchroniczne-operacje\Zwróć-z-wyrażeniem | prezent | nieobecny |
---|---|---|
obecny | static Task<int> Main(string[] args) |
static Task Main(string[] args) |
nieobecny | static int Main(string[] args) |
static void Main(string[] args) |
Powyższy przykład daje następującą deklarację 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);
}
}
}
W tym samym czasie przykład podobny do następującego:
await System.Threading.Tasks.Task.Delay(1000);
System.Console.WriteLine("Hi!");
dałoby:
partial class Program
{
static async Task $Main(string[] args)
{
await System.Threading.Tasks.Task.Delay(1000);
System.Console.WriteLine("Hi!");
}
}
Przykład podobny do następującego:
await System.Threading.Tasks.Task.Delay(1000);
System.Console.WriteLine("Hi!");
return 0;
mogłoby dać:
partial class Program
{
static async Task<int> $Main(string[] args)
{
await System.Threading.Tasks.Task.Delay(1000);
System.Console.WriteLine("Hi!");
return 0;
}
}
I oto przykład:
System.Console.WriteLine("Hi!");
return 2;
dałoby:
partial class Program
{
static int $Main(string[] args)
{
System.Console.WriteLine("Hi!");
return 2;
}
}
Zakres zmiennych lokalnych najwyższego poziomu i funkcji lokalnych
Mimo że zmienne lokalne i funkcje najwyższego poziomu są "opakowane" w metodę wygenerowanego punktu wejścia, powinny one nadal znajdować się w widoczności w całym programie w każdej jednostce kompilacji. W celu oceny prostej nazwy po osiągnięciu globalnej przestrzeni nazw:
- Najpierw podjęto próbę oceny nazwy w wygenerowanej metodzie punktu wejścia i tylko wtedy, gdy ta próba zakończy się niepowodzeniem
- Wykonywana jest ocena "regularna" w globalnej deklaracji przestrzeni nazw.
Może to prowadzić do cieniowania nazw przestrzeni nazw i typów zadeklarowanych w globalnej przestrzeni nazw, a także do cieniowania importowanych nazw.
Jeśli ocena prostych nazw występuje poza instrukcjami najwyższego poziomu i skutkuje lokalną zmienną lub funkcją najwyższego poziomu, powinno to prowadzić do błędu.
W ten sposób chronimy naszą przyszłą zdolność do lepszego rozwiązywania problemów z "funkcjami najwyższego poziomu" (scenariusz 2 w https://github.com/dotnet/csharplang/issues/3117), i jesteśmy w stanie zapewnić przydatną diagnostykę użytkownikom, którzy błędnie uważają, że są one obsługiwane.
C# feature specifications