Aplicativo do ConsoleConsole Application

Este tutorial ensina vários recursos no .NET Core e da linguagem C#.This tutorial teaches you a number of features in .NET Core and the C# language. Você aprenderá:You’ll learn:

  • As noções básicas da CLI (Interface de Linha de Comando) do .NET CoreThe basics of the .NET Core Command Line Interface (CLI)
  • A estrutura de um aplicativo de console C#The structure of a C# Console Application
  • E/S do ConsoleConsole I/O
  • Fundamentos das APIs de E/S de arquivo no .NETThe basics of File I/O APIs in .NET
  • Os fundamentos da programação assíncrona controlada por tarefas no .NET CoreThe basics of the Task-based Asynchronous Programming in .NET

Você compilará um aplicativo que lê um arquivo de texto e exibe o conteúdo desse arquivo de texto no console.You’ll build an application that reads a text file, and echoes the contents of that text file to the console. A saída para o console é conduzida a fim de corresponder à leitura em voz alta.The output to the console is paced to match reading it aloud. É possível acelerar ou diminuir o ritmo pressionando as teclas "<" (menor que) ou ">" (maior que).You can speed up or slow down the pace by pressing the ‘<’ (less than) or ‘>’ (greater than) keys.

Há vários recursos neste tutorial.There are a lot of features in this tutorial. Vamos compilá-los individualmente.Let’s build them one by one.

Pré-requisitosPrerequisites

Você precisará configurar seu computador para executar o .NET Core.You’ll need to setup your machine to run .NET Core. Você pode encontrar as instruções de instalação na página de downloads do .NET Core .You can find the installation instructions on the .NET Core Downloads page. Execute esse aplicativo no Windows, Linux, macOS ou em um contêiner do Docker.You can run this application on Windows, Linux, macOS or in a Docker container. Será necessário instalar o editor de código de sua preferência.You’ll need to install your favorite code editor.

Criar o aplicativoCreate the Application

A primeira etapa é criar um novo aplicativo.The first step is to create a new application. Abra um prompt de comando e crie um novo diretório para seu aplicativo.Open a command prompt and create a new directory for your application. Torne ele o diretório atual.Make that the current directory. Digite o comando dotnet new console no prompt de comando.Type the command dotnet new console at the command prompt. Isso cria os arquivos iniciais de um aplicativo "Olá, Mundo" básico.This creates the starter files for a basic "Hello World" application.

Antes de começar as modificações, vamos percorrer as etapas para execução do aplicativo simples Hello World.Before you start making modifications, let’s go through the steps to run the simple Hello World application. Depois de criar o aplicativo, digite dotnet restore no prompt de comando.After creating the application, type dotnet restore at the command prompt. Esse comando executa o processo de restauração do pacote NuGet.This command runs the NuGet package restore process. O NuGet é um gerenciador de pacotes do .NET.NuGet is a .NET package manager. Esse comando baixa qualquer uma das dependências ausentes para seu projeto.This command downloads any of the missing dependencies for your project. Como esse é um novo projeto, nenhuma das dependências foram aplicadas, portanto, a primeira execução baixará a estrutura do .NET Core.As this is a new project, none of the dependencies are in place, so the first run will download the .NET Core framework. Após essa etapa inicial, você só precisará executar o dotnet restore ao adicionar novos pacotes dependentes, ou atualizar as versões de qualquer uma de suas dependências.After this initial step, you will only need to run dotnet restore when you add new dependent packages, or update the versions of any of your dependencies.

Observação

Começando com o SDK do .NET Core 2.0, não é necessário executar dotnet restore, pois ele é executado implicitamente por todos os comandos que exigem uma restauração, como dotnet new, dotnet build e dotnet run.Starting with .NET Core 2.0 SDK, you don't have to run dotnet restore because it's run implicitly by all commands that require a restore to occur, such as dotnet new, dotnet build and dotnet run. Ainda é um comando válido em determinados cenários em que realizar uma restauração explícita faz sentido, como builds de integração contínua no Azure DevOps Services ou em sistemas de build que precisam controlar explicitamente o horário em que a restauração ocorrerá.It's still a valid command in certain scenarios where doing an explicit restore makes sense, such as continuous integration builds in Azure DevOps Services or in build systems that need to explicitly control the time at which the restore occurs.

