Поделиться через


Руководство. Создание программ C# на основе файлов

Приложения на основе файлов — это программы, содержащиеся в одном *.cs файле, который вы создаете и запускаете без соответствующего файла проекта (*.csproj). Приложения на основе файлов идеально подходят для обучения C#, так как они имеют меньшую сложность: вся программа хранится в одном файле. Приложения на основе файлов также полезны для создания служебных программ командной строки. На платформах Unix можно запускать приложения на основе файлов с помощью #!директив (shebang). Изучив это руководство, вы:

  • Создайте файловое приложение.
  • Добавьте поддержку Unix Shebang (#!).
  • Чтение аргументов командной строки.
  • Обработка стандартных входных данных.
  • Напишите вывод ASCII-графики.
  • Обработка аргументов командной строки.
  • Используйте проанализированные результаты командной строки.
  • Протестируйте окончательное приложение.

Вы создаете приложение, работающее с файлами, которое записывает текст в виде графики ASCII. Приложение содержится в одном файле, использует пакеты NuGet и реализует основные функции.

Предпосылки

Создание файлового приложения

  1. Откройте Visual Studio Code и создайте новый файл с именем AsciiArt.cs. Введите следующий текст:

    Console.WriteLine("Hello, world!");
    
  2. Сохраните файл. Затем откройте интегрированный терминал в Visual Studio Code и введите следующее:

    dotnet AsciiArt.cs
    

При первом запуске этой программы dotnet хост создает исполняемый файл из вашего исходного файла, сохраняет артефакты сборки во временной папке, а затем запускает созданный исполняемый файл. Этот опыт можно проверить, введя dotnet AsciiArt.cs еще раз. На этот раз dotnet хост определяет, что исполняемый файл является актуальным и запускает его без повторной сборки. Выходные данные сборки не отображаются.

В предыдущих шагах показано, что приложения на основе файлов не являются файлами скриптов. Это исходные файлы C#, которые dotnet хост создает с помощью сгенерированного файла проекта во временной папке. Одна из строк выходных данных, отображаемых при сборке программы, должна выглядеть примерно так (в Windows):

AsciiArt succeeded (7.3s) → AppData\Local\Temp\dotnet\runfile\AsciiArt-85c58ae0cd68371711f06f297fa0d7891d0de82afde04d8c64d5f910ddc04ddc\bin\debug\AsciiArt.dll

На платформах Unix выходная папка похожа на следующее:

AsciiArt succeeded (7.3s) → Library/Application Support/dotnet/runfile/AsciiArt-85c58ae0cd68371711f06f297fa0d7891d0de82afde04d8c64d5f910ddc04ddc/bin/debug/AsciiArt.dll

Эти выходные данные сообщают о том, где размещаются временные файлы и выходные данные сборки. В этом руководстве каждый раз при редактировании исходного файла dotnet хост обновляет исполняемый файл до его запуска.

Приложения на основе файлов — это обычные программы C#. Единственное ограничение заключается в том, что их необходимо записать в один исходный файл. Можно использовать операторы верхнего уровня или классический метод Main в качестве точки входа. Можно объявить любые типы: классы, интерфейсы и структуры. Алгоритмы можно структурировать в файловом приложении так же, как и в любой программе C#. Можно даже объявить несколько пространств имен для упорядочивания кода. Если приложение на основе файлов становится слишком большим для одного файла, его можно преобразовать в программу на основе проекта и разделить источник на несколько файлов. Приложения на основе файлов — это отличный инструмент прототипа. Вы можете начать экспериментировать с минимальными затратами, чтобы подтвердить концепции и создавать алгоритмы.

Поддержка Unix Shebang (#!)

Замечание

Поддержка директив #! применяется только на платформах Unix. Для Windows нет аналогичной директивы для непосредственного выполнения программы C#. В Windows необходимо использовать dotnet в командной строке.

В Unix запустите приложения на основе файлов непосредственно, введя только имя исходного файла. Вместо использования dotnet AsciiArt.csвведите имя исходного файла в командной строке. Необходимо внести два изменения:

  1. Задайте разрешения на выполнение исходного файла:

    chmod +x AsciiArt.cs
    
  2. Добавьте директиву shebang (#!) в качестве первой строки AsciiArt.cs файла:

    #!/usr/local/share/dotnet/dotnet
    

Расположение dotnet может отличаться в разных установках Unix. Используйте команду which dotnet, чтобы найти хост в вашей dotnet среде.

Кроме того, можно использовать #!/usr/bin/env dotnet для автоматического разрешения пути dotnet из переменной среды PATH:

#!/usr/bin/env dotnet

После внесения этих двух изменений вы можете запустить программу из командной строки напрямую:

./AsciiArt.cs

Если вы предпочитаете, можете удалить расширение, чтобы вместо этого ввести ./AsciiArt. Вы можете добавить #! в исходный файл, даже если вы используете Windows. Командная строка Windows не поддерживается #!, но компилятор C# разрешает директиву в приложениях на основе файлов на всех платформах.

Чтение аргументов командной строки

Теперь напишите все аргументы в командной строке в выходные данные.

  1. Замените текущее содержимое AsciiArt.cs следующим кодом:

    if (args.Length > 0)
    {
        string message = string.Join(' ', args);
        Console.WriteLine(message);
    }
    
  2. Чтобы запустить эту версию, введите следующую команду:

    dotnet AsciiArt.cs -- This is the command line.
    

    Параметр -- указывает, что все следующие аргументы команд должны передаваться в программу AsciiArt. Аргументы This is the command line. передаются в виде массива строк, где каждая строка — одно слово: This, is, theи commandline..

Эта версия демонстрирует следующие новые понятия:

  • Предопределенная переменная args передает аргументы командной строки программе. Переменная args представляет собой массив строк: string[] Если длина args равна 0, аргументы не были предоставлены. В противном случае каждое слово в списке аргументов хранится в соответствующей записи в массиве.
  • Метод string.Join объединяет несколько строк в одну строку с указанным разделителем. В этом случае разделитель — один пробел.
  • Console.WriteLine записывает строку в стандартную выходную консоль, за которой следует новая строка.

Обработка стандартных входных данных

Предыдущий код правильно обрабатывает аргументы командной строки. Теперь добавьте код для обработки входных данных из стандартных входных данных (stdin) вместо аргументов командной строки.

  1. Добавьте следующий else пункт к утверждению if, которое вы добавили в предыдущем коде.

    else
    {
        while (Console.ReadLine() is string line && line.Length > 0)
        {
            Console.WriteLine(line);
        }
    }
    

    Предыдущий код считывает данные с консоли до тех пор, пока не будет считана пустая строка или символ null. (Метод Console.ReadLine возвращает null, если входной поток закрыт при нажатии ctrl+C.)

  2. Проверьте стандартные входные данные, создав текстовый файл в той же папке. Назовите файл input.txt и добавьте следующие строки:

    Hello from ...
    dotnet!
    
    You can create
    file-based apps
    in .NET 10 and
    C# 14
    
    Have fun writing
    useful utilities
    

    Оставьте строки короткими, чтобы они правильно форматировались при добавлении возможности использовать ASCII-графику.

  3. Снова запустите программу.

    С помощью bash:

    cat input.txt | dotnet AsciiArt.cs
    

    Или с помощью PowerShell:

    Get-Content input.txt | dotnet AsciiArt.cs
    

Теперь программа может принимать аргументы командной строки или стандартные входные данные.

Написать ASCII-арт

Затем добавьте пакет, поддерживающий искусство ASCII, Colorful.Console. Чтобы добавить пакет в файловое приложение, используйте директиву #:package .

  1. Добавьте следующую директиву после директивы #! в AsciiArt.cs файле:

    #:package Colorful.Console@1.2.15
    

    Это важно

    Версия 1.2.15 была последней версией пакета Colorful.Console, когда это руководство было в последний раз обновлено. Проверьте страницу NuGet пакета для последней версии, чтобы убедиться, что вы используете версию пакета с последними исправлениями безопасности.

  2. Измените строки таким образом, чтобы вместо вызова Console.WriteLine использовался метод Colorful.Console.WriteAscii.

    Colorful.Console.WriteAscii(line);
    
  3. Запустите программу. Вы видите вывод ASCII-арта вместо отображаемого текста.

Параметры команды обработки

Затем добавьте синтаксический анализ командной строки. Текущая версия записывает каждое слово в виде другой строки выходных данных. Аргументы командной строки, которые вы добавляете, поддерживают две функции:

  1. Процитировать несколько слов, которые должны быть написаны на одной строке:

    AsciiArt.cs "This is line one" "This is another line" "This is the last line"
    
  2. Добавьте параметр для приостановки между каждой --delay строкой:

    AsciiArt.cs --delay 1000
    

Пользователи могут использовать оба аргумента вместе.

Большинство приложений командной строки должны анализировать аргументы командной строки для эффективной обработки параметров, команд и ввода пользователем. Библиотека System.CommandLine предоставляет комплексные возможности для обработки команд, вложенных команд, параметров и аргументов. Вы можете сосредоточиться на том, что делает ваше приложение, а не механика синтаксического анализа входных данных командной строки.

Библиотека System.CommandLine предлагает несколько ключевых преимуществ:

  • Автоматическое создание и проверка текста справки.
  • Поддержка командной строки по стандартам POSIX и Windows.
  • Встроенные возможности завершения вкладок.
  • Согласованное поведение синтаксического анализа между приложениями.
  1. Добавьте пакет System.CommandLine. Добавьте эту директиву после существующей директивы пакета:

    #:package System.CommandLine@2.0.0
    

    Это важно

    Версия 2.0.0 была последней, когда это руководство было в последний раз обновлено. Если доступна более новая версия, используйте последнюю версию, чтобы обеспечить наличие последних пакетов безопасности. Проверьте страницу NuGet пакета для последней версии, чтобы убедиться, что вы используете версию пакета с последними исправлениями безопасности.

  2. Добавьте необходимые операторы using в верхней части файла (после директив #! и #:package):

    using System.CommandLine;
    using System.CommandLine.Parsing;
    
  3. Определите параметр задержки и аргумент сообщений. Добавьте следующий код для создания CommandLine.Option и CommandLine.Argument объектов для представления параметра командной строки и аргумента:

    Option<int> delayOption = new("--delay")
    {
        Description = "Delay between lines, specified as milliseconds.",
        DefaultValueFactory = parseResult => 100
    };
    
    Argument<string[]> messagesArgument = new("Messages")
    {
        Description = "Text to render."
    };
    

    В приложениях командной строки параметры обычно начинаются с -- (двойной дефис) и могут принимать аргументы. Параметр --delay принимает целый аргумент, указывающий задержку в миллисекундах. messagesArgument определяет, как все оставшиеся метки интерпретируются как текст после разбора параметров. Каждый токен становится отдельной строкой в массиве, но текст может быть заключен в кавычки, чтобы объединить несколько слов в один токен. Например, "This is one message" становится одним маркером, а This is four tokens становится четырьмя отдельными маркерами.

    Предыдущий код определяет тип аргумента для --delay параметра и что аргументы являются массивом значений string . Это приложение имеет только одну команду, поэтому вы используете корневую команду.

  4. Создайте корневую команду и настройте ее с помощью параметра и аргумента. Добавьте аргумент и параметр в корневую команду:

    RootCommand rootCommand = new("Ascii Art file-based app sample");
    
    rootCommand.Options.Add(delayOption);
    rootCommand.Arguments.Add(messagesArgument);
    
  5. Добавьте код для синтаксического анализа аргументов командной строки и обработки ошибок. Этот код проверяет аргументы командной строки и сохраняет проанализированные аргументы в объекте System.CommandLine.ParseResult :

    ParseResult result = rootCommand.Parse(args);
    foreach (ParseError parseError in result.Errors)
    {
        Console.Error.WriteLine(parseError.Message);
    }
    if (result.Errors.Count > 0)
    {
        return 1;
    }
    

Предыдущий код проверяет все аргументы командной строки. Если проверка завершается ошибкой, приложение записывает ошибки в консоль и завершает работу.

Использование проанализированных результатов командной строки

Теперь завершите работу приложения для использования параметров синтаксического анализа и записи выходных данных. Сначала определите запись для хранения параметров синтаксического анализа. Приложения на основе файлов могут включать объявления типов, такие как записи и классы. Они должны быть после всех инструкций верхнего уровня и локальных функций.

  1. Добавьте объявление record для хранения сообщений и значения параметра задержки:

    public record AsciiMessageOptions(string[] Messages, int Delay);
    
  2. Добавьте следующую локальную функцию перед объявлением записи. Этот метод обрабатывает аргументы командной строки и стандартные входные данные и возвращает новый экземпляр записи:

    async Task<AsciiMessageOptions> ProcessParseResults(ParseResult result)
    {
        int delay = result.GetValue(delayOption);
        List<string> messages = [.. result.GetValue(messagesArgument) ?? Array.Empty<string>()];
    
        if (messages.Count == 0)
        {
            while (Console.ReadLine() is string line && line.Length > 0)
            {
                Colorful.Console.WriteAscii(line);
                await Task.Delay(delay);
            }
        }
        return new([.. messages], delay);
    }
    
  3. Создайте локальную функцию для рисования ASCII графики с заданной задержкой. Эта функция записывает каждое сообщение в записи с указанной задержкой между каждым сообщением:

    async Task WriteAsciiArt(AsciiMessageOptions options)
    {
        foreach (string message in options.Messages)
        {
            Colorful.Console.WriteAscii(message);
            await Task.Delay(options.Delay);
        }
    }
    
  4. Замените if предложение, написанного ранее, следующим кодом, который обрабатывает аргументы командной строки и записывает выходные данные:

    var parsedArgs = await ProcessParseResults(result);
    
    await WriteAsciiArt(parsedArgs);
    return 0;
    

Вы создали record тип, который предоставляет структуру для параметров и аргументов командной строки синтаксического анализа. Новые локальные функции создают экземпляр записи и используют её для создания вывода в формате ASCII-графики.

Тестирование окончательного приложения

Протестируйте приложение, выполнив несколько различных команд. Если у вас возникли проблемы, вот готовый пример для сравнения с тем, что вы создали:

#!/usr/bin/env dotnet

#:package Colorful.Console@1.2.15
#:package System.CommandLine@2.0.0

using System.CommandLine;
using System.CommandLine.Parsing;

Option<int> delayOption = new("--delay")
{
    Description = "Delay between lines, specified as milliseconds.",
    DefaultValueFactory = parseResult => 100
};

Argument<string[]> messagesArgument = new("Messages")
{
    Description = "Text to render."
};

RootCommand rootCommand = new("Ascii Art file-based app sample");

rootCommand.Options.Add(delayOption);
rootCommand.Arguments.Add(messagesArgument);

ParseResult result = rootCommand.Parse(args);
foreach (ParseError parseError in result.Errors)
{
    Console.Error.WriteLine(parseError.Message);
}
if (result.Errors.Count > 0)
{
    return 1;
}

var parsedArgs = await ProcessParseResults(result);

await WriteAsciiArt(parsedArgs);
return 0;

async Task<AsciiMessageOptions> ProcessParseResults(ParseResult result)
{
    int delay = result.GetValue(delayOption);
    List<string> messages = [.. result.GetValue(messagesArgument) ?? Array.Empty<string>()];

    if (messages.Count == 0)
    {
        while (Console.ReadLine() is string line && line.Length > 0)
        {
            // <WriteAscii>
            Colorful.Console.WriteAscii(line);
            // </WriteAscii>
            await Task.Delay(delay);
        }
    }
    return new([.. messages], delay);
}

async Task WriteAsciiArt(AsciiMessageOptions options)
{
    foreach (string message in options.Messages)
    {
        Colorful.Console.WriteAscii(message);
        await Task.Delay(options.Delay);
    }
}

public record AsciiMessageOptions(string[] Messages, int Delay);

В этом руководстве вы узнали, как создать файловое приложение, где вы создаете программу в одном файле C#. Эти программы не используют файл проекта и могут использовать директиву #! в системах Unix. Учащиеся могут создавать эти программы после работы с нашими онлайн-учебниками и перед созданием более крупных приложений на основе проектов. Приложения на основе файлов также являются отличной платформой для служебных программ командной строки.