Konzolalkalmazás

Ez az oktatóanyag a .NET és a C# nyelv számos funkcióját ismerteti. Az oktatóanyagból a következőket sajátíthatja el:

  • A .NET CLI alapjai
  • A C#-konzolalkalmazás felépítése
  • Konzol I/O
  • A fájl I/O API-k alapjai a .NET-ben
  • A feladatalapú aszinkron programozás alapjai a .NET-ben

Létrehoz egy alkalmazást, amely beolvassa a szövegfájlt, és a konzolon visszhangozza a szövegfájl tartalmát. A konzol kimenete a hangos olvasáshoz igazodik. A "" (kisebb mint) vagy a "<" (nagyobb, mint) billentyű lenyomásával felgyorsíthatja vagy> lelassíthatja a tempót. Ezt az alkalmazást Windows, Linux, macOS vagy Docker-tárolóban futtathatja.

Ebben az oktatóanyagban számos funkció található. Építsük fel őket egyenként.

Előfeltételek

Az alkalmazás létrehozása

Az első lépés egy új alkalmazás létrehozása. Nyisson meg egy parancssort, és hozzon létre egy új könyvtárat az alkalmazáshoz. Állítsa be az aktuális könyvtárat. Írja be a parancsot dotnet new console a parancssorba. Ez létrehozza a kezdőfájlokat egy alapszintű ""Helló világ!" alkalmazás" alkalmazáshoz.

Mielőtt elkezdené a módosításokat, futtassa az egyszerű "Helló világ!" alkalmazás alkalmazást. Az alkalmazás létrehozása után írja be dotnet run a parancsot a parancssorba. Ez a parancs futtatja a NuGet-csomag visszaállítási folyamatát, létrehozza az alkalmazás végrehajtható fájlját, és futtatja a végrehajtható fájlt.

Az egyszerű "Helló világ!" alkalmazás alkalmazáskód mind a Program.cs fájlban található. Nyissa meg a fájlt a kedvenc szövegszerkesztőjével. Cserélje le a Program.cs fájlban lévő kódot a következő kódra:

namespace TeleprompterConsole;

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

A fájl tetején tekintse meg az utasítást namespace . A többi objektumorientált nyelvhez hasonlóan a C# névtereket használ a típusok rendszerezéséhez. Ez a "Helló világ!" alkalmazás program nem különbözik. Láthatja, hogy a program a névtérben van a következő névvel TeleprompterConsole: .

A fájl olvasása és visszhangja

Az első hozzáadandó funkció egy szövegfájl olvasása és az összes szöveg megjelenítése a konzolon. Először adjunk hozzá egy szövegfájlt. Másolja a sampleQuotes.txt fájlt a minta GitHub-adattárából a projektkönyvtárba. Ez lesz az alkalmazás szkriptje. Az oktatóanyaghoz tartozó mintaalkalmazás letöltéséről a Minták és oktatóanyagok című témakörben talál további információt.

Ezután adja hozzá a következő metódust a Program osztályhoz (közvetlenül a Main metódus alá):

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

Ez a metódus egy speciális C#-metódus, más néven iterátormetódus. Az iterátor metódusok lazán kiértékelt szekvenciákat adnak vissza. Ez azt jelenti, hogy a sorozat minden eleme úgy jön létre, ahogyan azt a sorozatot használó kód kéri. Az iterátor metódusok olyan metódusok, amelyek egy vagy több yield return utasítást tartalmaznak. A metódus által ReadFrom visszaadott objektum tartalmazza a sorozat egyes elemeinek létrehozásához használt kódot. Ebben a példában ez magában foglalja a forrásfájl következő szövegsorának beolvasását és a sztring visszaadását. Minden alkalommal, amikor a hívó kód lekéri a következő elemet a sorozatból, a kód beolvassa a következő szövegsort a fájlból, és visszaadja azt. A fájl teljes olvasása után a sorrend azt jelzi, hogy nincs több elem.

