[C#] Tutorial „Aplicație de consolă”



Acest tutorial vă învață câteva facilități în .NET Core și limbajul C#. Veți învăța:
  • Bazele Interfeței la linie de comandă .NET Core (CLI)
  • Structura unei Aplicații consolă C#
  • I/O Consolă
  • Bazele API-urilor de I/O Fișier în .NET
  • Bazele Programării asincrone bazate pe sarcină (Task) în .NET
Veți construi o aplicație care citește un fișier text, și imită conținutul acestui fișier text în consolă. Ieșirea la consolă este setată să se potrivească cu ritmul citirii ei cu voce tare. Puteți grăbi sau încetini ritmul apăsând tastele '<' (mai mic decât) sau '>' (mai mare decât).

Sunt o mulțime de facilități în acest tutorial. Haideți să le construim una câte una.

Cerințe preliminare

Va trebui să vă configurați mașina să ruleze .NET Core. Puteți găsi instrucțiunile de instalare pe pagina .NET Core. Puteți rula această aplicație pe Windows, Linux, maOS sau într-un container Docker. Va trebui să vă instalați editorul de cod favorit al dvs.

Creați aplicația

Primul pas este să creați o nouă aplicație. Deschideți o linie de comandă și creați un nou director pentru aplicația dvs. Făceți-l directorul curent. Tastați comanda dotnet new console în linia de comandă. Aceasta creează fișierele de începere pentru o aplicație de bază „Hello World”.

Inainte de a începe s faceți modificări, haideți să mergem prin pașii rulării simplei aplicații Hello World. După crearea aplicației, tastați dotnet restore la linia de comandă. Această comandă rulează procesul de restaurare a pachetelor NuGet. NuGet este un organizator de pachete .NET. Această comandă descarcă toate dependențele care lipsesc pentru proiectul dvs. Fiindcă acesta este un proiect nou, nici una dintre dependențe nu este la locul ei, deci prima rulare va descărca framework-ul (cadrul de lucru) .NET Core. După acest pas inițial, va trebui să rulați dotnet restore doar când adăugați pachete de dependență noi, sau actualizați versiunile oricărei dependențe a dvs.

Notă. Începând cu .NET Core 2.0, nu trebuie să rulați dotnet restore deoarece este rulat implicit de toate comenzile care cer o restaurare să aibă loc, cum sunt dotnet new, dotnet build și dotnet run. Este încă o comandă validă în unele scenarii în care facerea unei restaurări explicite are sens, cum ar fi construcții de integrare continuă în Azure DevOps Services sau în sisteme de construcție care necesită să controleze explicit timpul la care restaurarea are loc.

După restaurarea pachetelor, rulați dotnet build. Aceasta execută motorul de construcție și creează executabilul aplicației dvs. In final, executați dotnet run pentru a rula aplicația dvs.

Codul simplu al aplicației Hello World este în întregime în Program.cs. Deschideți acest fișier cu editorul de text favorit al dvs. Suntem pe cale să facem primele noastre schimbări. La începutul fișierului vedeți o instrucțiune using:

using System;

Această instrucțiune spune compilatorului că oricare tipuri din namespace-ul System sunt în scop. Ca alte limbaje orientate pe obiecte pe care poate le-ați folosit, C# folosește namespace-uri pentru a organiza tipuri. Acest program Hello World nu este diferit. Puteți vedea că programul este închis în namespace-ul cu numele bazat pe numele directorului curent. Pentru acest tutorial, haideți să schimbăm numele namespace-ului în TeleprompterConsole:

namespace TeleprompterConsole

Citirea și imitarea fișierului

Prima facilitate de adăugat este abilitatea de a citi un fișier text și a afișa tot acel text în consolă. Mai întâi, haideți să adăugăm un fișier text. Copiați fișierul sampleQuotes.txt din depozitul GitHub pentru acest exemplu în directorul proiectului dvs. Acesta va servi ca scenariu pentru aplicația dvs. Dacă v-ar plăcea informații despre cum să descărcați aplicația exemplu pentru acest subiect, vedeți instrucțiunile în subiectul Samples and Tutorials.

In continuare, adăugați următoarea metodă în clasa dvs. Program (chiar sub metoda Main):

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


Această metodă folosește tipuri din două noi namespace-uri. Pentru ca aceasta să se compileze va trebui să adăugați următoarele două linii la începutul fișierului:

using System.Collections.Generic;
using System.IO;


Interfața IEnumerable<T> este definită în namespace-ul System.Collections.Generic. Clasa File este definită în namespace-ul System.IO.

Această metodă este un tip special de metodă C# numită metodă iterator. Metodele enumeratoare întorc secvențe care sunt evaluate leneș. Aceasta înseamnă că fiecare element din secvență este generat atunci când este cerut de codul care consumă secvența. Metodele enumeratoare sunt metode care conțin una sau mai multe instrucțiuni yield return. Obiectul întors de metoda ReadFrom conține codul care generează fiecare element în secvență. In acest exemplu, aceasta implică citirea următorului rând de text din fișierul sursă, și întoarcerea acelui șir. De fiecare dată când codul apelant cere următorul element din secvență, codul citește următorul rând de text din fișier și îl întoarce. Când fișierul este citit complet, secvența indică faptul că nu mai există elemente.

Există alte două elemente de sintaxă C# care ar putea fi noi pentru dvs. Instrucțiunea using din această metodă organizează curățarea resurselor. Variabila care este inițializată în instrucțiunea using (reader, în acest exemplu) trebuie să implementeze interfața IDisposable. Această interfață definește o singură metodă, Dispose, care ar trebui să fie apelată când resursa ar trebui să fie eliberată. Compilatorul generează acel apel când execuția ajunge la acolada închisă a instrucțiunii using. Codul generat de compilator se asigură că resursa este eliberată chiar dacă o excepție este aruncată din codul din blocul definit de instrucțiunea using.

Variabila reader este definită folosind cuvântul cheie var. var definește o variabilă locală implicit tipizată. Aceasta înseamnă că tipul variabilei este determinat de tipul obiectului atribuit variabilei la momentul compilării. Aici, acesta este valoarea întoarsă de metoda OpenText(String), care este un obiect StreamReader.

Acum, haideți să completăm codul pentru citirea fișierului în metoda Main:

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


Rulați programul (folosind dotnet run) și dvs. puteți vedea fiecare linie tipărită în consolă.

Adăugarea întârzierilor și formatarea ieșirii

Ceea ce aveți este afișat mult prea repede pentru a fi citit cu voce tare. Acum dvs. trebuie să adăugați întârzieri în ieșire. Începând, dvs. veți fi construind o parte din codul nucleu care permite procesarea asincronă. Totuși, acești primi pași vor urmă câteva anti-exemple. Anti-exemplele sunt indicate în comentarii după cum adăugați codul, și codul va fi actualizat în pași următori.

Sunt doi pași în această secțiune. Mai întâi, veți actualiza metoda iterator să întoarcă cuvinte singure în loc de linii întregi. Aceea se face cu aceste modificări. Inlocuiți instrucțiunea yield return line; cu următorul cod:

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


In continuare, trebuie să modificați cum consumați liniile fișierului, și să adăugați o întârziere după scrierea fiecărui cuvânt. Inlocuiți instrucțiunea Console.WriteLine(line) în metoda Main cu următorul bloc:

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


Clasa Task este în namespace-ul System.Threading.Tasks, deci trebuie să adăugați acea instrucțiune using la începutul fișierului:

using System.Threading.Tasks;

Rulați exemplul, și verificați ieșirea. Acum, fiecare cuvânt singur este tipărit, urmat de o întârziere de 200 ms. Totuși, ieșirea afișată arată unele probleme deoarece fișierul de text sursă are câteva linii care au mai mult de 80 de caractere fără o rupere de linie. Aceasta poate fi greu de citit în timp ce se derulează. Este ușor de rezolvat. Doar veți urmări lungimea fiecărei linii, și veți genera o nouă linie de fiecare dată când lungimea liniei atinge o anumită limită. Declarați o variabilă locală după declararea words în metoda ReadFrom care reține lungimea liniei:

var lineLength = 0;

Apoi, adăugați următorul cod după instrucțiunea yield return word + " "; (înainte de acolada închisă):

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


Rulați exemplul, și dvs. veți putea citi cu voce tare la ritmul lui preconfigurat.

Sarcini asincrone

In acest pas final, dvs. veți adăuga codul care scrie ieșirea asincron într-o sarcină, în același timp rulând o altă sarcină care citește intrare de la utilizator dacă dânsul/dânsa dorește să grăbească sau să încetinească afișarea textului. Acesta are câțiva pași în el și la sfârșit, veți avea toate actualizările de care aveți nevoie. Primul pas este să creați o metodă asincronă care întoarce Task, metodă care reprezintă codul pe care l-ați creat până acum să citească și să afișeze fișierul.

Adăugați această metodă în clasa dvs. Program (ea este luată din corpul metodei dvs. 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);
        }
    }
}


