Compartilhar via


Tutorial: Criar programas C# baseados em arquivo

Aplicativos baseados em arquivo são programas contidos em um único *.cs arquivo que são criados e executados sem um arquivo de projeto (*.csproj) correspondente. Aplicativos baseados em arquivo são ideais para aprender C# porque têm menos complexidade: todo o programa é armazenado em um único arquivo. Aplicativos baseados em arquivo também são úteis para criar utilitários de linha de comando. Em plataformas Unix, aplicativos baseados em arquivo podem ser executados usando #!(shebang) diretivas. Neste tutorial, você:

  • Crie um programa baseado em arquivo.
  • Adicione suporte ao shebang do Unix (#!).
  • Ler argumentos de linha de comando.
  • Manipule a entrada padrão.
  • Gravar saída de arte ASCII.
  • Processe argumentos de linha de comando.
  • Use resultados de linha de comando analisados.
  • Teste o aplicativo final.

Você cria um programa baseado em arquivo que grava texto como arte ASCII. O aplicativo está contido em um único arquivo, usa pacotes NuGet que implementam alguns dos principais recursos.

Pré-requisitos

Criar um programa baseado em arquivo

  1. Abra o Visual Studio Code e crie um novo arquivo chamado AsciiArt.cs. Insira o seguinte texto:

    Console.WriteLine("Hello, world!");
    
  2. Salve o arquivo. Em seguida, abra o terminal integrado no Visual Studio Code e digite:

    dotnet run AsciiArt.cs
    

Na primeira vez que você executar este programa, o dotnet host criará o executável do arquivo de origem, armazenará artefatos de build em uma pasta temporária e executará o executável criado. Você pode verificar essa experiência digitando dotnet run AsciiArt.cs novamente. Desta vez, o dotnet host determina que o executável é atual e executa o executável sem compilar novamente. Você não vê nenhuma saída de build.

As etapas anteriores demonstram que os aplicativos baseados em arquivo não são arquivos de script. São arquivos de origem C# criados usando um arquivo de projeto gerado em uma pasta temporária. Uma das linhas de saída exibidas quando você criou o programa deve ser semelhante a esta (no Windows):

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

Em plataformas unix, a pasta de saída é algo semelhante a:

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

Essa saída informa onde os arquivos temporários e as saídas de build são colocados. Ao longo deste tutorial, sempre que você edita o arquivo de origem, o dotnet host atualiza o executável antes de ser executado.

Aplicativos baseados em arquivo são programas C# regulares. A única limitação é que eles devem ser gravados em um arquivo de origem. Você pode usar instruções de nível superior ou um método clássico Main como um ponto de entrada. Você pode declarar qualquer tipo: classes, interfaces e structs. Você pode estruturar os algoritmos em um programa baseado em arquivo da mesma forma que faria em qualquer programa C#. Você pode até declarar vários namespaces para organizar seu código. Se você descobrir que um programa baseado em arquivo está crescendo muito grande para um único arquivo, você pode convertê-lo em um programa baseado em projeto e dividir a origem em vários arquivos. Aplicativos baseados em arquivo são uma ótima ferramenta de criação de protótipos. Você pode começar a experimentar com sobrecarga mínima para provar conceitos e criar algoritmos.

Suporte ao unix shebang (#!)

Observação

O suporte para #! diretivas se aplica somente a plataformas unix. Não há uma diretiva semelhante para o Windows executar diretamente um programa em C#. No Windows, você deve usar dotnet run na linha de comando.

No unix, você pode executar aplicativos baseados em arquivo diretamente, digitando o nome do arquivo de origem na linha de comando em vez de dotnet run. Você precisa fazer duas alterações:

  1. Defina permissões de execução no arquivo de origem:

    chmod +x AsciiArt.cs
    
  2. Adicione uma diretiva shebang (#!) como a primeira linha do AsciiArt.cs arquivo:

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

O local pode dotnet ser diferente em diferentes instalações do Unix. Use o comando which dotnet para localizar o dotnet host em seu ambiente.

Como alternativa, você pode usar #!/usr/bin/env dotnet para resolver o caminho dotnet da variável de ambiente PATH automaticamente:

#!/usr/bin/env dotnet

Depois de fazer essas duas alterações, você pode executar o programa diretamente na linha de comando:

./AsciiArt.cs

Se preferir, você pode remover a extensão para poder digitar ./AsciiArt . Você pode adicionar o arquivo de origem #! mesmo se usar o Windows. A linha de comando do Windows não dá suporte #!, mas o compilador C# permite essa diretiva em aplicativos baseados em arquivo em todas as plataformas.

Ler argumentos de linha de comando

Agora, escreva todos os argumentos na linha de comando na saída.

  1. Substitua o conteúdo atual pelo AsciiArt.cs seguinte código:

    if (args.Length > 0)
    {
        string message = string.Join(' ', args);
        Console.WriteLine(message);
    }
    
  2. Você pode executar esta versão digitando o seguinte comando:

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

    A -- opção indica que todos os argumentos de comando a seguir devem ser passados para o programa AsciiArt. Os argumentos são passados This is the command line. como uma matriz de cadeias de caracteres, em que cada cadeia de caracteres é uma palavra: This, , is, thee commandline..

Esta versão demonstra estes novos conceitos:

  • Os argumentos de linha de comando são passados para o programa usando a variável argspredefinida. A args variável é uma matriz de cadeias de caracteres: string[]. Se o comprimento for args 0, isso significa que nenhum argumento foi fornecido. Caso contrário, cada palavra na lista de argumentos será armazenada na entrada correspondente na matriz.
  • O string.Join método une várias cadeias de caracteres em uma única cadeia de caracteres, com o separador especificado. Nesse caso, o separador é um único espaço.
  • Console.WriteLine grava a cadeia de caracteres no console de saída padrão, seguido por uma nova linha.

Manipular entrada padrão

Isso manipula os argumentos de linha de comando corretamente. Agora, adicione o código para lidar com a entrada de leitura da entrada padrão (stdin) em vez de argumentos de linha de comando.

  1. Adicione a seguinte else cláusula à instrução if que você adicionou no código anterior:

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

    O código anterior lê a entrada do console até que uma linha em branco ou uma null seja lida. (O Console.ReadLine método retornará null se o fluxo de entrada for fechado digitando ctrl+C.)

  2. Teste a leitura de entrada padrão criando um novo arquivo de texto na mesma pasta. Nomeie o arquivo input.txt e adicione as seguintes linhas:

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

    Mantenha as linhas curtas para que elas sejam formatadas corretamente quando você adicionar o recurso para usar a arte ASCII.

  3. Execute o programa novamente.

    Com bash:

    cat input.txt | dotnet run AsciiArt.cs
    

    Ou, com o PowerShell:

    Get-Content input.txt | dotnet run AsciiArt.cs
    

Agora, seu programa pode aceitar argumentos de linha de comando ou entrada padrão.

Gravar saída de arte ASCII

Em seguida, adicione um pacote que dê suporte à arte ASCII, Colorful.Console. Para adicionar um pacote a um programa baseado em arquivo, use a #:package diretiva.

  1. Adicione a seguinte diretiva após a #! diretiva em seu arquivo de AsciiArt.cs:

    #:package Colorful.Console@1.2.15
    

    Importante

    A versão 1.2.15 era a versão mais recente do Colorful.Console pacote quando este tutorial foi atualizado pela última vez. Verifique a página NuGet do pacote para obter a versão mais recente para garantir que você use uma versão do pacote com as correções de segurança mais recentes.

  2. Altere as linhas que chamam Console.WriteLine para usar o Colorful.Console.WriteAscii método em vez disso:

    async Task WriteAsciiArt(AsciiMessageOptions options)
    {
        foreach (string message in options.Messages)
        {
            Colorful.Console.WriteAscii(message);
            await Task.Delay(options.Delay);
        }
    }
    
  3. Execute o programa e você verá a saída de arte ASCII em vez de texto ecoado.

Opções de comando de processo

Em seguida, vamos adicionar a análise de linha de comando. A versão atual grava cada palavra como uma linha de saída diferente. Os argumentos de linha de comando que você adicionou dão suporte a dois recursos:

  1. Aspas várias palavras que devem ser escritas em uma linha:

    AsciiArt.cs "This is line one" "This is another line" "This is the last line"
    
  2. Adicione uma --delay opção para pausar entre cada linha:

    AsciiArt.cs --delay 1000
    

Os usuários devem ser capazes de usar os dois argumentos juntos.

A maioria dos aplicativos de linha de comando precisa analisar argumentos de linha de comando para lidar com opções, comandos e entrada do usuário com eficiência. A System.CommandLine biblioteca fornece recursos abrangentes para lidar com comandos, subcomandos, opções e argumentos, permitindo que você se concentre no que seu aplicativo faz em vez da mecânica de analisar a entrada da linha de comando.

A System.CommandLine biblioteca oferece vários benefícios principais:

  • Geração e validação automáticas de texto de ajuda.
  • Suporte para convenções de linha de comando POSIX e Windows.
  • Funcionalidades de conclusão de guia interna.
  • Comportamento de análise consistente entre aplicativos.
  1. Adicione o System.CommandLine pacote. Adicione essa diretiva após a diretiva de pacote existente:

    #:package System.CommandLine@2.0.0
    

    Importante

    A versão 2.0.0 era a versão mais recente quando este tutorial foi atualizado pela última vez. Se houver uma versão mais recente disponível, use a versão mais recente para garantir que você tenha os pacotes de segurança mais recentes. Verifique a página NuGet do pacote para obter a versão mais recente para garantir que você use uma versão do pacote com as correções de segurança mais recentes.

  2. Adicione as instruções de uso necessárias na parte superior do arquivo (após as diretivas e #! as #:package instruções):

    using System.CommandLine;
    using System.CommandLine.Parsing;
    
  3. Defina a opção de atraso e o argumento de mensagens. Adicione o seguinte código para criar e CommandLine.OptionCommandLine.Argument objetos para representar a opção e o argumento da linha de comando:

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

    Em aplicativos de linha de comando, as opções geralmente começam com -- (traço duplo) e podem aceitar argumentos. A --delay opção aceita um argumento inteiro que especifica o atraso em milissegundos. O messagesArgument define como os tokens restantes após as opções são analisados como texto. Cada token se torna uma cadeia de caracteres separada na matriz, mas o texto pode ser citado para incluir várias palavras em um token. Por exemplo, "This is one message" torna-se um único token, enquanto This is four tokens se torna quatro tokens separados.

    O código anterior define o tipo de argumento para a opção --delay e que os argumentos são uma matriz de string valores. Esse aplicativo tem apenas um comando, portanto, você usa o comando raiz.

  4. Crie um comando raiz e configure-o com a opção e o argumento. Adicione o argumento e a opção ao comando raiz:

    RootCommand rootCommand = new("Ascii Art file-based program sample");
    
    rootCommand.Options.Add(delayOption);
    rootCommand.Arguments.Add(messagesArgument);
    
  5. Adicione o código para analisar os argumentos de linha de comando e lidar com erros. Esse código valida os argumentos de linha de comando e armazena argumentos analisados no System.CommandLine.ParseResult objeto:

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

O código anterior valida todos os argumentos de linha de comando. Se a validação falhar, os erros serão gravados no console e o aplicativo será encerrado.

Usar resultados de linha de comando analisados

Agora, conclua o aplicativo para usar as opções analisadas e gravar a saída. Primeiro, defina um registro para manter as opções analisadas. Aplicativos baseados em arquivo podem incluir declarações de tipo, como registros e classes. Eles devem estar atrás de todas as instruções de nível superior e funções locais.

  1. Adicione uma record declaração para armazenar as mensagens e o valor da opção de atraso:

    public record AsciiMessageOptions(string[] Messages, int Delay);
    
  2. Adicione a seguinte função local antes da declaração de registro. Esse método manipula argumentos de linha de comando e entrada padrão e retorna uma nova instância de registro:

    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. Crie uma função local para gravar a arte ASCII com o atraso especificado. Essa função grava cada mensagem no registro com o atraso especificado entre cada mensagem:

    async Task WriteAsciiArt(AsciiMessageOptions options)
    {
        foreach (string message in options.Messages)
        {
            Colorful.Console.WriteAscii(message);
            await Task.Delay(options.Delay);
        }
    }
    
  4. Substitua a if cláusula que você escreveu anteriormente pelo seguinte código que processa os argumentos de linha de comando e grava a saída:

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

Você criou um record tipo que fornece estrutura para as opções e argumentos de linha de comando analisados. Novas funções locais criam uma instância do registro e usam o registro para gravar a saída de arte ASCII.

Testar o aplicativo final

Teste o aplicativo executando vários comandos diferentes. Se você tiver problemas, aqui está o exemplo concluído para comparar com o que você criou:

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

#: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 program 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);

Neste tutorial, você aprendeu a criar um programa baseado em arquivo, no qual compila o programa em um único arquivo C#. Esses programas não usam um arquivo de projeto e podem usar a #! diretiva em sistemas unix. Os aprendizes podem criar esses programas depois de experimentar nossos tutoriais online e antes de criar aplicativos maiores baseados em projeto. Aplicativos baseados em arquivo também são uma ótima plataforma para utilitários de linha de comando.