Partilhar via


Tutorial: Criar programas C# baseados em arquivo

Aplicações baseadas em ficheiros são programas contidos num único *.cs ficheiro que se constrói e executa sem um ficheiro de projeto correspondente.*.csproj Os aplicativos baseados em arquivos são ideais para aprender C# porque têm menos complexidade: todo o programa é armazenado em um único arquivo. Os aplicativos baseados em arquivo também são úteis para criar utilitários de linha de comando. Em plataformas Unix, podes executar aplicações baseadas em ficheiros usando #!diretivas (shebang). Neste tutorial, você:

  • Crie um programa baseado em arquivo.
  • Adicione suporte a Unix shebang (#!).
  • Leia os argumentos da linha de comando.
  • Manipule a entrada padrão.
  • Escreva a saída de arte ASCII.
  • Processar argumentos de linha de comando.
  • Use resultados de linha de comando analisados.
  • Teste a aplicação final.

Você cria um programa baseado em arquivo que escreve texto como arte ASCII. A aplicação está contida num único ficheiro, utiliza pacotes NuGet e implementa funcionalidades essenciais.

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 executas este programa, o dotnet host constrói o executável a partir do ficheiro de origem, armazena artefactos de construção numa pasta temporária e depois executa 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 está atual e executa o executável sem o compilar novamente. Você não vê nenhuma saída de compilação.

As etapas anteriores demonstram que os aplicativos baseados em arquivo não são arquivos de script. São ficheiros fonte C# que o dotnet anfitrião constrói usando um ficheiro de projeto gerado numa pasta temporária. Uma das linhas de saída apresentadas quando constróis o programa deve parecer-se mais ou menos assim (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 compilação são colocados. Ao longo deste tutorial, sempre que você editar o arquivo de origem, o dotnet host atualiza o executável antes que ele seja executado.

As aplicações baseadas em ficheiros são programas C# normais. A única limitação é que tens de os escrever num único ficheiro fonte. Você pode usar instruções de nível superior ou um método clássico Main como 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é mesmo declarar vários namespaces para organizar seu código. Se descobrires que um programa baseado em ficheiros está a crescer demasiado para um único ficheiro, podes convertê-lo num programa baseado em projetos e dividir a fonte em vários ficheiros. Os aplicativos baseados em arquivos são uma ótima ferramenta de prototipagem. Você pode começar a experimentar com sobrecarga mínima para provar conceitos e criar algoritmos.

Suporte a Unix shebang (#!)

Observação

O suporte a #! diretivas aplica-se apenas em plataformas Unix. Não existe 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, podes executar aplicações baseadas em ficheiros diretamente. Em vez de usar dotnet run, escreve o nome do ficheiro de origem na linha de comandos. 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
    

A localização de dotnet pode variar em diferentes instalações Unix. Use o comando which dotnet para localizar o dotnet hospedeiro no seu ambiente.

Alternativamente, pode usar #!/usr/bin/env dotnet para resolver automaticamente o caminho dotnet a partir da variável de ambiente PATH:

#!/usr/bin/env dotnet

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

./AsciiArt.cs

Se preferir, você pode remover a extensão para poder digitar ./AsciiArt . Pode adicionar o ao ficheiro de origem mesmo que utilize o #! Windows. A linha de comando do Windows não suporta #!, mas o compilador C# permite essa diretiva em aplicativos baseados em arquivos em todas as plataformas.

Ler argumentos de linha de comando

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

  1. Substitua o conteúdo atual do AsciiArt.cs pelo 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 seguintes argumentos de comando devem ser passados para o programa AsciiArt. Os argumentos This is the command line. são passados como uma matriz de cadeias de caracteres, onde cada cadeia de caracteres é uma palavra: This, is, the, command, e line..

Esta versão demonstra estes novos conceitos:

  • A variável args pré-definida passa os argumentos da linha de comandos para o programa. A args variável é uma matriz de strings: string[]. Se o comprimento de args for 0, não foram fornecidos argumentos. 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. Neste caso, o separador é um espaço único.
  • Console.WriteLine Grava a cadeia de caracteres no console de saída padrão, seguido por uma nova linha.

Manipule a entrada padrão

O código anterior trata corretamente os argumentos da linha de comandos. Agora, adicione o código para manipular a entrada de leitura da entrada padrão (stdin) em vez de argumentos de linha de comando.

  1. Adicione a seguinte else cláusula à if instrução adicionada 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 um null seja lido. (O Console.ReadLine método retorna 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.

    Ao usar bash:

    cat input.txt | dotnet run AsciiArt.cs
    

    Ou, usando o PowerShell:

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

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

Escrever saída de arte ASCII

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

  1. Adicione a seguinte diretiva após a diretiva #! no ficheiro 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:

    Colorful.Console.WriteAscii(line);
    
  3. Execute o programa. Vês saída de arte em ASCII em vez de texto com eco.

Opções de comando de processo

De seguida, adiciona a análise sintática na linha de comandos. A versão atual escreve cada palavra como uma linha de saída diferente. Os argumentos de linha de comandos que adicionas suportam duas funcionalidades:

  1. Cite 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 utilizadores podem usar ambos os argumentos em conjunto.

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 de forma eficaz. A System.CommandLine biblioteca oferece capacidades abrangentes para lidar com comandos, subcomandos, opções e argumentos. Podes concentrar-te no que a tua aplicação faz em vez da mecânica de analisar a entrada da linha de comandos.

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

  • Geração e validação automática de texto de ajuda.
  • Suporte para POSIX e convenções de linha de comando do Windows.
  • Capacidades integradas de preenchimento de separadores.
  • Comportamento de análise consistente entre aplicativos.
  1. Adicione o System.CommandLine pacote. Aditar esta diretiva após a diretiva 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 utilização necessárias na parte superior do seu ficheiro (após as #! diretivas e #:package ):

    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 os CommandLine.Option objetos e para representar a opção de CommandLine.Argument linha de comando e o argumento:

    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 normalmente 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 quaisquer 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 --delay opção e que os argumentos são uma matriz de string valores. Este aplicativo tem apenas um comando, então você usa o comando root.

  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 da linha de comando e manipular quaisquer erros. Esse código valida os argumentos da linha de comando e armazena System.CommandLine.ParseResult os argumentos analisados no 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, a aplicação escreve erros na consola e sai.

Usar resultados de linha de comando analisados

Agora, termine o aplicativo para usar as opções analisadas e escreva a saída. Primeiro, defina um registro para armazenar as opções analisadas. Os aplicativos baseados em arquivo podem incluir declarações de tipo, como registros e classes. Devem ser, afinal, declarações de alto nível 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 escrever a arte ASCII com o atraso especificado. Esta 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 escreveu anteriormente pelo seguinte código que processa os argumentos da linha de comandos e escreve o resultado:

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

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

Testar a aplicação 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, onde você cria o programa em um único arquivo C#. Estes programas não usam um ficheiro de projeto e podem usar a #! diretiva em sistemas Unix. Os alunos podem criar esses programas depois de experimentar nossos tutoriais on-line e antes de criar aplicativos maiores baseados em projetos. Os aplicativos baseados em arquivos também são uma ótima plataforma para utilitários de linha de comando.