Console-app

In deze zelfstudie leert u een aantal functies in .NET en de C#-taal. U leert het volgende:

  • De basisbeginselen van de .NET CLI
  • De structuur van een C#-consoletoepassing
  • Console-I/O
  • De basisbeginselen van bestands-I/O-API's in .NET
  • De basisbeginselen van Asynchroon programmeren op basis van taken in .NET

U bouwt een toepassing die een tekstbestand leest en de inhoud van dat tekstbestand echot naar de console. De uitvoer naar de console wordt aangepast aan het hardop voorlezen. U kunt het tempo versnellen of vertragen door op de toetsen '<' (kleiner dan) of '>' (groter dan) te drukken. U kunt deze toepassing uitvoeren op Windows, Linux, macOS of in een Docker-container.

Deze zelfstudie bevat een groot aantal functies. We gaan ze een voor een maken.

Vereisten

De app maken

De eerste stap bestaat uit het maken van een nieuwe toepassing. Open een opdrachtprompt en maak een nieuwe map voor uw toepassing. Maak hiervan de huidige map. Typ de opdracht dotnet new console bij de opdrachtprompt. Hiermee maakt u de startersbestanden voor een eenvoudige Hallo wereld-toepassing.

Voordat u begint met het aanbrengen van wijzigingen, gaan we de eenvoudige Hallo wereld-toepassing uitvoeren. Nadat u de toepassing hebt gemaakt, typt u dotnet run bij de opdrachtprompt. Met deze opdracht wordt het herstelproces van het NuGet-pakket uitgevoerd, wordt het uitvoerbare bestand van de toepassing gemaakt en wordt het uitvoerbare bestand uitgevoerd.

De eenvoudige Hallo wereld toepassingscode vindt u allemaal in Program.cs. Open dat bestand met uw favoriete teksteditor. Vervang de code in Program.cs door de volgende code:

namespace TeleprompterConsole;

internal class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Hello World!");
    }
}

Bovenaan het bestand ziet u een namespace -instructie. Net als andere objectgeoriënteerde talen die u mogelijk hebt gebruikt, gebruikt C# naamruimten om typen te ordenen. Dit Hallo wereld programma is niet anders. U kunt zien dat het programma zich in de naamruimte bevindt met de naam TeleprompterConsole.

Het bestand lezen en echoën

De eerste functie die moet worden toegevoegd, is de mogelijkheid om een tekstbestand te lezen en al die tekst weer te geven op de console. Eerst voegen we een tekstbestand toe. Kopieer het sampleQuotes.txt-bestand uit de GitHub-opslagplaats voor dit voorbeeld naar uw projectmap. Dit fungeert als het script voor uw toepassing. Zie de instructies in Voorbeelden en zelfstudies voor meer informatie over het downloaden van de voorbeeld-app voor deze zelfstudie.

Voeg vervolgens de volgende methode toe in uw Program klasse (rechts onder de Main methode):

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

Deze methode is een speciaal type C#-methode, een iterator-methode. Iterator-methoden retourneren reeksen die lazily worden geëvalueerd. Dit betekent dat elk item in de reeks wordt gegenereerd zoals het wordt aangevraagd door de code die de reeks verbruikt. Iterator-methoden zijn methoden die een of meer yield return instructies bevatten. Het object dat door de ReadFrom methode wordt geretourneerd, bevat de code voor het genereren van elk item in de reeks. In dit voorbeeld gaat het om het lezen van de volgende regel tekst uit het bronbestand en het retourneren van die tekenreeks. Telkens wanneer de aanroepende code het volgende item uit de reeks aanvraagt, leest de code de volgende regel tekst uit het bestand en retourneert deze. Wanneer het bestand volledig is gelezen, geeft de volgorde aan dat er geen items meer zijn.

Er zijn twee C#-syntaxiselementen die mogelijk nieuw voor u zijn. De using instructie in deze methode beheert het opschonen van resources. De variabele die is geïnitialiseerd in de using -instructie (readerin dit voorbeeld) moet de IDisposable interface implementeren. Deze interface definieert één methode, Dispose, die moet worden aangeroepen wanneer de resource moet worden vrijgegeven. De compiler genereert die aanroep wanneer de uitvoering de afsluitende accolade van de using -instructie bereikt. De door de compiler gegenereerde code zorgt ervoor dat de resource wordt vrijgegeven, zelfs als er een uitzondering wordt gegenereerd op basis van de code in het blok dat is gedefinieerd door de using-instructie.