Két C#-szintaxiselem közül választhat, amelyek újak lehetnek. Az using ebben a metódusban található utasítás kezeli az erőforrás-törlést. Az utasításban inicializált változónak (readerebben a using példában) implementálnia kell a IDisposable felületet. Ez az interfész egyetlen metódust határoz meg, Disposeamelyet az erőforrás felszabadításakor kell meghívni. A fordító akkor hozza létre ezt a hívást, amikor a végrehajtás eléri az using utasítás záró zárójelét. A fordító által létrehozott kód biztosítja, hogy az erőforrás akkor is felszabaduljon, ha kivételt okoz a using utasítás által meghatározott blokk kódjából.

A reader változó a var kulcsszóval van definiálva. varimplicit módon beírt helyi változót határoz meg. Ez azt jelenti, hogy a változó típusát a változóhoz rendelt objektum fordítási idő típusa határozza meg. Itt ez a metódus visszatérési OpenText(String) értéke, amely egy StreamReader objektum.

Most töltse ki a kódot, hogy beolvassa a fájlt a Main metódusban:

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

Futtassa a programot (a használatával dotnet run), és láthatja a konzolra kiírt összes sort.

Késések és formázási kimenet hozzáadása

Amije van, az túl gyorsan jelenik meg ahhoz, hogy hangosan elolvassa. Most hozzá kell adnia a kimenet késéseit. Első lépésként néhány olyan alapvető kódot fog létrehozni, amely lehetővé teszi az aszinkron feldolgozást. Ezek az első lépések azonban néhány antimintát követnek. A kód hozzáadásakor az antiminták a megjegyzésekben lesznek kiemelve, és a kód a későbbi lépésekben frissülni fog.

Ennek a szakasznak két lépése van. Először frissítenie kell az iterátormetódust, hogy egyetlen szót ad vissza a teljes sorok helyett. Ez történik ezekkel a módosításokkal. Cserélje le az yield return line; utasítást a következő kódra:

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

Ezután módosítania kell a fájl sorainak felhasználásának módját, és az egyes szavak írása után késleltetést kell hozzáadnia. Cserélje le a Console.WriteLine(line) metódus utasítását Main a következő blokkra:

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

Futtassa a mintát, és ellenőrizze a kimenetet. Most minden egyes szót kinyomtatnak, amelyet 200 ms késleltetés követ. A megjelenített kimenet azonban problémákat jelez, mivel a forrásszövegfájl több sorból áll, amelyek 80 karakternél hosszabbak, sortörés nélkül. Ezt nehéz lehet elolvasni görgetés közben. Ezt könnyű kijavítani. Csak nyomon követheti az egyes sorok hosszát, és létrehozhat egy új sort, amikor a vonal hossza eléri a küszöbértéket. Deklaráljon egy helyi változót a sorhosszt tartalmazó metódus deklarációja wordsReadFrom után:

var lineLength = 0;

Ezután adja hozzá a következő kódot az yield return word + " "; utasítás után (a záró zárójel előtt):

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

Futtassa a mintát, és az előre konfigurált tempójában hangosan olvashat.

Aszinkron feladatok

Ebben az utolsó lépésben hozzáadja azt a kódot, amely aszinkron módon írja a kimenetet egy feladatba, miközben egy másik feladatot is futtat a felhasználótól érkező bemenet olvasásához, ha fel szeretné gyorsítani vagy lelassítani a szöveg megjelenítését, vagy teljesen le szeretné állítani a szöveg megjelenítését. Ez tartalmaz néhány lépést, és a végére minden szükséges frissítéssel rendelkezni fog. Az első lépés egy aszinkron Task visszatérési metódus létrehozása, amely az eddig létrehozott kódot jelöli a fájl olvasásához és megjelenítéséhez.

Adja hozzá ezt a metódust a Program osztályhoz (a metódus törzséből származik Main ):

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

Két változást fog látni. Először is, a metódus törzsében ahelyett, hogy a hívás Wait() szinkron módon megvárja a feladat befejezését, ez a verzió a kulcsszót await használja. Ehhez hozzá kell adnia a módosítót a async metódus aláírásához. Ez a metódus egy Taskértéket ad vissza. Figyelje meg, hogy nincsenek olyan visszatérési utasítások, amelyek objektumot Task adnak vissza. Ehelyett az Task objektumot a fordító által az operátor használatakor await generált kód hozza létre. El lehet képzelni, hogy ez a metódus akkor tér vissza, amikor eléri a értéket await. A visszaadott Task érték azt jelzi, hogy a munka nem fejeződött be. A metódus a várt feladat befejeződésekor folytatódik. Amikor a végrehajtás befejeződött, a visszaadott Task érték azt jelzi, hogy befejeződött. A hívási kód képes monitorozni a visszaadott Task kódot, hogy megállapítsa, mikor fejeződött be.