Veți observa două schimbări. Mai întâi, în corpul metodei, în loc de a apela Wait() pentru a aștepta în mod sincron pentru o sarcină să se finalizeze, această versiune folosește cuvântul cheie await. Pentru a face aceasta, dvs. trebuie să adăugați modificatorul async la semnătura metodei. Această metodă întoarce un Task. Observați că nu există nici o instrucțiune de întoarcere care întoarce un obiect Task. In loc, acel obiect Task este creat de codul generat de compilator când folosiți operatorul await. Vă puteți imagina că această metodă întoarce când atinge un await. Task-ul întors indică faptul că munca nu s-a terminat. Metoda își revine când sarcina așteptată se finalizează. Când s-a executat până la final, Task-ul întors indică faptul că este complet. Codul apelant poate monitoriza acel Task întors să determine când s-a finalizat.

Puteți apela această nouă metodă în metoda dvs. Main:

ShowTeleprompter().Wait();

Aici, în Main, codul așteaptă în mod sincron. Ar trebui să folosiți operatorul await în loc de așteptarea sincronă ori de câte ori este posibil. Dar, în metoda Main a unei aplicații consolă, nu puteți folosi operatorul await. Aceasta ar rezulta în terminarea aplicației înainte ca toate sarcinile să fie finalizate.

