Инструкции верхнего уровня (Top-Level Statement) позволяют отказаться от некоторых формальностей при написании приложений и сделать код проще. Возможно, это не очень будет заметно при написании сложных приложений, но может хорошо сэкономить время при проведении исследований, создании небольших утилит и прототипов. |
Минимальная платформа с полной поддержкой: нет ограничений.
Так обычно выглядит точка входа в консольное приложение:
namespace Prototype
{
public static class Program
{
public static int Main(string[] args)
{
bool success = DoSomeJob();
return success ? 1 : -1;
}
}
}
В данном случае C# 9 позволяет отказаться от таких шаблонных деталей как namespace, class Program, метод Main(…) и сразу начать писать код точки входа.
var result = DoSomeJob();
return result ? 1 : -1;
Такой код и называется инструкциями верхнего уровня.
Возможности
В инструкциях верхнего уровня можно:
- обращаться к переменной string[] args, представляющей собой массив аргументов переданных через командную строку.
- возвращать целочисленное (int) значение.
- вызывать асинхронные методы.
- объявлять локальные методы.
- объявлять свои пространства имен и классы, но только после кода инструкций верхнего уровня.
При сборке проекта компилятор в глобальном пространстве имен (global namespace) автоматически создаст класс Program c одним из четырех вариантов метода Main(…), в зависимости от написанного кода:
- void Main(string[] args)
- int Main(string[] args)
- Task Main(string[] args)
- Task<int> Main(string[] args)
Стоит отметить, что классы, объявленные в файле с инструкциями верхнего уровня будут обычными, а не внутренними классами Program, независимо от наличия собственного пространства имен.
Рассмотрим описанные возможности на следующем примере:
using System;
using System.Threading.Tasks;
using DemoApp.Reader;
var reader = new FileReader();
string content = await reader.Read(GetFileName());
Console.WriteLine(content);
return content.Length;
string GetFileName() => args[0];
namespace DemoApp.Reader
{
public class FileReader
{
public async Task Read(string fileName)
=> await System.IO.File.ReadAllTextAsync(fileName);
}
}
Результат декомпиляции собранного проекта будет выглядеть следующим образом:
using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using DemoApp.Reader;
[CompilerGenerated]
internal class Program
{
private static async Task<int> <Main>$(string[] args)
{
FileReader reader = new FileReader();
string content = await reader.Read(GetFileName());
Console.WriteLine(content);
return content.Length;
string GetFileName()
{
return args[0];
}
}
}
Отдельно, в пространстве имен DemoApp.Reader, можно найти класс FileReader. Его код, по сути, ничем не отличается от его объявления выше.
Ограничения
- В проекте может быть только один файл с инструкциями верхнего уровня.
- В проекте может быть только или объялена классическая точка входа (метод Main(…)) или указаны инструкции верхнего уровня.
Особенности
Несмотря на то, что класс Program автоматически генерируемый, на этапе разработки он доступен в приложении как и любой другой класс.
Кроме того, его можно расширить, добавив свои методы. Для этого необходимо самостоятельно объявить internal partial class Program. Соответственно, добавленные статические методы, будут также доступны в коде инструкций верхнего уровня.
Перепишем пример выше, заменив локальный метод GetFileName() на публичный статический:
using System;
using System.Threading.Tasks;
using DemoApp.Reader;
var reader = new FileReader();
string content = await reader.Read(GetFileName(args));
Console.WriteLine(content);
return content.Length;
internal partial class Program
{
public static string GetFileName(string[] args)
=> args[0];
}
namespace DemoApp.Reader
{
public class FileReader
{
public async Task Read(string fileName)
=> await System.IO.File.ReadAllTextAsync(fileName);
}
}
Результат декомпиляции будет следующий:
// Program
using System;
using System.Threading.Tasks;
using DemoApp.Reader;
internal class Program
{
private static async Task<int> <Main>$(string[] args)
{
FileReader reader = new FileReader();
string content = await reader.Read(GetFileName(args));
Console.WriteLine(content);
return content.Length;
}
public static string GetFileName(string[] args)
{
return args[0];
}
}