Aplicación de consolaConsole Application

Este tutorial le enseña varias características de .NET Core y el lenguaje C#.This tutorial teaches you a number of features in .NET Core and the C# language. Aprenderá lo siguiente:You’ll learn:

  • Los aspectos básicos de la interfaz de línea de comandos (CLI) de .NET CoreThe basics of the .NET Core Command Line Interface (CLI)
  • La estructura de una aplicación de consola en C#The structure of a C# Console Application
  • E/S de consolaConsole I/O
  • Aspectos básicos de las API de E/S de archivo en .NETThe basics of File I/O APIs in .NET
  • Aspectos básicos de la programación asincrónica basada en tareas en .NETThe basics of the Task-based Asynchronous Programming in .NET

Creará una aplicación que lea un archivo de texto y refleje el contenido de ese archivo de texto en la consola.You’ll build an application that reads a text file, and echoes the contents of that text file to the console. El ritmo de la salida a la consola se ajusta para que coincida con la lectura en voz alta.The output to the console is paced to match reading it aloud. Para aumentar o reducir el ritmo, presione las teclas "<" (menor que) o ">" (mayor que).You can speed up or slow down the pace by pressing the ‘<’ (less than) or ‘>’ (greater than) keys.

Hay muchas características en este tutorial.There are a lot of features in this tutorial. Vamos a compilarlas una a una.Let’s build them one by one.

Requisitos previosPrerequisites

Deberá configurar la máquina para ejecutar .NET Core.You’ll need to setup your machine to run .NET Core. Puede encontrar las instrucciones de instalación en la página Descargas de .NET Core.You can find the installation instructions on the .NET Core Downloads page. Puede ejecutar esta aplicación en Windows, Linux, Mac OS o en un contenedor de Docker.You can run this application on Windows, Linux, macOS or in a Docker container. Deberá instalar su editor de código favorito.You’ll need to install your favorite code editor.

Crear la aplicaciónCreate the Application

El primer paso es crear una nueva aplicación.The first step is to create a new application. Abra un símbolo del sistema y cree un nuevo directorio para la aplicación.Open a command prompt and create a new directory for your application. Conviértalo en el directorio actual.Make that the current directory. Escriba el comando dotnet new console en el símbolo del sistema.Type the command dotnet new console at the command prompt. Esta acción crea los archivos de inicio para una aplicación básica "Hola mundo".This creates the starter files for a basic "Hello World" application.

Antes de comenzar a realizar modificaciones, vamos a recorrer los pasos para ejecutar la aplicación Hola a todos sencilla.Before you start making modifications, let’s go through the steps to run the simple Hello World application. Después de crear la aplicación, escriba dotnet restore en el símbolo del sistema.After creating the application, type dotnet restore at the command prompt. Este comando ejecuta el proceso de restauración de paquetes de NuGet.This command runs the NuGet package restore process. NuGet es un administrador de paquetes .NET.NuGet is a .NET package manager. Este comando permite descargar cualquiera de las dependencias que faltan para el proyecto.This command downloads any of the missing dependencies for your project. Como se trata de un nuevo proyecto, ninguna de las dependencias está en su lugar, así que con la primera ejecución se descargará .NET Core Framework.As this is a new project, none of the dependencies are in place, so the first run will download the .NET Core framework. Después de este paso inicial, solo deberá ejecutar dotnet restore al agregar nuevos paquetes dependientes, o actualizar las versiones de cualquiera de sus dependencias.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.

Nota

A partir del SDK de .NET Core 2.0, no es necesario ejecutar dotnet restore porque lo ejecutan implícitamente todos los comandos que requieren que se produzca una restauración, como dotnet new, dotnet build y 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. Sigue siendo un comando válido en algunos escenarios donde tiene sentido realizar una restauración explícita, como las compilaciones de integración continua en Azure DevOps Services o en los sistemas de compilación que necesitan controlar explícitamente la hora a la que se produce la restauración.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.

Después de restaurar los paquetes, ejecutará dotnet build.After restoring packages, you run dotnet build. Esta acción ejecuta el motor de compilación y crea el ejecutable de aplicación.This executes the build engine and creates your application executable. Por último, ejecute dotnet run para ejecutar la aplicación.Finally, you execute dotnet run to run your application.

El código de la aplicación sencilla Hola a todos está todo en Program.cs.The simple Hello World application code is all in Program.cs. Abra ese archivo con el editor de texto de su elección.Open that file with your favorite text editor. Nos disponemos a realizar nuestros primeros cambios.We’re about to make our first changes. En la parte superior del archivo, verá una instrucción using:At the top of the file, see a using statement:

using System;

