Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Este tutorial ensina vários recursos no .NET e na linguagem C#. Irá aprender:
- Noções básicas da CLI do .NET
- A estrutura de um aplicativo de console C#
- E/S da consola
- Noções básicas de APIs de E/S de arquivo no .NET
- Noções básicas da programação assíncrona baseada em tarefas no .NET
Você criará um aplicativo que lê um arquivo de texto e ecoa o conteúdo desse arquivo de texto para o console. A saída para o console é cadenciada para corresponder à leitura em voz alta. Você pode acelerar ou diminuir o ritmo pressionando as teclas '<' (menor que) ou '>' (maior que). Você pode executar este aplicativo no Windows, Linux, macOS ou em um contêiner do Docker.
Há muitos recursos neste tutorial. Vamos construí-los um a um.
Pré-requisitos
- O mais recente SDK do .NET
- Editor de código do Visual Studio
- O Kit de Desenvolvimento C#
Criar a aplicação
O primeiro passo é criar um novo aplicativo. Abra um prompt de comando e crie um novo diretório para seu aplicativo. Defina isso como o diretório atual. Digite o comando dotnet new console
no prompt de comando. Isso cria os arquivos iniciais para um aplicativo básico "Hello World".
Antes de começar a fazer modificações, vamos executar o aplicativo Hello World simples. Depois de criar o aplicativo, digite dotnet run
no prompt de comando. Este comando executa o processo de restauração do pacote NuGet, cria o executável do aplicativo e executa o executável.
O código simples do aplicativo Hello World está todo em Program.cs. Abra esse arquivo com seu editor de texto favorito. Substitua o código no Program.cs pelo seguinte código:
namespace TeleprompterConsole;
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
Na parte superior do ficheiro, veja uma namespace
instrução. Como outras linguagens orientadas a objetos que você pode ter usado, C# usa namespaces para organizar tipos. Este programa Hello World não é diferente. Você pode ver que o programa está no namespace com o nome TeleprompterConsole
.
Lendo e ecoando o arquivo
O primeiro recurso a ser adicionado é a capacidade de ler um arquivo de texto e exibir todo esse texto no console. Primeiro, vamos adicionar um arquivo de texto. Copie o arquivo sampleQuotes.txt do repositório GitHub para este exemplo no diretório do projeto. Isso servirá como o script para seu aplicativo. Para obter informações sobre como baixar o aplicativo de exemplo para este tutorial, consulte as instruções em Exemplos e tutoriais.
Em seguida, adicione o seguinte método em sua Program
classe (logo abaixo do Main
método):
static IEnumerable<string> ReadFrom(string file)
{
string? line;
using (var reader = File.OpenText(file))
{
while ((line = reader.ReadLine()) != null)
{
yield return line;
}
}
}
Este método é um tipo especial de método C# chamado um método iterador. Os métodos iteradores retornam sequências que são avaliadas preguiçosamente. Isso significa que cada item na sequência é gerado conforme é solicitado pelo código que consome a sequência. Métodos iteradores são métodos que contêm uma ou mais yield return
instruções. O objeto retornado pelo ReadFrom
método contém o código para gerar cada item na sequência. Neste exemplo, isso envolve ler a próxima linha de texto do arquivo de origem e retornar essa cadeia de caracteres. Cada 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. Quando o arquivo é completamente lido, a sequência indica que não há mais itens.
Há dois elementos de sintaxe C# que podem ser novos para você. A using
instrução neste método gerencia a limpeza de recursos. A variável que é inicializada na using
instrução (reader
, neste exemplo) deve implementar a IDisposable interface. Essa interface define um único método, Dispose
, que deve ser chamado quando o recurso deve ser liberado. O compilador gera essa chamada quando a execução atinge a chave de fechamento da using
instrução. O código gerado pelo compilador garante que o recurso seja liberado mesmo que uma exceção seja lançada do código contido no bloco definido pela instrução using.
A reader
variável é definida usando a var
palavra-chave.
var
define uma variável local digitada implicitamente. Isso significa que o tipo da variável é determinado pelo tipo de tempo de compilação do objeto atribuído à variável. Aqui, esse é o valor de retorno do OpenText(String) método, que é um StreamReader objeto.
Agora, vamos preencher o código para ler o arquivo no método Main
.
var lines = ReadFrom("sampleQuotes.txt");
foreach (var line in lines)
{
Console.WriteLine(line);
}
Execute o programa (usando dotnet run
) e você pode ver todas as linhas impressas no console.
Adicionando atrasos e formatando a saída
O que tu tens está a ser exibido rápido demais para ser lido em voz alta. Agora você precisa adicionar os atrasos na saída. Ao começar, você criará parte do código principal que permite o processamento assíncrono. No entanto, estes primeiros passos seguirão algumas práticas inadequadas. Os anti-padrões são apontados nos comentários à medida que se adiciona o código, e o código será atualizado nas etapas seguintes.
Há duas etapas para esta seção. Primeiro, você atualizará o método iterador para retornar palavras únicas em vez de linhas inteiras. Isso é feito com essas modificações. Substitua a yield return line;
instrução pelo seguinte código:
var words = line.Split(' ');
foreach (var word in words)
{
yield return word + " ";
}
yield return Environment.NewLine;
Em seguida, precisas modificar a forma como consomes as linhas do ficheiro e adicionar um atraso após escreveres cada palavra. Substitua a declaração Console.WriteLine(line)
no método Main
pelo seguinte bloco:
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();
}
Execute o exemplo e verifique a saída. Agora, cada palavra é impressa, seguida por um atraso de 200 ms. No entanto, a saída exibida mostra alguns problemas porque o arquivo de texto de origem tem várias linhas que têm mais de 80 caracteres sem uma quebra de linha. Isso pode ser difícil de ler enquanto está rolando. Isso é fácil de corrigir. Você apenas controlará o comprimento de cada linha e gerará uma nova linha sempre que o comprimento da linha atingir um determinado limite. Declare uma variável local após a declaração de words
no método ReadFrom
que contém o comprimento da linha.
var lineLength = 0;
Em seguida, adicione o seguinte código após a instrução yield return word + " ";
(antes do fecho):
lineLength += word.Length + 1;
if (lineLength > 70)
{
yield return Environment.NewLine;
lineLength = 0;
}
Execute a amostra e você poderá ler em voz alta em seu ritmo pré-configurado.
Tarefas assíncronas
Nesta etapa final, você adicionará o código para gravar a saída de forma assíncrona em uma tarefa, enquanto também executa outra tarefa para ler a entrada do usuário se ele quiser acelerar ou diminuir a exibição de texto, ou interromper a exibição de texto completamente. Isso tem algumas etapas e, no final, você terá todas as atualizações de que precisa. A primeira etapa é criar um método de retorno assíncrono Task que represente o código que você criou até agora para ler e exibir o arquivo.
Adicione este método à sua Program
classe (ele é retirado do corpo do seu Main
método):
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ê notará duas alterações. Primeiro, no corpo do método, em vez de chamar Wait() para aguardar de forma síncrona a conclusão de uma tarefa, esta versão usa a await
palavra-chave. Para fazer isso, você precisa adicionar o async
modificador à assinatura do método. Este método retorna um Task
. Observe que não há instruções de retorno que retornam um Task
objeto. Em vez disso, esse objeto é criado pelo código que o compilador gera quando o operador await
é utilizado. Você pode imaginar que este método retorna quando atinge um await
. O retorno Task
indica que o trabalho não foi concluído. O método é retomado quando a tarefa aguardada é concluída. Quando tiver sido executado até a conclusão, o retorno Task
indica que ele está completo.
O código de chamada pode monitorizar o que retornou Task
para determinar quando estiver concluído.
Adicione uma await
palavra-chave antes da chamada para ShowTeleprompter
:
await ShowTeleprompter();
Isso requer que você altere a assinatura do Main
método para:
static async Task Main(string[] args)
Saiba mais sobre o async Main
método em nossa seção de fundamentos.
Em seguida, precisas escrever o segundo método assíncrono para ler do Console e observar as teclas '<' (menor que), '>' (maior que) e 'X' ou 'x'. Aqui está o método que você adiciona para essa tarefa:
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 Action delegado que lê uma tecla do Console e modifica uma variável local que representa o atraso quando o usuário pressiona as teclas '<' (menor que) ou '>' (maior que). O método de delegação termina quando o usuário pressiona as teclas 'X' ou 'x', que permitem ao usuário parar a exibição de texto a qualquer momento. Este método usa ReadKey() para bloquear e esperar que o usuário pressione uma tecla.
Para concluir esse recurso, você precisa criar um novo async Task
método de retorno que inicie ambas as tarefas (GetInput
e ShowTeleprompter
) e também gerencie os dados compartilhados entre essas duas tarefas.
É hora de criar uma classe que possa lidar com os dados compartilhados entre essas duas tarefas. Essa classe contém duas propriedades públicas: o atraso e um sinalizador Done
para indicar que o arquivo foi completamente lido:
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 inclua essa classe no TeleprompterConsole
namespace, conforme mostrado. Você também precisará adicionar uma using static
instrução na parte superior do arquivo para que consiga referenciar os métodos Min
e Max
sem os nomes de classe nem de namespace que os envolvem. Uma using static
instrução importa os métodos de uma classe. Isso contrasta com a declaração using
sem static
, que importa todas as classes de um namespace.
using static System.Math;
Em seguida, você precisa atualizar os ShowTeleprompter
métodos e GetInput
para usar o novo config
objeto. Escreva um método final `Task
` que retorne `async
` para iniciar ambas as tarefas e sair quando a primeira tarefa terminar:
private static async Task RunTeleprompter()
{
var config = new TelePrompterConfig();
var displayTask = ShowTeleprompter(config);
var speedTask = GetInput(config);
await Task.WhenAny(displayTask, speedTask);
}
O método novo aqui é a chamada WhenAny(Task[]). Isso cria um Task
que termina assim que qualquer uma das tarefas em sua lista de argumentos for concluída.
Em seguida, você precisa atualizar os métodos ShowTeleprompter
e GetInput
para usar o objeto config
para o atraso.
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);
}
Esta nova versão do ShowTeleprompter
chama um novo método na TeleprompterConfig
classe. Agora, você precisa atualizar Main
para chamar RunTeleprompter
em vez de ShowTeleprompter
:
await RunTeleprompter();
Conclusão
Este tutorial mostrou vários recursos em torno da linguagem C# e das bibliotecas do .NET Core relacionadas ao trabalho em aplicativos de console. Você pode aproveitar esse conhecimento para explorar mais sobre o idioma e as aulas apresentadas aqui. Você viu os conceitos básicos de E/S de arquivos e consoles, o uso bloqueado e não bloqueado da programação assíncrona baseada em tarefas, um tour pela linguagem C# e como os programas C# são organizados, além da ferramenta CLI do .NET.
Para obter mais informações sobre E/S de arquivo, consulte E/S de arquivo e fluxo. Para obter mais informações sobre o modelo de programação assíncrona usado neste tutorial, consulte Programação assíncrona baseada em tarefas e Programação assíncrona.