Notă. Dacă folosiți C# 7.1 sau mai târziu, puteți crea aplicații consolă cu metoda async_Main.

In continuare, trebuie să scrieți următoarea metodă asincronă pentru a citi din Consolă și a urmări tastele '<' (mai mic decât) și '>' (mai mare decât). Aici este metoda pe care o adăugați pentru această sarcină:

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;
            }
        } while (true);
    };
    await Task.Run(work);
}


Aceasta creează o expresie lambda să reprezinte un delegat Action care citește o tastă din Consolă și modifică o variabilă locală reprezentând întârzierea când utilizatorul apasă tastele '<' (mai mic decât) sau '>' (mai mare decât). Această metodă folosește ReadKey() pentru a bloca și a aștepta după utilizator să apese o tastă.

Pentru a finaliza această facilitate, dvs. trebuie să creați o nouă metodă întorcând async Task care începe ambele aceste sarcini (GetInput și ShowTeleprompter), și de asemenea organizează datele împărțite între aceste două sarcini.

Este timpul să creați o clasă care poate mânui datele împărțite între aceste două sarcini. Această clasă conține două proprietăți publice: întârzierea, și un steag Done să indice că fișierul a fost citit în întregime:

namespace TeleprompterConsole
{
    internal class TelePrompterConfig
    {
        private object lockHandle = new object();
        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);
            lock (lockHandle)
            {
                DelayInMilliseconds = newDelay;
            }
        }


        public bool Done { get; private set; }

        public void SetDone()
        {
            Done = true;
        }
    }
}


Puneți această clasă într-un fișier nu, și închideți această clasă în namespace-ul TeleprompterConsole cum s-a arătat deasupra. Dvs. va trebui de asemenea să adăugați o instrucțiune using static astfel încât dvs. puteți să referiți la metodele Min și Max fără clasa care le cuprinde sau nume de namespace-uri. O instrucțiune using static importă metodele dintr-o clasă. Aceasta este în contrast cu instrucțiunile using folosite până în acest punct care au importat toate clasele dintr-un namespace.

using static System.Math;

Cealaltă facilitate a limbajului care este nouă este instrucțiunea lock. Această instrucțiune asigură că doar un singur fir de execuție poate fi în acel cod în oricare moment dat. Dacă un fir de execuție este în secțiunea blocată, alte fire de execuție trebuie să aștepte după primul fir de execuție să iasă din acea secțiune. Instrucțiunea lock folosește un obiect care păzește secțiunea lock. Această clasă urmează un idiom standard să blocheze un obiect privat în clasă.

In continuare, dvs. trebuie să actualizați metodele ShowTeleprompter și GetInput să folosească noul obiect config. Scrieți o finală metodă async care întoarce Task pentru a începe ambele sarcini și a ieși când prima se finalizează:

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


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


Metoda cea nouă aici este apelul WhenAny(Task[]). Aceasta creează un Task care se finalizează de îndată ce oricare din sarcinile din lista sa de argumente se finalizează.

In continuare, dvs. va trebui să actualizați ambele metode ShowTeleprompter și GetInput să folosească obiectul config pentru întârziere:

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);
        } while (!config.Done);
    };
    await Task.Run(work);
}


Această nouă versiune a ShowTeleprompter apelează o nouă metodă în clasa TeleprompterConfig. Acum, dvs. trebuie să actualizați Main să apeleze RunTeleprompter în loc de ShowTeleprompter:

RunTeleprompter().Wait();

Concluzie

Acest tutorial v-a arătat câteva din facilitățile din jurul limbajului C# și al bibliotecilor .NET Core legate cu lucrul în aplicații Consolă. Dvs. puteți construi pe baza acestei cunoașteri pentru a explora mai mult despre limbaj, și clasele introduse aici. Ați văzut bazele I/O Fișier și Consolă, programarea asincronă bazată pe Task-uri care blochează și care nu blochează, un tur al limbajului C# și cum programele C# sunt organizate și interfața Linie de Comandă și uneltele .NET Core.

Pentru mai multe informații despre I/O cu fișiere, vedeți subiectul File and Stream I/O. Pentru mai multe informații despre modelul de programare asincronă folosit în acest tutorial, vedeți subiectul Task-based Asynchronous Programming și subiectul Asynchronous programming.

Tradus din această pagină oficială de documentație Microsoft.

Niciun comentariu:

Trimiteți un comentariu