Depois de restaurar os pacotes, execute dotnet build.After restoring packages, you run dotnet build. Isso executa o mecanismo de compilação e cria o executável de seu aplicativo.This executes the build engine and creates your application executable. Por fim, execute dotnet run para executar o aplicativo.Finally, you execute dotnet run to run your application.

O código simples do aplicativo Hello World está totalmente em Program.cs.The simple Hello World application code is all in Program.cs. Abra esse arquivo com o seu editor de texto favorito.Open that file with your favorite text editor. Estamos prestes a fazer nossas primeiras alterações.We’re about to make our first changes. Na parte superior do arquivo, confira uma instrução using:At the top of the file, see a using statement:

using System;

Essa instrução informa ao compilador que quaisquer tipos do namespace System estão dentro do escopo.This statement tells the compiler that any types from the System namespace are in scope. Assim como em outras linguagens orientadas a objeto que você pode ter usado, o C# usa namespaces para organizar tipos.Like other Object Oriented languages you may have used, C# uses namespaces to organize types. Este programa Olá, Mundo não é diferente.This Hello World program is no different. Você pode ver que o programa é encerrado no namespace com o nome baseado no nome do diretório atual.You can see that the program is enclosed in the namespace with the name based on the name of the current directory. Para este tutorial, vamos alterar o nome do namespace para TeleprompterConsole:For this tutorial, let's change the name of the namespace to TeleprompterConsole:

namespace TeleprompterConsole

Como ler e exibir o arquivoReading and Echoing the File

O primeiro recurso a ser adicionado é a capacidade de ler um arquivo de texto e a exibição de todo esse texto para um console.The first feature to add is the ability to read a text file and display all that text to the console. Primeiro, vamos adicionar um arquivo de texto.First, let’s add a text file. Copie o arquivo sampleQuotes.txt do repositório do GitHub para este exemplo no diretório de seu projeto.Copy the sampleQuotes.txt file from the GitHub repository for this sample into your project directory. Isso servirá como o script de seu aplicativo.This will serve as the script for your application. Se desejar obter informações sobre como baixar o aplicativo de exemplo para este tópico, consulte as instruções no tópico Exemplos e Tutoriais.If you would like information on how to download the sample app for this topic, see the instructions in the Samples and Tutorials topic.

Em seguida, adicione o seguinte método em sua classe Program (logo abaixo do método Main):Next, add the following method in your Program class (right below the Main method):

static IEnumerable<string> ReadFrom(string file)
{
    string line;
    using (var reader = File.OpenText(file))
    {
        while ((line = reader.ReadLine()) != null)
        {
            yield return line;
        }
    }
}

Esse método usa tipos de dois namespaces novos.This method uses types from two new namespaces. Para que isso seja compilado, será necessário adicionar as duas linhas a seguir na parte superior do arquivo:For this to compile you’ll need to add the following two lines to the top of the file:

using System.Collections.Generic;
using System.IO;

A interface IEnumerable<T> é definida no namespace System.Collections.Generic.The IEnumerable<T> interface is defined in the System.Collections.Generic namespace. A classe File é definida no namespace System.IO.The File class is defined in the System.IO namespace.

Esse método é um tipo especial de método C# chamado de Método iterador.This method is a special type of C# method called an Iterator method. Os métodos enumeradores retornam sequências que são avaliadas lentamente.Enumerator methods return sequences that are evaluated lazily. Isso significa que cada item na sequência é gerado conforme a solicitação do código que está consumindo a sequência.That means each item in the sequence is generated as it is requested by the code consuming the sequence. Os métodos enumeradores contêm uma ou mais instruções yield return.Enumerator methods are methods that contain one or more yield return statements. O objeto retornado pelo método ReadFrom contém o código para gerar cada item na sequência.The object returned by the ReadFrom method contains the code to generate each item in the sequence. Neste exemplo, isso envolve a leitura da próxima linha de texto do arquivo de origem e o retorno dessa cadeia de caracteres.In this example, that involves reading the next line of text from the source file, and returning that string. Toda vez que o código de chamada solicita o próximo item da sequência, o código lê a próxima linha de texto do arquivo e a retorna.Each time the calling code requests the next item from the sequence, the code reads the next line of text from the file and returns it. Após a leitura completa do arquivo, a sequência indicará que não há mais itens.When the file is completely read, the sequence indicates that there are no more items.

