Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Questa esercitazione illustra diverse funzionalità in .NET e nel linguaggio C#. Si apprenderà:
- Nozioni di base dell'interfaccia della riga di comando di .NET
- Struttura di un'applicazione console C#
- Input/Output della console
- Nozioni di base sulle API di I/O dei file in .NET
- Nozioni di base sulla programmazione asincrona basata su attività in .NET
Si creerà un'applicazione che legge un file di testo e riecherà il contenuto del file di testo nella console. L'uscita sulla console è calibrata per corrispondere alla velocità della lettura ad alta voce. È possibile accelerare o rallentare il ritmo premendo i tasti '<' (minore di) o '>' (maggiore di). È possibile eseguire questa applicazione in Windows, Linux, macOS o in un contenitore Docker.
In questo tutorial sono disponibili molte funzionalità. Creiamoli uno per uno.
Prerequisiti
Creare l'app
Il primo passaggio consiste nel creare una nuova applicazione. Aprire un prompt dei comandi e creare una nuova directory per l'applicazione. Imposta la directory corrente. Digitare il comando dotnet new console al prompt dei comandi. Per esempio:
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.
In questo modo vengono creati i file di avvio per un'applicazione "Hello World" di base.
Prima di iniziare a apportare modifiche, eseguire la semplice applicazione Hello World. Dopo aver creato l'applicazione, digitare dotnet run al prompt dei comandi. Questo comando esegue il processo di ripristino del pacchetto NuGet, crea l'eseguibile dell'applicazione ed esegue il file eseguibile.
Il semplice codice dell'applicazione Hello World è tutto in Program.cs. Aprire il file con l'editor di testo preferito. Sostituire il codice in Program.cs con il codice seguente:
namespace TeleprompterConsole;
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
Nella parte superiore del file, trova un'istruzione namespace. Analogamente ad altri linguaggi orientati agli oggetti che è possibile usare, C# usa gli spazi dei nomi per organizzare i tipi. Questo programma Hello World non è diverso. È possibile notare che il programma si trova nello spazio dei nomi con il nome TeleprompterConsole.
Lettura e risposta del file
La prima funzionalità da aggiungere è la possibilità di leggere un file di testo e visualizzare tutto il testo nella console. Aggiungere prima di tutto un file di testo. Copiare il file sampleQuotes.txt dal repository GitHub per questo esempio nella tua directory del progetto. Verrà usato come script per l'applicazione. Per informazioni su come scaricare l'app di esempio per questa esercitazione, vedere le istruzioni in esempi ed esercitazioni.
Aggiungere quindi il metodo seguente nella classe Program (immediatamente sotto il metodo Main):
static IEnumerable<string> ReadFrom(string file)
{
string? line;
using (var reader = File.OpenText(file))
{
while ((line = reader.ReadLine()) != null)
{
yield return line;
}
}
}
Questo metodo è un tipo speciale di metodo C# denominato metodo iteratore . I metodi iteratori restituiscono sequenze valutate in modo ritardato. Ciò significa che ogni elemento nella sequenza viene generato come richiesto dal codice che utilizza la sequenza. I metodi iteratore sono metodi che contengono una o più istruzioni yield return. L'oggetto restituito dal metodo ReadFrom contiene il codice per generare ogni elemento nella sequenza. In questo esempio, che implica la lettura della riga di testo successiva dal file di origine e la restituzione di tale stringa. Ogni volta che il codice chiamante richiede l'elemento successivo dalla sequenza, il codice legge la riga di testo successiva dal file e la restituisce. Quando il file viene letto completamente, la sequenza indica che non sono presenti altri elementi.
Esistono due elementi della sintassi C# che potrebbero essere nuovi. L'istruzione using in questo metodo gestisce la pulizia delle risorse. La variabile inizializzata nell'istruzione using (reader, in questo esempio) deve implementare l'interfaccia IDisposable. Tale interfaccia definisce un singolo metodo, Dispose, che deve essere chiamato quando la risorsa deve essere rilasciata. Il compilatore genera la chiamata quando l'esecuzione raggiunge la graffa di chiusura della parentesi dell'istruzione using. Il codice generato dal compilatore garantisce che la risorsa venga rilasciata anche se viene generata un'eccezione dal codice nel blocco definito dall'istruzione using.
La variabile reader viene definita usando la parola chiave var.
var definisce una variabile locale tipizzata in modo implicito . Ciò significa che il tipo della variabile è determinato dal tipo in fase di compilazione dell'oggetto assegnato alla variabile. Qui, si tratta del valore restituito dal metodo OpenText(String), che è un oggetto StreamReader.
Compilare ora il codice per leggere il file nel metodo Main:
var lines = ReadFrom("sampleQuotes.txt");
foreach (var line in lines)
{
Console.WriteLine(line);
}
Eseguire il programma (usando dotnet run) ed è possibile visualizzare tutte le righe stampate nella console.
Aggiunta di ritardi e formattazione dell'output
Quello che avete viene mostrato troppo velocemente per essere letto ad alta voce. È ora necessario aggiungere i ritardi nell'output. All'inizio, si creerà parte del codice principale che abilita l'elaborazione asincrona. Tuttavia, questi primi passaggi seguiranno alcuni anti-pattern. Gli anti-pattern vengono indicati nei commenti man mano che si aggiunge il codice e il codice verrà aggiornato nei passaggi successivi.
Questa sezione prevede due passaggi. Prima di tutto, si aggiornerà il metodo iteratore per restituire singole parole anziché intere righe. Questa operazione viene eseguita con queste modifiche. Sostituire l'istruzione yield return line; con il codice seguente:
var words = line.Split(' ');
foreach (var word in words)
{
yield return word + " ";
}
yield return Environment.NewLine;
Successivamente, è necessario modificare la modalità di utilizzo delle righe del file e aggiungere un ritardo dopo la scrittura di ogni parola. Sostituire l'istruzione Console.WriteLine(line) nel metodo Main con il blocco seguente:
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();
}
Esegui l'esempio e controlla l'output. Ora, ogni singola parola viene stampata, seguita da un ritardo di 200 ms. Tuttavia, l'output visualizzato mostra alcuni problemi perché il file di testo di origine ha più di 80 caratteri senza interruzione di riga. Può risultare difficile da leggere mentre scorre. È facile da risolvere. È sufficiente tenere traccia della lunghezza di ogni riga e generare una nuova riga ogni volta che la lunghezza della riga raggiunge una determinata soglia. Dichiarare una variabile locale dopo la dichiarazione di words nel metodo ReadFrom che contiene la lunghezza della riga:
var lineLength = 0;
Aggiungere quindi il codice seguente dopo l'istruzione yield return word + " "; (prima della parentesi graffa di chiusura):
lineLength += word.Length + 1;
if (lineLength > 70)
{
yield return Environment.NewLine;
lineLength = 0;
}
Esegui l'esempio, e potrai leggere ad alta voce al ritmo preconfigurato.
Attività asincrone
In questo passaggio finale si aggiungerà il codice per scrivere l'output in modo asincrono in un'attività, eseguendo anche un'altra attività per leggere l'input dell'utente se vogliono velocizzare o rallentare la visualizzazione del testo o arrestare completamente la visualizzazione del testo. Questa operazione include alcuni passaggi e, alla fine, si avranno tutti gli aggiornamenti necessari. Il primo passaggio consiste nel creare un metodo asincrono Task che rappresenta il codice creato finora per leggere e visualizzare il file.
Aggiungere questo metodo alla classe Program (viene ricavato dal corpo del metodo 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);
}
}
}
Si noteranno due modifiche. Prima di tutto, nel corpo del metodo, invece di chiamare Wait() per attendere in modo sincrono il completamento di un'attività, questa versione usa la parola chiave await. A tale scopo, è necessario aggiungere il modificatore async alla firma del metodo. Questo metodo restituisce un Task. Si noti che non sono presenti istruzioni di ritorno che restituiscono un oggetto Task. Al contrario, tale oggetto Task viene creato dal codice generato dal compilatore quando si usa l'operatore await. È possibile immaginare che questo metodo restituisca quando raggiunge un await. Il Task restituito indica che il lavoro non è stato completato. Il metodo riprende al completamento dell'attività attesa. Quando è stata eseguita fino al completamento, il Task restituito indica che è stato completato.
Il codice chiamante può monitorare il Task restituito per determinare quando è stato completato.
Aggiungere una parola chiave await prima della chiamata a ShowTeleprompter:
await ShowTeleprompter();
È quindi necessario modificare la firma del metodo Main in:
static async Task Main(string[] args)
Altre informazioni sul metodo async Main nella sezione Nozioni fondamentali.
Successivamente, è necessario scrivere il secondo metodo asincrono per leggere dalla Console e osservare i tasti '<' (minore di), '>' (maggiore di) e 'X' o 'x'. Ecco il metodo che aggiungi per quell'attività:
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);
}
In questo modo viene creata un'espressione lambda per rappresentare un delegato Action che legge una chiave dalla console e modifica una variabile locale che rappresenta il ritardo quando l'utente preme i tasti '<' (minore di) o '>' (maggiore di). Il metodo delegato termina quando l'utente preme i tasti 'X' o 'x', che consentono all'utente di arrestare la visualizzazione del testo in qualsiasi momento. Questo metodo usa ReadKey() per bloccare e attendere che l'utente preme un tasto.
È possibile creare una classe in grado di gestire i dati condivisi tra queste due attività. Questa classe contiene due proprietà pubbliche: il ritardo e un flag Done per indicare che il file è stato letto completamente:
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;
}
}
Creare un nuovo file; può essere qualsiasi nome che termina con .cs. Ad esempio, TelePrompterConfig.cs. Incollare il codice della classe TelePrompterConfig, salvare e chiudere. Inserire tale classe nel namespace TeleprompterConsole come illustrato. Si noti che l'istruzione using static consente di fare riferimento ai metodi Min e Max senza utilizzare i nomi delle classi o degli spazi dei nomi. Un'istruzione using static importa i metodi da una classe. Ciò contrasta con l'istruzione using senza static, che importa tutte le classi da un namespace.
Successivamente, è necessario aggiornare i metodi ShowTeleprompter e GetInput per usare il nuovo oggetto config. Per completare questa funzionalità, è necessario creare un nuovo metodo di restituzione async Task che avvia entrambe queste attività (GetInput e ShowTeleprompter) e gestisce anche i dati condivisi tra queste due attività. Creare un'attività RunTelePrompter per avviare entrambe le attività e uscire al termine della prima attività:
private static async Task RunTeleprompter()
{
var config = new TelePrompterConfig();
var displayTask = ShowTeleprompter(config);
var speedTask = GetInput(config);
await Task.WhenAny(displayTask, speedTask);
}
Il nuovo metodo qui è la chiamata WhenAny(Task[]). In questo modo viene creato un Task che termina non appena una delle attività nel suo elenco di argomenti viene completata.
Successivamente, è necessario aggiornare entrambi i metodi ShowTeleprompter e GetInput per utilizzare l'oggetto config per gestire il ritardo. L'oggetto config viene passato come parametro a questi metodi. Usare copia/incolla per sostituire completamente i metodi con il nuovo codice qui. È possibile vedere che il codice usa attributi e chiama i metodi dall'oggetto config:
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);
}
È ora necessario aggiornare Main per chiamare RunTeleprompter anziché ShowTeleprompter:
await RunTeleprompter();
Conclusione
Questa esercitazione ha illustrato alcune funzionalità relative al linguaggio C# e alle librerie .NET Core correlate all'uso nelle applicazioni console. È possibile sviluppare queste conoscenze per esplorare altre informazioni sul linguaggio e le classi introdotte qui. Sono state illustrati i concetti di base dell'I/O di file e della console, il blocco e l'uso non bloccante della programmazione asincrona basata su attività, una panoramica del linguaggio C# e del modo in cui sono organizzati i programmi C# e l'interfaccia della riga di comando di .NET.
Per altre informazioni sull'I/O dei file, vedere file e flussi di I/O. Per ulteriori informazioni sul modello di programmazione asincrona utilizzato in questo tutorial, vedere Programmazione Asincrona Basata su Attività e Programmazione Asincrona.