De reader variabele wordt gedefinieerd met behulp van het var trefwoord. var definieert een impliciet getypte lokale variabele. Dit betekent dat het type van de variabele wordt bepaald door het type compileertijd van het object dat is toegewezen aan de variabele. Hier is dat de retourwaarde van de OpenText(String) methode, die een StreamReader -object is.

Laten we nu de code invullen om het bestand in de Main -methode te lezen:

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

Voer het programma uit (met ) dotnet runen u ziet elke regel die naar de console wordt afgedrukt.

Vertragingen en opmaakuitvoer toevoegen

Wat u hebt, wordt veel te snel weergegeven om hardop voor te lezen. Nu moet u de vertragingen in de uitvoer toevoegen. Terwijl u begint, bouwt u enkele van de kerncode die asynchrone verwerking mogelijk maakt. Deze eerste stappen volgen echter enkele antipatronen. De antipatronen worden in opmerkingen vermeld wanneer u de code toevoegt en de code wordt in latere stappen bijgewerkt.

Deze sectie bestaat uit twee stappen. Eerst werkt u de iterator-methode bij om één woord te retourneren in plaats van hele regels. Dat is gedaan met deze wijzigingen. Vervang de yield return line; instructie door de volgende code:

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

Vervolgens moet u wijzigen hoe u de regels van het bestand gebruikt en een vertraging toevoegen na het schrijven van elk woord. Vervang de Console.WriteLine(line) instructie in de Main methode door het volgende blok:

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();
}

Voer het voorbeeld uit en controleer de uitvoer. Nu wordt elk woord afgedrukt, gevolgd door een vertraging van 200 ms. De weergegeven uitvoer toont echter enkele problemen omdat het brontekstbestand meerdere regels bevat met meer dan 80 tekens zonder regeleinde. Dat kan moeilijk te lezen zijn terwijl het voorbij schuift. Dat is makkelijk op te lossen. U houdt alleen de lengte van elke regel bij en genereert een nieuwe lijn wanneer de lijnlengte een bepaalde drempelwaarde bereikt. Declareer een lokale variabele na de declaratie van words in de ReadFrom methode die de regellengte bevat:

var lineLength = 0;

Voeg vervolgens de volgende code toe na de yield return word + " "; instructie (vóór de accolade sluiten):

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

Voer het voorbeeld uit en u kunt hardop voorlezen in het vooraf geconfigureerde tempo.

Asynchrone taken

In deze laatste stap voegt u de code toe om de uitvoer asynchroon in één taak te schrijven, terwijl u ook een andere taak uitvoert om de invoer van de gebruiker te lezen als deze de tekstweergave wil versnellen of vertragen, of de tekstweergave helemaal wilt stoppen. Dit bevat een paar stappen en aan het einde hebt u alle updates die u nodig hebt. De eerste stap is het maken van een asynchrone Task retourmethode die de code vertegenwoordigt die u tot nu toe hebt gemaakt om het bestand te lezen en weer te geven.

Voeg deze methode toe aan uw Program klasse (deze is afkomstig uit de hoofdtekst van uw Main methode):

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);
        }
    }
}

U ziet twee wijzigingen. Ten eerste gebruikt await deze versie in de hoofdtekst van de methode het trefwoord in plaats van aan te roepen Wait() om synchroon te wachten tot een taak is voltooid. Hiervoor moet u de async wijzigingsfunctie toevoegen aan de methodehandtekening. Deze methode retourneert een Task. U ziet dat er geen retourinstructies zijn die een Task object retourneren. In plaats daarvan wordt dat object gemaakt door code die Task de compiler genereert wanneer u de await operator gebruikt. U kunt zich voorstellen dat deze methode retourneert wanneer deze een awaitbereikt. De geretourneerde Task geeft aan dat het werk nog niet is voltooid. De methode wordt hervat wanneer de verwachte taak is voltooid. Wanneer het is uitgevoerd tot voltooiing, geeft de geretourneerde Task aan dat het voltooid is. Aanroepende code kan controleren welke geretourneerde code wordt geretourneerd Task om te bepalen wanneer deze is voltooid.