Há dois outros elementos da sintaxe em C# que podem ser novidade para você.There are two other C# syntax elements that may be new to you. A instrução using nesse método gerencia a limpeza de recursos.The using statement in this method manages resource cleanup. A variável inicializada na instrução using (reader, neste exemplo) deve implementar a interface IDisposable.The variable that is initialized in the using statement (reader, in this example) must implement the IDisposable interface. Essa interface define um único método, Dispose, que deve ser chamado quando o recurso for liberado.That interface defines a single method, Dispose, that should be called when the resource should be released. O compilador gera essa chamada quando a execução atingir a chave de fechamento da instrução using.The compiler generates that call when execution reaches the closing brace of the using statement. O código gerado pelo compilador garante que o recurso seja liberado, mesmo se uma exceção for lançada do código no bloco definido pela instrução using.The compiler-generated code ensures that the resource is released even if an exception is thrown from the code in the block defined by the using statement.

A variável reader é definida usando a palavra-chave var.The reader variable is defined using the var keyword. var define uma variável local de tipo implícito.var defines an implicitly typed local variable. Isso significa que o tipo da variável é determinado pelo tipo de tempo de compilação do objeto atribuído à variável.That means the type of the variable is determined by the compile-time type of the object assigned to the variable. Aqui, esse é o valor retornado do método OpenText(String), que é um objeto StreamReader.Here, that is the return value from the OpenText(String) method, which is a StreamReader object.

Agora, vamos preencher o código para ler o arquivo no método Main:Now, let’s fill in the code to read the file in the Main method:

var lines = ReadFrom("sampleQuotes.txt");
foreach (var line in lines)
{
    Console.WriteLine(line);
}

Execute o programa (usando dotnet run, e você poderá ver todas as linhas impressa no console).Run the program (using dotnet run) and you can see every line printed out to the console.

Adicionar atrasos e formatar a saídaAdding Delays and Formatting output

O que você possui está sendo exibido muito rápido para permitir a leitura em voz alta.What you have is being displayed far too fast to read aloud. Agora você precisa adicionar os atrasos na saída.Now you need to add the delays in the output. Ao começar, você compilará parte do código principal que permite o processamento assíncrono.As you start, you’ll be building some of the core code that enables asynchronous processing. No entanto, essas primeiras etapas seguirão alguns antipadrões.However, these first steps will follow a few anti-patterns. Os antipadrões são indicados nos comentários durante a adição do código, e o código será atualizado em etapas posteriores.The anti-patterns are pointed out in comments as you add the code, and the code will be updated in later steps.

Há duas etapas nesta seção.There are two steps to this section. Primeiro, você atualizará o método iterador a fim de retornar palavras individuais em vez de linhas inteiras.First, you’ll update the iterator method to return single words instead of entire lines. Isso é feito com estas modificações.That’s done with these modifications. Substitua a instrução yield return line; pelo seguinte código:Replace the yield return line; statement with the following code:

var words = line.Split(' ');
foreach (var word in words)
{
    yield return word + " ";
}
yield return Environment.NewLine;

Em seguida, será necessário modificar a forma como você consume as linhas do arquivo, e adicionar um atraso depois de escrever cada palavra.Next, you need to modify how you consume the lines of the file, and add a delay after writing each word. Substitua a instrução Console.WriteLine(line) no método Main pelo seguinte bloco:Replace the Console.WriteLine(line) statement in the Main method with the following block:

Console.Write(line);
if (!string.IsNullOrWhiteSpace(line))
{
    var pause = Task.Delay(200);
    // Synchronously waiting on a task is an
    // anti-pattern. This will get fixed in later
    // steps.
    pause.Wait();
}

