Konzolová aplikace

V tomto kurzu se naučíte řadu funkcí v .NET a jazyce C#. Naučíte se:

  • Základy rozhraní příkazového řádku .NET
  • Struktura konzolové aplikace v jazyce C#
  • Vstupně-výstupní operace konzoly
  • Základy vstupně-výstupních rozhraní API souborů v .NET
  • Základy asynchronního programování založeného na úlohách v .NET

Vytvoříte aplikaci, která čte textový soubor a obsah tohoto textového souboru bude opakovat v konzole nástroje . Výstup do konzoly se chytá tak, aby odpovídal čtení nahlas. Tempo můžete zrychlit nebo zpomalit stisknutím kláves "<" (menší než) nebo ">větší než". Tuto aplikaci můžete spustit ve Windows, Linuxu, macOS nebo v kontejneru Dockeru.

V tomto kurzu je spousta funkcí. Pojďme je sestavit jeden po druhém.

Požadavky

Vytvoření aplikace

Prvním krokem je vytvoření nové aplikace. Otevřete příkazový řádek a vytvořte nový adresář pro vaši aplikaci. Nastavte ho jako aktuální adresář. Zadejte příkaz dotnet new console na příkazovém řádku. Tím se vytvoří počáteční soubory pro základní "Hello World" aplikaci.

Než začnete s úpravami, spustíme jednoduchou aplikaci Hello World. Po vytvoření aplikace zadejte dotnet run na příkazovém řádku příkaz . Tento příkaz spustí proces obnovení balíčku NuGet, vytvoří spustitelný soubor aplikace a spustí spustitelný soubor.

Jednoduchý Hello World kód aplikace je v souboru Program.cs. Otevřete tento soubor v oblíbeném textovém editoru. Nahraďte kód v souboru Program.cs následujícím kódem:

namespace TeleprompterConsole;

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

V horní části souboru se podívejte na namespace příkaz. Stejně jako ostatní objektově orientované jazyky, které jste možná použili, používá jazyk C# k uspořádání typů obory názvů. Tento Hello World program se nijak neliší. Uvidíte, že program je v oboru názvů s názvem TeleprompterConsole.

Čtení a opakování souboru

První funkcí, kterou přidáte, je možnost přečíst textový soubor a zobrazit veškerý tento text v konzole. Nejprve přidáme textový soubor. Zkopírujte souborsampleQuotes.txt z úložiště GitHub pro tuto ukázku do adresáře projektu. Bude sloužit jako skript pro vaši aplikaci. Informace o tom, jak stáhnout ukázkovou aplikaci pro tento kurz, najdete v pokynech v části Ukázky a kurzy.

Dále přidejte do třídy Program následující metodu (přímo pod metodu Main ):

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

Tato metoda je speciálním typem metody jazyka C# označovanou jako metoda iterátoru. Metody iterátoru vracejí sekvence, které jsou vyhodnoceny líně. To znamená, že každá položka v sekvenci se vygeneruje tak, jak je požadována kódem, který tuto sekvenci využívá. Metody iterátoru jsou metody, které obsahují jeden nebo více yield return příkazů. Objekt vrácený metodou ReadFrom obsahuje kód pro vygenerování každé položky v sekvenci. V tomto příkladu to zahrnuje čtení dalšího řádku textu ze zdrojového souboru a vrácení tohoto řetězce. Pokaždé, když volající kód požádá o další položku ze sekvence, přečte další řádek textu ze souboru a vrátí ho. Když je soubor zcela přečtený, sekvence indikuje, že neexistují žádné další položky.

Existují dva prvky syntaxe jazyka C#, které pro vás můžou být nové. Příkaz using v této metodě spravuje vyčištění prostředků. Proměnná inicializovaná v using příkazu (readerv tomto příkladu IDisposable ) musí implementovat rozhraní. Toto rozhraní definuje jednu metodu , Dispose, která by měla být volána při uvolnění prostředku. Kompilátor vygeneruje toto volání, když provádění dosáhne konečné závorky using příkazu. Kód vygenerovaný kompilátorem zajišťuje uvolnění prostředku i v případě, že je vyvolána výjimka z kódu v bloku definovaném příkazem using.

Proměnná reader je definována pomocí klíčového var slova. var definuje implicitně napsanou místní proměnnou. To znamená, že typ proměnné je určen typem kompilace objektu přiřazeného k proměnné. Tady se jedná o návratovou hodnotu z OpenText(String) metody, což je StreamReader objekt.

Teď vyplňme kód pro čtení souboru v Main metodě:

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

Spusťte program (pomocí dotnet runpříkazu ) a uvidíte každý řádek vytištěný do konzoly.

Přidání zpoždění a formátování výstupu

To, co máte, se zobrazuje příliš rychle, než abyste ho mohli číst nahlas. Teď musíte do výstupu přidat zpoždění. Na začátku budete vytvářet základní kód, který umožňuje asynchronní zpracování. Tyto první kroky ale budou mít několik anti-vzorů. Při přidávání kódu se na anti-vzory upozorňují v komentářích a kód se aktualizuje v dalších krocích.

V této části jsou dva kroky. Nejprve aktualizujete metodu iterátoru tak, aby vracela jednotlivá slova místo celých řádků. To se s těmito úpravami dokončilo. yield return line; Nahraďte příkaz následujícím kódem:

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

Dále musíte upravit způsob, jakým používáte řádky souboru, a přidat zpoždění po napsání každého slova. Console.WriteLine(line) Nahraďte příkaz v Main metodě následujícím blokem:

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

