Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
I den här handledningen lär du dig ett antal funktioner i .NET och C#. Du får lära dig detta:
- Grunderna i .NET CLI
- Strukturen för ett C#-konsolprogram
- Konsol-I/O
- Grunderna i fil-I/O-API:er i .NET
- Grunderna i aktivitetsbaserad asynkron programmering i .NET
Du skapar ett program som läser en textfil och ekar innehållet i textfilen till konsolen. Utdata till konsolen är i takt för att matcha läsningen högt. Du kan öka eller sakta ned takten genom att trycka på tangenterna "<" (mindre än) eller ">" (större än). Du kan köra det här programmet i Windows, Linux, macOS eller i en Docker-container.
Det finns många funktioner i den här handledningen. Låt oss bygga dem en i taget.
Förutsättningar
- Den senaste versionen av .NET SDK
- Visual Studio Code-redigerare
- C# DevKit
Skapa appen
Det första steget är att skapa ett nytt program. Öppna en kommandotolk och skapa en ny katalog för ditt program. Gör den till den aktuella katalogen. Skriv kommandot dotnet new console i kommandotolken. Till exempel:
E:\development\VSprojects>mkdir teleprompter
E:\development\VSprojects>cd teleprompter
E:\development\VSprojects\teleprompter>dotnet new console
The template "Console Application" was created successfully.
Processing post-creation actions...
Running 'dotnet restore' on E:\development\VSprojects\teleprompter\teleprompter.csproj...
Determining projects to restore...
Restored E:\development\VSprojects\teleprompter\teleprompter.csproj (in 78 ms).
Restore succeeded.
Detta skapar startfilerna för ett grundläggande "Hello World"-program.
Innan du börjar göra ändringar ska vi köra det enkla Hello World-programmet. När du har skapat programmet skriver du dotnet run i kommandotolken. Det här kommandot kör återställningsprocessen för NuGet-paketet, skapar det körbara programmet och kör den körbara filen.
Den enkla Hello World-programkoden finns i Program.cs. Öppna filen med din favorittextredigerare. Ersätt koden i Program.cs med följande kod:
namespace TeleprompterConsole;
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
Överst i filen finns en namespace-instruktion. Precis som andra objektorienterade språk som du kan ha använt använder C# namnområden för att organisera typer. Det här Hello World-programmet är inte annorlunda. Du kan se att programmet finns i namnområdet med namnet TeleprompterConsole.
Läsa och upprepa filen
Den första funktionen att lägga till är möjligheten att läsa en textfil och visa all text i konsolen. Först ska vi lägga till en textfil. Kopiera sampleQuotes.txt-filen från GitHub-lagringsplatsen för den här exempel till projektkatalogen. Detta fungerar som skript för ditt program. Information om hur du laddar ned exempelappen för den här självstudien finns i anvisningarna i Exempel och självstudier.
Lägg sedan till följande metod i klassen Program (precis under metoden Main):
static IEnumerable<string> ReadFrom(string file)
{
string? line;
using (var reader = File.OpenText(file))
{
while ((line = reader.ReadLine()) != null)
{
yield return line;
}
}
}
Den här metoden är en särskild typ av C#-metod som kallas iteratormetod. Iterator-metoder returnerar sekvenserna som utvärderas fördröjt. Det innebär att varje objekt i sekvensen genereras eftersom det begärs av koden som använder sekvensen. Iteratormetoder är metoder som innehåller en eller flera yield return-instruktioner. Objektet som returneras av metoden ReadFrom innehåller koden för att generera varje objekt i sekvensen. I det här exemplet innebär det att du läser nästa textrad från källfilen och returnerar strängen. Varje gång den anropande koden begär nästa objekt från sekvensen läser koden nästa textrad från filen och returnerar den. När filen är helt läst indikerar sekvensen att det inte finns några fler objekt.
Det finns två C#-syntaxelement som kan vara nya för dig. Instruktionen using i den här metoden hanterar resursrensning. Variabeln som initieras i using -instruktionen (reader, i det här exemplet) måste implementera IDisposable-gränssnittet. Det gränssnittet definierar en enda metod, Dispose, som ska anropas när resursen ska släppas. Kompilatorn genererar det anropet när körningen når den avslutande klammerparentesen för using-instruktionen. Den kompilatorgenererade koden säkerställer att resursen släpps även om ett undantag genereras från koden i blocket som definieras av instruktionen using.
Variabeln reader definieras med nyckelordet var.
var definierar en implicit inskriven lokal variabel. Det innebär att variabeltypen bestäms av kompileringstidstypen för det objekt som tilldelats variabeln. Här är det returvärdet från metoden OpenText(String), som är ett StreamReader-objekt.
Nu ska vi fylla i koden för att läsa filen i metoden Main:
var lines = ReadFrom("sampleQuotes.txt");
foreach (var line in lines)
{
Console.WriteLine(line);
}
Kör programmet (med dotnet run) och du kan se varje rad som skrivs ut till konsolen.
Lägga till fördröjningar och formatera utdata
Det du har visas alldeles för snabbt för att läsa upp. Nu måste du lägga till fördröjningarna i utdata. När du börjar skapar du en del av den kärnkod som möjliggör asynkron bearbetning. Dessa första steg följer dock några antimönster. Antimönstren framhävs i kommentarer när du lägger till koden och koden uppdateras i senare steg.
Det finns två steg i det här avsnittet. Först uppdaterar du iteratormetoden för att returnera enkla ord i stället för hela rader. Det är gjort med de här ändringarna. Ersätt yield return line;-instruktionen med följande kod:
var words = line.Split(' ');
foreach (var word in words)
{
yield return word + " ";
}
yield return Environment.NewLine;
Därefter måste du ändra hur du använder raderna i filen och lägga till en fördröjning när du har skrivit varje ord. Ersätt Console.WriteLine(line)-instruktionen i metoden Main med följande 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();
}
Kör exemplet och kontrollera utdata. Nu skrivs varje enskilt ord ut, följt av en fördröjning på 200 ms. De utdata som visas visar dock vissa problem eftersom källtextfilen har flera rader med fler än 80 tecken utan radbrytning. Det kan vara svårt att läsa medan det rullar förbi. Det är lätt att fixa. Du håller bara reda på längden på varje rad och genererar en ny rad när radlängden når ett visst tröskelvärde. Deklarera en lokal variabel efter deklarationen av words i metoden ReadFrom som innehåller radlängden:
var lineLength = 0;
Lägg sedan till följande kod efter yield return word + " "; -instruktionen (före den avslutande klammerparentesen):
lineLength += word.Length + 1;
if (lineLength > 70)
{
yield return Environment.NewLine;
lineLength = 0;
}
Kör exemplet så kan du läsa upp det i förkonfigurerad takt.
Asynkrona uppgifter
I det här sista steget lägger du till koden för att skriva utdata asynkront i en uppgift, samtidigt som du kör en annan uppgift för att läsa indata från användaren om de vill påskynda eller sakta ned textvisningen eller stoppa textvisningen helt och hållet. Detta har några steg i den och i slutet har du alla uppdateringar som du behöver. Det första steget är att skapa en asynkron Task returnerande metod som representerar den kod som du har skapat hittills för att läsa och visa filen.
Lägg till den här metoden i klassen Program (den tas från brödtexten i din Main-metod):
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);
}
}
}
Du kommer att märka två ändringar. I metodens brödtext använder den här versionen först nyckelordet Wait() i stället för att anropa await för att synkront vänta på att en uppgift ska slutföras. För att göra det måste du lägga till async-modifieraren i metodsignaturen. Den här metoden returnerar en Task. Observera att det inte finns några returnsatser som returnerar något Task-objekt. I stället skapas det Task objektet av kod som kompilatorn genererar när du använder operatorn await. Du kan tänka dig att den här metoden returneras när den når en await. Den returnerade Task anger att arbetet inte har slutförts. Metoden återupptas när den väntande uppgiften är klar. När den har körts till slutförande anger den returnerade Task att den är klar.
Anropande kod kan övervaka det returnerade Task så att du kan avgöra när det har avslutats.
Lägg till ett await nyckelord före anropet till ShowTeleprompter:
await ShowTeleprompter();
Detta kräver att du ändrar Main-metodsignaturen till:
static async Task Main(string[] args)
Läs mer om async Main-metoden i avsnittet grunderna.
Därefter måste du skriva den andra asynkrona metoden för att läsa från konsolen och titta efter nycklarna "<" (mindre än), ">" (större än) och "X" eller "x". Här är den metod som du lägger till för den uppgiften:
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);
}
Detta skapar ett lambda-uttryck för att representera ett Action ombud som läser en nyckel från konsolen och ändrar en lokal variabel som representerar fördröjningen när användaren trycker på nycklarna "<" (mindre än) eller ">" (större än). Ombudsmetoden avslutas när användaren trycker på X- eller x-tangenterna, vilket gör att användaren kan stoppa textvisningen när som helst. Den här metoden använder ReadKey() för att blockera och vänta tills användaren trycker på en nyckel.
Det är dags att skapa en klass som kan hantera delade data mellan dessa två uppgifter. Den här klassen innehåller två offentliga egenskaper: fördröjningen och en flagga Done för att indikera att filen har lästs helt:
using static System.Math;
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;
}
}
Skapa en ny fil. det kan vara valfritt namn som slutar med .cs. Till exempel TelePrompterConfig.cs. Klistra in klasskoden TelePrompterConfig och spara och stäng. Placera klassen i TeleprompterConsole namnområdet som det visas. Observera att instruktionen using static låter dig referera till metoderna Min och Max utan de omslutande klass- eller namnområdesnamnen. En using static-instruktion importerar metoderna från en klass. Detta står i kontrast till using-instruktionen utan static, som importerar alla klasser från ett namnområde.
Därefter måste du uppdatera metoderna ShowTeleprompter och GetInput för att använda det nya config-objektet. För att slutföra den här funktionen måste du skapa en ny async Task returnerande metod som startar båda dessa uppgifter (GetInput och ShowTeleprompter), och även hanterar delade data mellan dessa två uppgifter. Skapa en RunTelePrompter-aktivitet för att starta båda aktiviteterna och avsluta när den första aktiviteten är klar:
private static async Task RunTeleprompter()
{
var config = new TelePrompterConfig();
var displayTask = ShowTeleprompter(config);
var speedTask = GetInput(config);
await Task.WhenAny(displayTask, speedTask);
}
Här är den nya metoden anropet WhenAny(Task[]). Det skapar en Task som slutförs så snart någon av uppgifterna i argumentlistan slutförs.
Därefter måste du uppdatera både ShowTeleprompter metoderna och GetInput för att använda config objektet för fördröjningen. Konfigurationsobjektet skickas som en parameter till dessa metoder. Använd kopiera/klistra in för att helt ersätta metoderna med den nya koden här. Du kan se att koden använder attribut och anropande metoder från konfigurationsobjektet:
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);
}
Nu måste du uppdatera Main för att anropa RunTeleprompter i stället för ShowTeleprompter:
await RunTeleprompter();
Slutsats
I den här självstudien visas ett antal funktioner kring C#-språket och .NET Core-biblioteken som är relaterade till arbete med konsolapplikationer. Du kan bygga vidare på den här kunskapen för att utforska mer om språket och de klasser som introduceras här. Du har sett grunderna i Fil- och konsol-I/O, blockering och icke-blockerande användning av aktivitetsbaserad asynkron programmering, en genomgång av C#-språket och hur C#-program organiseras samt .NET CLI.
Mer information om fil-I/O finns i File and Stream I/O. Mer information om asynkron programmeringsmodell som används i den här handledningen finns i Aktivitetsbaserad Asynkron Programmering och Asynkron Programmering.