A classe Task é o namespace System.Threading.Tasks, portanto, você precisa adicionar essa instrução using na parte superior do arquivo:The Task class is in the System.Threading.Tasks namespace, so you need to add that using statement at the top of file:

using System.Threading.Tasks;

Execute o exemplo e verifique a saída.Run the sample, and check the output. Agora, cada palavra única é impressa, seguida por um atraso de 200 ms.Now, each single word is printed, followed by a 200 ms delay. No entanto, a saída exibida mostra alguns problemas, pois o arquivo de texto de origem contém várias linhas com mais de 80 caracteres sem uma quebra de linha.However, the displayed output shows some issues because the source text file has several lines that have more than 80 characters without a line break. Isso pode ser difícil de ler durante a rolagem da tela.That can be hard to read while it's scrolling by. Mas também é fácil de corrigir.That’s easy to fix. Apenas mantenha o controle do comprimento de cada linha e gere uma nova linha sempre que o comprimento atingir um certo limite.You’ll just keep track of the length of each line, and generate a new line whenever the line length reaches a certain threshold. Declare uma variável local após a declaração de words no método ReadFrom que contém o comprimento da linha:Declare a local variable after the declaration of words in the ReadFrom method that holds the line length:

var lineLength = 0;

Em seguida, adicione o seguinte código após a instrução yield return word + " "; (antes da chave de fechamento):Then, add the following code after the yield return word + " "; statement (before the closing brace):

lineLength += word.Length + 1;
if (lineLength > 70)
{
    yield return Environment.NewLine;
    lineLength = 0;
}

Execute o exemplo e você poderá ler em voz alta de acordo com o ritmo pré-configurado.Run the sample, and you’ll be able to read aloud at its pre-configured pace.

Tarefas assíncronasAsync Tasks

Nesta etapa final, você adicionará o código para gravar a saída de forma assíncrona em uma tarefa, enquanto executa também outra tarefa para ler a entrada do usuário, casos ele queira acelerar ou diminuir o ritmo da exibição do texto ou interromper a exibição do texto por completo.In this final step, you’ll add the code to write the output asynchronously in one task, while also running another task to read input from the user if they want to speed up or slow down the text display, or stop the text display altogether. Essa etapa tem alguns passos e, no final, você terá todas as atualizações necessárias.This has a few steps in it and by the end, you’ll have all the updates that you need. A primeira etapa é criar um método de retorno Task assíncrono que representa o código que você criou até agora para ler e exibir o arquivo.The first step is to create an asynchronous Task returning method that represents the code you’ve created so far to read and display the file.

Adicione este método à sua classe Program (ele é obtido do corpo de seu método Main):Add this method to your Program class (it’s taken from the body of your Main method):

private static async Task ShowTeleprompter()
{
    var words = ReadFrom("sampleQuotes.txt");
    foreach (var word in words)
    {
        Console.Write(word);
        if (!string.IsNullOrWhiteSpace(word))
        {
            await Task.Delay(200);
        }
    }
}

Você observará duas alterações.You’ll notice two changes. Primeiro, no corpo do método, em vez de chamar Wait() para aguardar de forma síncrona a conclusão de uma tarefa, essa versão usa a palavra-chave await.First, in the body of the method, instead of calling Wait() to synchronously wait for a task to finish, this version uses the await keyword. Para fazer isso, você precisa adicionar o modificador async à assinatura do método.In order to do that, you need to add the async modifier to the method signature. Esse método retorna Task.This method returns a Task. Observe que não há instruções return que retornam um objeto Task.Notice that there are no return statements that return a Task object. Em vez disso, esse objeto Task é criado pelo código gerado pelo compilador quando você usa o operador await.Instead, that Task object is created by code the compiler generates when you use the await operator. Você pode imaginar que esse método retorna quando atinge um await.You can imagine that this method returns when it reaches an await. A Task retornada indica que o trabalho não foi concluído.The returned Task indicates that the work has not completed. O método será retomado quando a tarefa em espera for concluída.The method resumes when the awaited task completes. Após a execução completa, a Task retornada indicará a conclusão.When it has executed to completion, the returned Task indicates that it is complete. O código de chamada pode monitorar essa Task retornada para determinar quando ela foi concluída.Calling code can monitor that returned Task to determine when it has completed.