Adjon hozzá egy kulcsszót await a hívása előtt:ShowTeleprompter

await ShowTeleprompter();

Ehhez módosítania kell a metódus aláírását a Main következőre:

static async Task Main(string[] args)

A módszerről azasync Main alapok című szakaszban olvashat bővebben.

Ezután meg kell írnia a második aszinkron metódust a konzolról való olvasáshoz, és watch a "<" (kisebb, mint), ">" (nagyobb, mint) és "X" vagy "x" kulcsokhoz. A feladathoz a következő metódust adja hozzá:

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

Ez létrehoz egy lambda kifejezést, amely egy Action olyan meghatalmazottat jelöl, aki beolvassa a kulcsot a konzolból, és módosít egy helyi változót, amely jelzi a késést, amikor a felhasználó lenyomja a "<" (kisebb, mint) vagy ">" (nagyobb, mint) billentyűket. A delegálási módszer akkor fejeződik be, amikor a felhasználó lenyomja az "X" vagy az "x" billentyűket, így a felhasználó bármikor leállíthatja a szövegmegjelenítést. Ezzel a módszerrel ReadKey() letilthatja és megvárhatja, amíg a felhasználó lenyom egy billentyűt.

A funkció befejezéséhez létre kell hoznia egy új async Task visszatérési módszert, amely mindkét tevékenységet elindítja (GetInput és ShowTeleprompter), és a két tevékenység közötti megosztott adatokat is kezeli.

Ideje létrehozni egy osztályt, amely képes kezelni a két feladat közötti megosztott adatokat. Ez az osztály két nyilvános tulajdonságot tartalmaz: a késleltetést és egy jelzőt Done , amely azt jelzi, hogy a fájl teljes olvasása megtörtént:

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

Helyezze az osztályt egy új fájlba, és adja hozzá az osztályt a névtérbe az TeleprompterConsole ábrán látható módon. A fájl tetején egy using static utasítást is hozzá kell adnia, hogy a és Max metódusokra Min hivatkozzon az osztály vagy a névtér neve nélkül. Egy using static utasítás importálja a metódusokat egy osztályból. Ez ellentétben áll a using anélküli staticutasítással, amely az összes osztályt egy névtérből importálja.

using static System.Math;

Ezután frissítenie kell a és GetInput a ShowTeleprompter metódusokat az új config objektum használatához. Írjon egy végső Task visszatérési async módszert a tevékenységek elindításához és a kilépéshez, amikor az első feladat befejeződik:

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

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

Az egyetlen új metódus itt a WhenAny(Task[]) hívás. Ez létrehoz egy Task olyan elemet, amely akkor fejeződik be, amikor az argumentumlistában szereplő tevékenységek bármelyike befejeződik.

Ezután frissítenie kell a és GetInput a ShowTeleprompter metódust is, hogy az config objektumot használhassa a késleltetéshez:

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

Az új verzió ShowTeleprompter egy új metódust hív meg az TeleprompterConfig osztályban. Most frissítenie Main kell a hívásra RunTeleprompter a helyett ShowTeleprompter:

await RunTeleprompter();

Összegzés

Ez az oktatóanyag a C# nyelv és a konzolalkalmazások használatához kapcsolódó .NET Core-kódtárak számos funkcióját mutatta be. Erre a tudásra építve többet is megtudhat a nyelvről és az itt bemutatott osztályokról. Megismerkedett a Fájl és konzol I/O alapjaival, a feladatalapú aszinkron programozás blokkolásával és letiltásával, a C# nyelv és a C#-programok rendszerezésének bejárásával, valamint a .NET CLI-vel.

A fájl I/O-járól további információt a Fájl és stream I/O című témakörben talál. Az oktatóanyagban használt aszinkron programozási modellel kapcsolatos további információkért lásd: Feladatalapú aszinkron programozás és aszinkron programozás.