Esta instrucción indica al compilador que cualquier tipo del espacio de nombres System está en el ámbito.This statement tells the compiler that any types from the System namespace are in scope. Al igual que otros lenguajes orientados a objetos que pueda haber usado, C# utiliza espacios de nombres para organizar los tipos.Like other Object Oriented languages you may have used, C# uses namespaces to organize types. Este programa Hola mundo no es diferente.This Hello World program is no different. Puede ver que el programa se incluye en el espacio de nombres y el nombre se basa en el del directorio actual.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 a cambiar el nombre del espacio de nombres por TeleprompterConsole:For this tutorial, let's change the name of the namespace to TeleprompterConsole:

namespace TeleprompterConsole

Lectura y reflejo del archivoReading and Echoing the File

La primera característica que se va a agregar es la capacidad de leer un archivo de texto y visualizar todo ese texto en la consola.The first feature to add is the ability to read a text file and display all that text to the console. En primer lugar, vamos a agregar un archivo de texto.First, let’s add a text file. Copie el archivo sampleQuotes.txt del repositorio de GitHub de este ejemplo en su directorio del proyecto.Copy the sampleQuotes.txt file from the GitHub repository for this sample into your project directory. Servirá como script para la aplicación.This will serve as the script for your application. Si quiere obtener información sobre cómo descargar la aplicación de ejemplo de este tema, vea las instrucciones del tema Ejemplos y tutoriales.If you would like information on how to download the sample app for this topic, see the instructions in the Samples and Tutorials topic.

Luego, agregue el siguiente método a la clase Program (justo debajo del 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;
        }
    }
}

Este método usa tipos de dos nuevos espacios de nombres.This method uses types from two new namespaces. Para compilar esto, deberá agregar las dos líneas siguientes a la parte superior del archivo: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;

La interfaz IEnumerable<T> se define en el espacio de nombres System.Collections.Generic.The IEnumerable<T> interface is defined in the System.Collections.Generic namespace. La clase File se define en el espacio de nombres System.IO.The File class is defined in the System.IO namespace.

Este método es un tipo especial de método de C# llamado método de iterador.This method is a special type of C# method called an Iterator method. Los métodos de enumerador devuelven secuencias que se evalúan de forma diferida.Enumerator methods return sequences that are evaluated lazily. Eso significa que cada elemento de la secuencia se genera como lo solicita el código que consume la secuencia.That means each item in the sequence is generated as it is requested by the code consuming the sequence. Los métodos de enumerador son métodos que contienen una o varias instrucciones yield return.Enumerator methods are methods that contain one or more yield return statements. El objeto que devuelve el método ReadFrom contiene el código para generar cada elemento en la secuencia.The object returned by the ReadFrom method contains the code to generate each item in the sequence. En este ejemplo, que implica la lectura de la siguiente línea de texto del archivo de origen y la devolución de esa cadena,In this example, that involves reading the next line of text from the source file, and returning that string. cada vez que el código de llamada solicita el siguiente elemento de la secuencia, el código lee la siguiente línea de texto del archivo y la devuelve.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. Cuando el archivo se ha leído completamente, la secuencia indica que no hay más elementos.When the file is completely read, the sequence indicates that there are no more items.

Hay otros dos elementos de la sintaxis de C# con los que podría no estar familiarizado.There are two other C# syntax elements that may be new to you. La instrucción using de este método administra la limpieza de recursos.The using statement in this method manages resource cleanup. La variable que se inicializa en la instrucción using (reader, en este ejemplo) debe implementar la interfaz IDisposable.The variable that is initialized in the using statement (reader, in this example) must implement the IDisposable interface. Esa interfaz define un único método, Dispose, que se debe llamar cuando sea necesario liberar el recurso.That interface defines a single method, Dispose, that should be called when the resource should be released. El compilador genera esa llamada cuando la ejecución llega a la llave de cierre de la instrucción using.The compiler generates that call when execution reaches the closing brace of the using statement. El código generado por el compilador garantiza que el recurso se libera incluso si se produce una excepción desde el código en el bloqueo definido mediante la instrucción 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.

La variable reader se define mediante la palabra clave var.The reader variable is defined using the var keyword. var define una variable local con tipo implícito.var defines an implicitly typed local variable. Esto significa que el tipo de la variable viene determinado por el tipo en tiempo de compilación del objeto asignado a la variable.That means the type of the variable is determined by the compile-time type of the object assigned to the variable. Aquí, ese es el valor devuelto por el método OpenText(String), que es un objeto StreamReader.Here, that is the return value from the OpenText(String) method, which is a StreamReader object.

Ahora, vamos a rellenar el código para leer el archivo en el 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);
}

Ejecute el programa (mediante dotnet run) y podrá ver cada línea impresa en la consola.Run the program (using dotnet run) and you can see every line printed out to the console.