Chame esse novo método em seu método Main:You can call this new method in your Main method:

ShowTeleprompter().Wait();

Aqui, em Main, o código aguarda de forma síncrona.Here, in Main, the code does synchronously wait. Use o operador await em vez de esperar de forma síncrona sempre que possível.You should use the await operator instead of synchronously waiting whenever possible. Porém, no método Main do aplicativo de console, não é possível usar o operador await.But, in a console application’s Main method, you cannot use the await operator. Isso resultaria no encerramento do aplicativo antes da conclusão de todas as tarefas.That would result in the application exiting before all tasks have completed.

Observação

Caso use o C# 7.1 ou posterior, você poderá criar aplicativos de console com o método async Main.If you use C# 7.1 or later, you can create console applications with async Main method.

Em seguida, é necessário escrever o segundo método assíncrono a ser lido no Console e ficar atento às teclas "<" (menor que), ">" (maior que) e "X" ou "x".Next, you need to write the second asynchronous method to read from the Console and watch for the ‘<’ (less than), ‘>’ (greater than) and ‘X’ or ‘x’ keys. Este é o método que você adiciona à tarefa:Here’s the method you add for that task:

private static async Task GetInput()
{
    var delay = 200;
    Action work = () =>
    {
        do {
            var key = Console.ReadKey(true);
            if (key.KeyChar == '>')
            {
                delay -= 10;
            }
            else if (key.KeyChar == '<')
            {
                delay += 10;
            }
            else if (key.KeyChar == 'X' || key.KeyChar == 'x')
            {
                break;
            }
        } while (true);
    };
    await Task.Run(work);
}

Isso cria uma expressão lambda para representar um delegado Action que lê uma chave no Console e modifica uma variável local que representa o atraso quando o usuário pressiona as teclas "<" (menor que) ou ">" (maior que).This creates a lambda expression to represent an Action delegate that reads a key from the Console and modifies a local variable representing the delay when the user presses the ‘<’ (less than) or ‘>’ (greater than) keys. O método de delegado termina quando o usuário pressiona a tecla "X" ou "x", que permitem ao usuário interromper a exibição de texto a qualquer momento.The delegate method finishes when user presses the ‘X’ or ‘x’ keys, which allow the user to stop the text display at any time. Esse método usa ReadKey() para bloquear e aguardar até que o usuário pressione uma tecla.This method uses ReadKey() to block and wait for the user to press a key.

Para concluir esse recurso, você precisa criar um novo método de retorno async Task que inicia essas duas tarefas (GetInput e ShowTeleprompter) e também gerencia os dados compartilhados entre essas tarefas.To finish this feature, you need to create a new async Task returning method that starts both of these tasks (GetInput and ShowTeleprompter), and also manages the shared data between these two tasks.

É hora de criar uma classe que pode manipular os dados compartilhados entre essas duas tarefas.It’s time to create a class that can handle the shared data between these two tasks. Essa classe contém duas propriedades públicas: o atraso e um sinalizador Done para indicar que o arquivo foi lido completamente:This class contains two public properties: the delay, and a flag Done to indicate that the file has been completely read:

namespace TeleprompterConsole
{
    internal class TelePrompterConfig
    {
        public int DelayInMilliseconds { get; private set; } = 200;

        public void UpdateDelay(int increment) // negative to speed up
        {
            var newDelay = Min(DelayInMilliseconds + increment, 1000);
            newDelay = Max(newDelay, 20);
            DelayInMilliseconds = newDelay;
        }

        public bool Done { get; private set; }

        public void SetDone()
        {
            Done = true;
        }
    }
}

