Skip to content

Formatting on console-teleprompter #1210

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 8, 2016
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 61 additions & 62 deletions docs/csharp/tutorials/console-teleprompter.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ title: Console Application
description: Console Application
keywords: .NET, .NET Core
author: BillWagner
ms.author: wiwagn
manager: wpickett
ms.date: 06/20/2016
ms.date: 11/06/2016
ms.topic: article
ms.prod: .net-core
ms.technology: .net-core-technologies
Expand All @@ -16,11 +17,11 @@ ms.assetid: 883cd93d-50ce-4144-b7c9-2df28d9c11a0

## Introduction
This tutorial teaches you a number of features in .NET Core and the C# language. You’ll learn:
* The basics of the .NET Core Command Line Interface (CLI).
* The structure of a C# Console Application.
* Console I/O.
* The basics of File I/O APIS in .NET Core
* The basics of the Task Asynchronous Programming Model in .NET Core.
* The basics of the .NET Core Command Line Interface (CLI)
* The structure of a C# Console Application
* Console I/O
* The basics of File I/O APIs in .NET Core
* The basics of the Task Asynchronous Programming Model in .NET Core

You’ll build an application that reads a text file, and echoes the
contents of that text file to the console. The output to the console will
Expand All @@ -37,25 +38,25 @@ You’ll need to install your favorite code editor.
## Create the Application
The first step is to create a new application. Open a command prompt and
create a new directory for your application. Make that the current
directory. Type the command "dotnet new" at the command prompt. This
directory. Type the command `dotnet new` at the command prompt. This
creates the starter files for a basic “Hello World” application.

Before you start making modifications, let’s go through the steps to run
the simple Hello World application. After creating the application, type
"dotnet restore" at the command prompt. This command runs the NuGet
`dotnet restore` at the command prompt. This command runs the NuGet
package restore process. NuGet is a .NET package manager. This command
downloads any of the missing dependencies for your project. As this is a
new project, none of the dependencies are in place, so the first run will
download the .NET Core framework. After this initial step, you will only
need to run dotnet restore when you add new dependent packages, or update
need to run `dotnet restore` when you add new dependent packages, or update
the versions of any of your dependencies. This process also creates the
project lock file (project.lock.json) in your project directory. This file
helps to manage the project dependencies. It contains the local location
of all the project dependencies. You do not need to put the file in source
control; it will be generated when you run dotnet restore.
control; it will be generated when you run `dotnet restore`.

After restoring packages, you run dotnet build. This executes the build
engine and creates your application executable. Finally, you execute dotnet run to
After restoring packages, you run `dotnet build`. This executes the build
engine and creates your application executable. Finally, you execute `dotnet run` to
run your application.