Spusťte ukázku a zkontrolujte výstup. Nyní se vytiskne každé jedno slovo následované zpožděním 200 ms. Zobrazený výstup ale zobrazuje některé problémy, protože zdrojový textový soubor obsahuje několik řádků, které mají více než 80 znaků bez konce řádku. To může být při posouvání špatně čitelné. To se dá snadno opravit. Budete jenom sledovat délku jednotlivých řádků a vygenerovat nový řádek vždy, když délka řádku dosáhne určité prahové hodnoty. Deklarujte místní proměnnou za deklarací words v ReadFrom metodě, která obsahuje délku řádku:

var lineLength = 0;

Potom za yield return word + " "; příkaz (před pravou závorku) přidejte následující kód:

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

Spusťte ukázku a budete moct nahlas číst její předem nakonfigurované tempo.

Asynchronní úlohy

V tomto posledním kroku přidáte kód pro asynchronní zápis výstupu v jedné úloze a zároveň spustíte další úlohu pro čtení vstupu od uživatele, pokud chce zrychlit nebo zpomalit zobrazení textu nebo úplně zastavit zobrazení textu. To má několik kroků a na konci budete mít všechny aktualizace, které potřebujete. Prvním krokem je vytvoření asynchronní Task návratové metody, která představuje kód, který jste zatím vytvořili pro čtení a zobrazení souboru.

Přidejte tuto metodu do své Program třídy (je převzatá z těla vaší Main metody):

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

Všimněte si dvou změn. Za prvé, v těle metody, místo volání Wait() synchronní čekání na dokončení úkolu, tato verze používá await klíčové slovo. Chcete-li to provést, musíte přidat async modifikátor do podpisu metody. Tato metoda vrátí .Task Všimněte si, že neexistují žádné návratové příkazy, které by vrátily Task objekt. Místo toho Task je tento objekt vytvořen kódem, který kompilátor vygeneruje při použití operátoru await . Můžete si představit, že tato metoda vrátí, když dosáhne hodnoty await. Task Vrácená hodnota označuje, že práce nebyla dokončena. Po dokončení očekávaného úkolu se metoda obnoví. Po provedení do dokončení vrácená Task hodnota značí, že je dokončená. Volající kód může monitorovat vrácený Task kód, aby se zjistilo, kdy se dokončil.

await Před volání ShowTeleprompterpřidejte klíčové slovo :

await ShowTeleprompter();

To vyžaduje, abyste změnili signaturu Main metody na:

static async Task Main(string[] args)

Další informace o metodě najdete async Main v části Základy.

Dále musíte napsat druhou asynchronní metodu pro čtení z konzoly a watch pro klíče '<' (menší než), '>' (větší než) a 'X' nebo "x". Tady je metoda, kterou přidáte pro tento úkol:

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

Tím se vytvoří výraz lambda, který představuje Action delegáta, který přečte klíč z konzoly a upraví místní proměnnou představující zpoždění, když uživatel stiskne klávesy '<' (menší než) nebo '>' (větší než). Metoda delegáta se dokončí, když uživatel stiskne klávesy "X" nebo "x", což uživateli umožní kdykoli zastavit zobrazení textu. Tato metoda používá ReadKey() k blokování a čekání, než uživatel stiskne klávesu.

K dokončení této funkce musíte vytvořit novou async Task návratovou metodu, která spustí obě tyto úlohy (GetInput a ShowTeleprompter) a také spravuje sdílená data mezi těmito dvěma úkoly.

Je čas vytvořit třídu, která dokáže zpracovat sdílená data mezi těmito dvěma úkoly. Tato třída obsahuje dvě veřejné vlastnosti: zpoždění a příznak Done označující, že soubor byl zcela přečtený:

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

Vložte danou třídu do nového souboru a zahrňte ji do oboru názvů, jak je znázorněno na obrázku TeleprompterConsole . Budete také muset přidat using static příkaz na začátek souboru, abyste mohli odkazovat na Min metody a Max bez ohraničujícího názvu třídy nebo oboru názvů. Příkaz using static importuje metody z jedné třídy. To je v kontrastu s příkazem using bez static, který importuje všechny třídy z oboru názvů.

using static System.Math;

Dále je potřeba aktualizovat ShowTeleprompter metody a GetInput , aby používaly nový config objekt. Napište jednu poslední Task návratovou async metodu pro spuštění obou úkolů a ukončení po dokončení prvního úkolu:

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

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

Jedna nová metoda je volání WhenAny(Task[]) . Tím se Task vytvoří objekt, který se dokončí, jakmile se dokončí některý z úkolů v seznamu argumentů.

Dále je potřeba aktualizovat metody a ShowTeleprompterGetInput tak, aby se objekt používal config pro zpoždění:

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

Tato nová verze volá ShowTeleprompter novou metodu TeleprompterConfig ve třídě. Teď je potřeba provést aktualizaciMain, aby volala RunTeleprompter místo :ShowTeleprompter

await RunTeleprompter();

Závěr

Tento kurz vám ukázal řadu funkcí jazyka C# a knihoven .NET Core souvisejících s prací v konzolových aplikacích. Na těchto znalostech můžete stavět a prozkoumat více o jazyce a zde představených třídách. Viděli jste základy vstupně-výstupních operací souborů a konzoly, blokování a neblokující použití asynchronního programování založeného na úlohách, prohlídku jazyka C# a uspořádání programů v jazyce C# a rozhraní příkazového řádku .NET.

Další informace o vstupně-výstupních operacích souborů najdete v tématu Vstupně-výstupní operace souborů a datových proudů. Další informace o asynchronním programovacím modelu použitém v tomto kurzu najdete v tématu Asynchronní programování založené na úlohách a asynchronní programování.