C# 9 – Инструкции верхнего уровня

C# logoИнструкции верхнего уровня (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];
    }
}

Добавить комментарий