Coloque essa classe em um novo arquivo e cerque-a pelo namespace TeleprompterConsole, conforme mostrado acima.Put that class in a new file, and enclose that class in the TeleprompterConsole namespace as shown above. Também é necessário adicionar uma instrução using static para que você possa fazer referência ao método Min e Max sem os nomes de classe ou namespace delimitadores.You’ll also need to add a using static statement so that you can reference the Min and Max methods without the enclosing class or namespace names. Uma instrução using static importa os métodos de uma classe.A using static statement imports the methods from one class. Isso é o oposto das instruções using usadas até este ponto, as quais importaram todas as classes de um namespace.This is in contrast with the using statements used up to this point that have imported all classes from a namespace.

using static System.Math;

Em seguida, atualize os métodos ShowTeleprompter e GetInput para usar o novo objeto config.Next, you need to update the ShowTeleprompter and GetInput methods to use the new config object. Escreva um método final async de retorno de Task para iniciar as duas tarefas e sair quando a primeira tarefa for concluída:Write one final Task returning async method to start both tasks and exit when the first task finishes:

private static async Task RunTeleprompter()
{
    var config = new TelePrompterConfig();
    var displayTask = ShowTeleprompter(config);

    var speedTask = GetInput(config);
    await Task.WhenAny(displayTask, speedTask);
}

O novo método aqui é a chamada WhenAny(Task[]).The one new method here is the WhenAny(Task[]) call. Isso cria uma Task que termina assim que qualquer uma das tarefas na lista de argumentos for concluída.That creates a Task that finishes as soon as any of the tasks in its argument list completes.

Depois, atualize os métodos ShowTeleprompter e GetInput para usar o objeto config para o atraso:Next, you need to update both the ShowTeleprompter and GetInput methods to use the config object for the delay:

private static async Task ShowTeleprompter(TelePrompterConfig config)
{
    var words = ReadFrom("sampleQuotes.txt");
    foreach (var word in words)
    {
        Console.Write(word);
        if (!string.IsNullOrWhiteSpace(word))
        {
            await Task.Delay(config.DelayInMilliseconds);
        }
    }
    config.SetDone();
}

private static async Task GetInput(TelePrompterConfig config)
{
    Action work = () =>
    {
        do {
            var key = Console.ReadKey(true);
            if (key.KeyChar == '>')
                config.UpdateDelay(-10);
            else if (key.KeyChar == '<')
                config.UpdateDelay(10);
            else if (key.KeyChar == 'X' || key.KeyChar == 'x')
                config.SetDone();
        } while (!config.Done);
    };
    await Task.Run(work);
}

Essa nova versão de ShowTeleprompter chama um novo método na classe TeleprompterConfig.This new version of ShowTeleprompter calls a new method in the TeleprompterConfig class. Agora, você precisa atualizar Main para chamar RunTeleprompter em vez de ShowTeleprompter:Now, you need to update Main to call RunTeleprompter instead of ShowTeleprompter:

RunTeleprompter().Wait();

ConclusãoConclusion

Este tutorial mostrou a você alguns recursos da linguagem C# e as bibliotecas .NET Core relacionadas ao trabalho em aplicativos de Console.This tutorial showed you a number of the features around the C# language and the .NET Core libraries related to working in Console applications. Use esse conhecimento como base para explorar mais sobre a linguagem e sobre as classes apresentadas aqui.You can build on this knowledge to explore more about the language, and the classes introduced here. Você já viu os fundamentos de E/S do Arquivo e do Console, uso com bloqueio e sem bloqueio da programação assíncrona controlada por tarefa, um tour pela linguagem C# e como os programas em C# são organizados, além da Interface de linha de comando e ferramentas do .NET Core.You’ve seen the basics of File and Console I/O, blocking and non-blocking use of the Task-based asynchronous programming, a tour of the C# language and how C# programs are organized and the .NET Core Command Line Interface and tools.

Para obter mais informações sobre E/S de arquivo, consulte o tópico E/S de arquivo e de fluxo.For more information about File I/O, see the File and Stream I/O topic. Para obter mais informações sobre o modelo de programação assíncrona usado neste tutorial, consulte os tópicos Programação assíncrona controlada por tarefas e Programação assíncrona.For more information about asynchronous programming model used in this tutorial, see the Task-based Asynchronous Programming topic and the Asynchronous programming topic.