Adición de retrasos y formato de salidaAdding Delays and Formatting output

Lo que tiene se va a mostrar lejos, demasiado rápido para leerlo en voz alta.What you have is being displayed far too fast to read aloud. Ahora debe agregar los retrasos en la salida.Now you need to add the delays in the output. Cuando empiece, estará creando parte del código principal que permite el procesamiento asincrónico.As you start, you’ll be building some of the core code that enables asynchronous processing. Sin embargo, a estos primeros pasos le seguirán algunos antipatrones.However, these first steps will follow a few anti-patterns. Los antipatrones se señalan en los comentarios cuando se agrega el código y el código se actualizará en los pasos posteriores.The anti-patterns are pointed out in comments as you add the code, and the code will be updated in later steps.

Hay dos pasos hasta esta sección.There are two steps to this section. Primero, actualizará el método Iterator para devolver palabras sueltas en lugar de líneas enteras.First, you’ll update the iterator method to return single words instead of entire lines. Para ello, son necesarias estas modificaciones.That’s done with these modifications. Reemplace la instrucción yield return line; por el código siguiente: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;

A continuación, debe modificar el modo en que se consumen las líneas del archivo y agregar un retraso después de escribir cada palabra.Next, you need to modify how you consume the lines of the file, and add a delay after writing each word. Reemplace la instrucción Console.WriteLine(line) del método Main por el bloqueo siguiente: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();
}

La clase Task está en el espacio de nombres System.Threading.Tasks, así que debe agregar esa instrucción using en la parte superior del archivo: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;

Ejecute el ejemplo y compruebe la salida.Run the sample, and check the output. Ahora, se imprime cada palabra suelta, seguido de un retraso de 200 ms.Now, each single word is printed, followed by a 200 ms delay. Sin embargo, la salida mostrada indica algunos problemas porque el archivo de texto de origen tiene varias líneas con más de 80 caracteres sin un salto de línea.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. Este texto puede ser difícil de leer al desplazarse por él.That can be hard to read while it's scrolling by. Esto es fácil de corregir.That’s easy to fix. Simplemente realizará el seguimiento de la longitud de cada línea y generará una nueva línea cada vez que la longitud de la línea alcance un determinado umbral.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 una variable local después de la declaración de words en el método ReadFrom que contiene la longitud de línea:Declare a local variable after the declaration of words in the ReadFrom method that holds the line length:

var lineLength = 0;

A continuación, agregue el código siguiente después de la instrucción yield return word + " "; (antes de la llave de cierre):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;
}

Ejecute el ejemplo y podrá leer en alto a su ritmo preconfigurado.Run the sample, and you’ll be able to read aloud at its pre-configured pace.

Tareas asincrónicasAsync Tasks

En este paso final, agregará el código para escribir la salida de manera asincrónica en una tarea, mientras se ejecuta también otra tarea para leer la entrada del usuario si quiere aumentar o reducir la velocidad de la pantalla de texto, o detendrá la presentación del 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. Incluye unos cuantos pasos y, al final, tendrá todas las actualizaciones que necesita.This has a few steps in it and by the end, you’ll have all the updates that you need. El primer paso es crear un método de devolución Task asincrónico que represente el código que ha creado hasta el momento para leer y visualizar el archivo.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.

Agregue este método a su clase Program (se toma del cuerpo del 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);
        }
    }
}

Advertirá dos cambios.You’ll notice two changes. Primero, en el cuerpo del método, en lugar de llamar a Wait() para esperar a que finalice una tarea de manera sincrónica, esta versión usa la palabra clave 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 ello, debe agregar el modificador async a la signatura del método.In order to do that, you need to add the async modifier to the method signature. Este método devuelve un objeto Task.This method returns a Task. Observe que no hay ninguna instrucción Return que devuelva un objeto Task.Notice that there are no return statements that return a Task object. En su lugar, ese objeto Task se crea mediante el código que genera el compilador cuando usa el operador await.Instead, that Task object is created by code the compiler generates when you use the await operator. Puede imaginar que este método devuelve cuando alcanza un valor de await.You can imagine that this method returns when it reaches an await. El valor devuelto de Task indica que el trabajo no ha finalizado.The returned Task indicates that the work has not completed. El método se reanuda cuando se completa la tarea en espera.The method resumes when the awaited task completes. Cuando se ha ejecutado hasta su finalización, el valor de Task devuelto indica que se ha completado.When it has executed to completion, the returned Task indicates that it is complete. El código de llamada puede supervisar ese valor de Task devuelto para determinar cuándo se ha completado.Calling code can monitor that returned Task to determine when it has completed.

Puede llamar a este nuevo método en su método Main:You can call this new method in your Main method:

ShowTeleprompter().Wait();

Aquí, en Main, el código espera de manera sincrónica.Here, in Main, the code does synchronously wait. Siempre que sea posible, debe usar el operador await en lugar de esperar sincrónicamente.You should use the await operator instead of synchronously waiting whenever possible. Sin embargo, en el método Main de una aplicación de consola, no puede usar el operador await.But, in a console application’s Main method, you cannot use the await operator. Si así fuera, la aplicación se cerraría antes de que todas las tareas se hubieran completado.That would result in the application exiting before all tasks have completed.

Nota

Si usa C# 7.1 o una versión posterior, puede crear aplicaciones de consola con el método async Main.If you use C# 7.1 or later, you can create console applications with async Main method.

A continuación, debe escribir el segundo método asincrónico para leer de la consola y controlar las teclas "<" (menor que), ">" (mayor que), "X" o "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 es el método que agrega para esa tarea: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);
}

Se crea una expresión lambda que representa un delegado de Action que lee una clave de la consola y modifica una variable local que representa el retraso cuando el usuario presiona las teclas "<" (menor que) o ">" (mayor 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. El método de delegado finaliza cuando el usuario presiona las teclas "X" o "x", que permiten al usuario detener la presentación del texto en cualquier 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. Este método usa ReadKey() para bloquear y esperar a que el usuario presione una tecla.This method uses ReadKey() to block and wait for the user to press a key.

Para finalizar esta característica, debe crear un nuevo método de devolución async Task que inicie estas dos tareas (GetInput y ShowTeleprompter) y también administre los datos compartidos entre ellas.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.

Es hora de crear una clase que controle los datos compartidos entre estas dos tareas.It’s time to create a class that can handle the shared data between these two tasks. Esta clase contiene dos propiedades públicas: el retraso y una marca Done para indicar que el archivo se ha leído 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 esa clase en un archivo nuevo e inclúyala en el espacio de nombres TeleprompterConsole, como se ha mostrado anteriormente.Put that class in a new file, and enclose that class in the TeleprompterConsole namespace as shown above. También deberá agregar una instrucción using static para que pueda hacer referencia a los métodos Min y Max sin la clase incluida o los nombres de espacio de nombres.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. Una instrucción using static importa los métodos de una clase,A using static statement imports the methods from one class. mientras que las instrucciones using usadas hasta el momento han importado todas las clases de un espacio de nombres.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;

A continuación, debe actualizar los métodos ShowTeleprompter y GetInput para usar el nuevo objeto config.Next, you need to update the ShowTeleprompter and GetInput methods to use the new config object. Escriba un método final async de devolución de Task para iniciar ambas tareas y salir cuando la primera tarea finalice: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);
}

El método nuevo aquí es la llamada a WhenAny(Task[]).The one new method here is the WhenAny(Task[]) call. Dicha llamada crea un valor de Task que finaliza en cuanto alguna de las tareas de su lista de argumentos se completa.That creates a Task that finishes as soon as any of the tasks in its argument list completes.

A continuación, debe actualizar los métodos ShowTeleprompter y GetInput para usar el objeto config para el retraso: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);
}

Esta nueva versión de ShowTeleprompter llama a un nuevo método de la clase TeleprompterConfig.This new version of ShowTeleprompter calls a new method in the TeleprompterConfig class. Ahora, debe actualizar Main para llamar a RunTeleprompter en lugar de a ShowTeleprompter:Now, you need to update Main to call RunTeleprompter instead of ShowTeleprompter:

RunTeleprompter().Wait();

ConclusiónConclusion

En este tutorial se han mostrado varias características en torno al lenguaje C# y las bibliotecas .NET Core, relacionadas con el trabajo en aplicaciones de consola.This tutorial showed you a number of the features around the C# language and the .NET Core libraries related to working in Console applications. Puede partir de este conocimiento para explorar más sobre el lenguaje y las clases aquí presentadas.You can build on this knowledge to explore more about the language, and the classes introduced here. Ha visto los conceptos básicos de E/S de archivo y consola, el uso con bloqueo y sin bloqueo de la programación asincrónica basada en tareas, un paseo por el lenguaje C# y cómo se organizan los programas en C#. También ha conocido la interfaz de la línea de comandos y las herramientas de .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 obtener más información sobre la E/S de archivo, consulte el tema E/S de archivos y secuencias.For more information about File I/O, see the File and Stream I/O topic. Para obtener más información sobre el modelo de programación asincrónica que se ha usado en este tutorial, vea los temas Programación asincrónica basada en tareas y Programación asincrónica.For more information about asynchronous programming model used in this tutorial, see the Task-based Asynchronous Programming topic and the Asynchronous programming topic.