The simple Hello World application code is all in Program.cs. Open that
Expand All @@ -66,12 +67,12 @@ At the top of the file, see a using statement:
using System;
```

This statement tells the compiler that any types from the System namespace
This statement tells the compiler that any types from the `System` namespace
are in scope. Like other Object Oriented languages you may have used, C#
uses namespaces to organize types. This hello world program is no
different. You can see that the program is enclosed in the
`ConsoleApplication` namespace. That’s not a very descriptive name, so
change it to `TeleprompterConsole`.
change it to `TeleprompterConsole`:

```cs
namespace TeleprompterConsole
Expand All @@ -85,7 +86,7 @@ file from the GitHub repository for this [sample](https://github.com/dotnet/docs
This will serve as the script for your
application.

Next, add the following method in your Program class (right below the Main
Next, add the following method in your Program class (right below the `Main`
method):

```cs
Expand All @@ -111,14 +112,14 @@ using System.IO;
```

The `IEnumerable<T>` interface is defined in the
`System.Collections.Generic` namespace. The File class is defined in the
`System.IO namespace`.
`System.Collections.Generic` namespace. The @System.IO.File class is defined in the
`System.IO` namespace.

This method is a special type of C# method called an *Enumerator method*.
Enumerator methods return sequences that are evaluated lazily. That means
each item in the sequence is generated as it is requested by the code
consuming the sequence. Enumerator methods are methods that contain one or
more `yield return` statements. The object returned by the `ReadFrom()`
more `yield return` statements. The object returned by the `ReadFrom`
method contains the code to generate each item in the sequence. In this
example, that involves reading the next line of text from the source file,
and returning that string. Each time the calling code requests the next
Expand All @@ -128,22 +129,22 @@ indicates that there are no more items.

There are two other C# syntax elements that may be new to you. The `using`
statement in this method manages resource cleanup. The variable that is
initialized in the using statement (`reader`, in this example) must
implement the `IDisposable` interface. The `IDisposable` interface
defines a single method, `Dispose()`, that should be called when the
initialized in the `using` statement (`reader`, in this example) must
implement the `IDisposable` interface. The @System.IDisposable interface
defines a single method, `Dispose`, that should be called when the
resource should be released. The compiler generates that call when
execution reaches the closing brace of the `using` statement. 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.

The reader variable is defined using the `var` keyword. `var` defines an
The `reader` variable is defined using the `var` keyword. `var` defines an
*implicitly typed local variable*. That means the type of the variable is
determined by the compile time type of the object assigned to the
variable. Here, that is the return value from `File.OpenText()`, which is
a `StreamReader` object.
variable. Here, that is the return value from @System.IO.File.OpenText, which is
a @System.IO.StreamReader object.

Now, let’s fill in the code to read the file in the Main method:
Now, let’s fill in the code to read the file in the `Main` method:

```cs
var lines = ReadFrom("SampleQuotes.txt");
Expand All @@ -153,8 +154,8 @@ foreach (var line in lines)
}
```

Run the program (using "dotnet run" and you can see every line printed out
to the console.
Run the program (using `dotnet run` and you can see every line printed out
to the console).

## Adding Delays and Formatting output
What you have is being displayed far too fast to read aloud. Now you need
Expand All @@ -179,25 +180,23 @@ yield return Environment.NewLine;
```

Next, you need to modify how you consume the lines of the file, and add a
delay after writing each word. Replace the `Console.WriteLine()` statement
delay after writing each word. Replace the `Console.WriteLine(line)` statement
in the `Main` method with the following block:

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

The `Task` class is in the `System.Threading.Tasks` namespace, so you need
to add that using statement at the top of file:
to add that `using` statement at the top of file:

```cs
using System.Threading.Tasks;
Expand All @@ -216,7 +215,7 @@ certain threshold. Declare a local variable after the declaration of
var lineLength = 0;
```

Then, add the following code after the `yield return word;` statement
Then, add the following code after the `yield return word + " ";` statement
(before the closing brace):

```cs
Expand All @@ -236,11 +235,11 @@ 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. This has a few
steps in it and by the end, you’ll have all the updates that you need.
The first step is to create an asynchronous `Task` returning method that
The first step is to create an asynchronous @System.Threading.Tasks.Task returning method that
represents the code you’ve created so far to read and display the file.

Add this method to your Program class: (It’s taken from the body of your
Main method:
Add this method to your `Program` class (it’s taken from the body of your
`Main` method):

```cs
private static async Task ShowTeleprompter()
Expand All @@ -258,25 +257,25 @@ private static async Task ShowTeleprompter()
```

You’ll notice two changes. First, in the body of the method, instead of
calling `Wait()` to synchronously wait for a task to finish, this version
calling @System.Threading.Tasks.Task.Wait to synchronously wait for a task to finish, this version
uses the `await` keyword. In order to do that, you need to add the `async`
modifier to the method signature. This method returns a `Task`. Notice that
there are no return statements that return a Task object. Instead, that
there are no return statements that return a `Task` object. Instead, that
`Task` object is created by code the compiler generates when you use the
`await` operator. You can imagine that this method returns when it reaches
an `await`. The returned Task indicates that the work has not completed.
an `await`. The returned `Task` indicates that the work has not completed.
The method resumes when the awaited task completes. When it has executed
to completion, the returned `Task` indicates that it is complete.
Calling code can
monitor that returned task to determine when it has completed.
monitor that returned `Task` to determine when it has completed.

You can call this new method in your Main program:
You can call this new method in your `Main` method:

```cs
ShowTeleprompter().Wait();
```

Here, in `Main()`, the code does synchronously wait. You should use the
Here, in `Main`, the code does synchronously wait. You should use the
`await` operator instead of synchronously waiting whenever possible. But,
in a console application’s `Main` method, you cannot use the `await`
operator. That would result in the application exiting before all tasks
Expand Down Expand Up @@ -308,14 +307,14 @@ private static async Task GetInput()
}
```

This creates a lambda expression to represent an `Action` that reads a key
This creates a lambda expression to represent an @System.Action delegate that reads a key
from the Console and modifies a local variable representing the delay when
the user presses the ‘<’ or ‘>’ keys. This method uses `Console.ReadKey()`
the user presses the ‘<’ or ‘>’ keys. This method uses @System.Console.ReadKey
to block and wait for the user to press a key.

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 manage the shared data between these two
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.

It’s time to create a class that can handle the shared data between these
Expand Down Expand Up @@ -344,10 +343,10 @@ namespace TeleprompterConsole
```

Put that class in a new file, and enclose that class in the
`TeleprompterConsole` namespace as shown above. You’ll also need to add a `static using`
`TeleprompterConsole` namespace as shown above. You’ll also need to add a `using static`
statement so that you can reference the `Min` and `Max` method without the
enclosing class or namespace names. A static using statement imports the
methods from one class. This is in contrast with the using statements used
enclosing class or namespace names. A `using static` statement imports the
methods from one class. This is in contrast with the `using` statements used
up to this point that have imported all classes from a namespace.

```cs
Expand All @@ -357,12 +356,12 @@ using static System.Math;
The other language feature that’s new is the `lock` statement. This
statement ensures that only a single thread can be in that code at any
given time. If one thread is in the locked section, other threads must
wait for the first thread to exit that section. The lock statement uses an
wait for the first thread to exit that section. The `lock` statement uses an
object that guards the lock section. This class follows a standard idiom
to lock a private object in the class.

Next, you need to update the `ShowTeleprompter` and `GetInput` methods to
use the new config object. Write one final Task returning async method to
use the new `config` object. Write one final `Task` returning `async` method to
start both tasks and exit when the first task finishes:

```cs
Expand All @@ -376,11 +375,11 @@ private static async Task RunTeleprompter()
}
```

The one new method here is the `Task.WhenAny()` call. That creates a Task
The one new method here is the @System.Threading.Tasks.Task.WhenAny(System.Threading.Tasks.Task[]) call. That creates a `Task`
that finishes as soon as any of the tasks in its argument list completes.

Next, you need to update both the ShowTeleprompter and GetInput methods to
use the config object for the delay:
Next, you need to update both the `ShowTeleprompter` and `GetInput` methods to
use the `config` object for the delay:

```cs
private static async Task ShowTeleprompter(TelePrompterConfig config)
Expand Down Expand Up @@ -415,7 +414,7 @@ private static async Task GetInput(TelePrompterConfig config)
```

This new version of `ShowTeleprompter` calls a new method in the
`TeleprompterConfig` class. Now, you need to update Main to call
`TeleprompterConfig` class. Now, you need to update `Main` to call
`RunTeleprompter` instead of `ShowTeleprompter`:

```cs
Expand Down