Voeg een await trefwoord toe vóór de aanroep naar ShowTeleprompter:

await ShowTeleprompter();

Hiervoor moet u de Main methodehandtekening wijzigen in:

static async Task Main(string[] args)

Meer informatie over de methode vindt u in de async Main sectie Grondbeginselen.

Vervolgens moet u de tweede asynchrone methode schrijven om te lezen vanuit de console en watch voor de toetsen '<' (kleiner dan), '>' (groter dan) en 'X' of 'x'. Dit is de methode die u voor die taak toevoegt:

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);
}

Hiermee maakt u een lambda-expressie die een Action gemachtigde vertegenwoordigt die een sleutel uit de console leest en een lokale variabele wijzigt die de vertraging aangeeft wanneer de gebruiker op de toetsen '<' (kleiner dan) of '>' (groter dan) drukt. De methode voor gemachtigden wordt voltooid wanneer de gebruiker op de toetsen 'X' of 'x' drukt, zodat de gebruiker de tekstweergave op elk gewenst moment kan stoppen. Deze methode gebruikt ReadKey() om te blokkeren en te wachten tot de gebruiker op een toets drukt.

Als u deze functie wilt voltooien, moet u een nieuwe async Task retourmethode maken waarmee beide taken (GetInput en ShowTeleprompter) worden gestart en waarmee ook de gedeelde gegevens tussen deze twee taken worden beheerd.

Het is tijd om een klasse te maken die de gedeelde gegevens tussen deze twee taken kan verwerken. Deze klasse bevat twee openbare eigenschappen: de vertraging en een vlag Done om aan te geven dat het bestand volledig is gelezen:

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;
    }
}

Plaats die klasse in een nieuw bestand en neem die klasse op in de TeleprompterConsole naamruimte zoals weergegeven. U moet ook een using static -instructie toevoegen bovenaan het bestand, zodat u kunt verwijzen naar de Min methoden en Max zonder de bijbehorende klasse- of naamruimtenamen. Met een using static -instructie worden de methoden uit één klasse geïmporteerd. Dit in tegenstelling tot de using instructie zonder static, waarmee alle klassen uit een naamruimte worden geïmporteerd.

using static System.Math;

Vervolgens moet u de ShowTeleprompter methoden en GetInput bijwerken om het nieuwe config object te gebruiken. Schrijf een laatste Task retourmethode async om beide taken te starten en af te sluiten wanneer de eerste taak is voltooid:

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

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

De enige nieuwe methode hier is de WhenAny(Task[]) aanroep. Hiermee wordt een Task gemaakt die is voltooid zodra een van de taken in de argumentenlijst is voltooid.

Vervolgens moet u de ShowTeleprompter methoden en GetInput bijwerken om het config object te gebruiken voor de vertraging:

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);
}

Met deze nieuwe versie van ShowTeleprompter wordt een nieuwe methode in de TeleprompterConfig klasse aangeroepen. Nu moet u bijwerken Main om aan te roepen RunTeleprompter in plaats van ShowTeleprompter:

await RunTeleprompter();

Conclusie

In deze zelfstudie hebt u een aantal functies van de C#-taal en de .NET Core-bibliotheken met betrekking tot het werken in consoletoepassingen laten zien. U kunt op deze kennis voortbouwen om meer te weten te komen over de taal en de klassen die hier worden geïntroduceerd. U hebt de basisbeginselen van bestands- en console-I/O gezien, het blokkeren en niet-blokkeren van het gebruik van op taken gebaseerde asynchrone programmering, een rondleiding door de C#-taal en hoe C#-programma's zijn georganiseerd, en de .NET CLI.

Zie Bestands- en stream-I/O voor meer informatie over bestands-I/O. Zie Taakgebaseerde asynchrone programmering en Asynchrone programmering voor meer informatie over het asynchrone programmeermodel dat in deze zelfstudie wordt gebruikt.