Utilizzare oggetti e classi della libreria standard in C#

Dopo che abbiamo familiarizzato con la funzionalità di base degli oggetti, andremo a considerare brevemente alcune classi di sistema comunemente utilizzate dalla libreria standard del .NET Framework.

In questo modo vedremo in pratica il materiale spiegato finora e mostreremo anche come le classi di sistema semplificano il nostro lavoro quotidiano.

La classe System.Environment

Iniziamo con una delle classi di sistema di base nel .NET Framework: System.Environment. Essa contiene un insieme di campi e metodi utili, che facilitano l'ottenimento di informazioni sull'hardware e sul sistema operativo e, alcuni di essi, danno la possibilità di interagire con l'ambiente del programma.

Ecco una parte della funzionalità fornita da questa classe:

  • Informazioni sul numero di processori, il nome della rete del computer, la versione del sistema operativo, il nome dell'utente corrente, la directory corrente, ecc.
  • Accesso a proprietà esterne definite e variabili d'ambiente.

Ora mostreremo un'applicazione interessante di un metodo della classe Environment, che è comunemente utilizzato in pratica quando si sviluppano programmi con prestazioni critiche.

Rileveremo il tempo necessario per l'esecuzione del codice sorgente con l'aiuto della proprietà TickCount. Ecco come funziona:

class SystemTest
{
    static void Main()
    {
        int sum = 0;
        int startTime = Environment.TickCount;

        // Il frammento di codice da testare
        for (int i = 0; i < 100000000; i++)
        {
            sum++;
        }

        int endTime = Environment.TickCount;
        Console.WriteLine("Il tempo trascorso è {0} sec.",
            (endTime - startTime) / 1000.0);
    }
}

La proprietà statica TickCount della classe Environment restituisce come risultato il numero di millisecondi trascorsi da quando il computer è stato acceso fino al momento della chiamata del metodo. Con il suo aiuto rileviamo i millisecondi trascorsi prima e dopo l'esecuzione del codice sorgente. La loro differenza è il tempo desiderato per l'esecuzione del frammento di codice sorgente misurato in millisecondi.

Come risultato dell'esecuzione del programma sull'output standard stampiamo un risultato del seguente tipo (il tempo misurato varia in base alla configurazione corrente del computer e al suo carico di lavoro in quel momento):

Il tempo trascorso è 0.031 sec.

Nell'esempio abbiamo utilizzato due membri statici di due classi di sistema: la proprietà statica Environment.TickCount e il metodo statico Console.WriteLine(…).

La classe System.Math

La classe System.Math contiene metodi per eseguire operazioni numeric­he e matematiche di base come elevare un numero a potenza, calcolare un logaritmo e una radice quadrata, oltre ad alcune funzioni trigonometriche. Proporremo un semplice esempio che ne illustra l'uso.

Vogliamo scrivere un programma che calcoli l'area di un triangolo a partire da due lati e dall'angolo compreso espresso in gradi. Ci occorrono quindi il metodo Sin(…) e la costante PI della classe Math. Con \pi possiamo convertire facilmente i gradi inseriti in radianti. Ecco l'implementazione della logica descritta:

class MathTest
{
    static void Main()
    {
        Console.WriteLine("Lunghezza del primo lato:");
        double a = double.Parse(Console.ReadLine());
        Console.WriteLine("Lunghezza del secondo lato:");
        double b = double.Parse(Console.ReadLine());
        Console.WriteLine("Angolo compreso tra i lati in gradi:");
        int angle = int.Parse(Console.ReadLine());

        double angleInRadians = Math.PI * angle / 180.0;
        Console.WriteLine("Area del triangolo: {0}",
            0.5 * a * b * Math.Sin(angleInRadians));
    }
}

Possiamo verificare facilmente il programma controllando che calcoli correttamente l'area di un triangolo equilatero. Per comodità scegliamo la lunghezza dei lati pari a 2, quindi l'area nota dalla formula ben conosciuta è:

S = \frac{\sqrt{3}}{4}\,2^{2} = \sqrt{3} \approx 1.7320508\dots

Inserendo consecutivamente i numeri 2, 2, 60, sull'output standard vedremo:

Lunghezza del primo lato:
2
Lunghezza del secondo lato:
2
Angolo compreso tra i lati in gradi:
60
Area del triangolo: 1.73205080756888

A seconda della localizzazione del sistema (impostazioni regionale e lingua) l'output potrebbe essere "1,73205080756888" oppure "1.73205080756888". È possibile fissare il punto decimale a "." con la riga di codice seguente, da eseguire all'avvio del programma:

System.Threading.Thread.CurrentThread.CurrentCulture =
    System.Globalization.CultureInfo.InvariantCulture;

Come già sappiamo, oltre ai metodi matematici, la classe Math definisce anche due costanti ben note in matematica: la costante trigonometrica \pi e il numero di Eulero e. Ecco un esempio del loro utilizzo:

Console.WriteLine(Math.PI);
Console.WriteLine(Math.E);

Eseguendo il codice otteniamo il seguente output:

3.141592653589793
2.718281828459045

La classe System.Random

A volte in programmazione è necessario usare numeri casuali.

Per esempio, potremmo voler generare 6 numeri casuali nell'intervallo da 1 a 90 (non necessariamente distinti). Ciò può essere fatto tramite la classe System.Random e il suo metodo Next(). Prima di usare la classe Random dobbiamo crearne un'istanza, che viene inizializzata con un valore casuale (derivato dall'ora di sistema corrente nel sistema operativo).

Dopo di ciò possiamo generare un numero nell'intervallo [0,n) chiamando il metodo Next(n). Si noti che questo metodo può restituire zero, ma restituisce sempre un numero inferiore al valore impostato n. Pertanto, se vogliamo un numero nell'intervallo [1,90], dobbiamo usare l'espressione Next(90) + 1.

Di seguito un esempio di sorgente che genera 6 numeri casuali nell'intervallo [1,90] usando la classe Random (si noti che non è garantito che i numeri siano unici come nel gioco del lotto):

class RandomNumbersBetween1And90
{
    static void Main()
    {
        Random rand = new Random();
        for (int number = 1; number <= 6; number++)
        {
            int randomNumber = rand.Next(90) + 1;
            Console.Write("{0} ", randomNumber);
        }
    }
}

Ecco un possibile output del programma:

16 90 7 29 1 28

Esempio: Generazione di una password casuale

Per mostrare quanto possa essere utile il generatore di numeri casuali nel .NET Framework, ci proponiamo di generare una password casuale lunga tra 8 e 15 caratteri, che contenga almeno due lettere maiuscole, almeno due lettere minuscole, almeno una cifra e almeno tre caratteri speciali.

A tale scopo useremo il seguente algoritmo:

  1. Iniziamo con una password vuota. Creiamo un generatore di numeri casuali.
  2. Generiamo due lettere maiuscole casuali e le inseriamo in posizioni casuali della password.
  3. Generiamo due lettere minuscole casuali e le inseriamo in posizioni casuali della password.
  4. Generiamo una cifra casuale e la inseriamo in una posizione casuale della password.
  5. Generiamo tre caratteri speciali casuali e li inseriamo in posizioni casuali della password.
  6. A questo punto la password dovrebbe avere 8 caratteri. Per portarla fino a un massimo di 15 caratteri, possiamo inserire un numero casuale di volte (tra 0 e 7) in una posizione casuale della password un carattere casuale (lettera maiuscola, lettera minuscola o carattere speciale).

Un'implementazione dell'algoritmo descritto è riportata qui sotto:

class RandomPasswordGenerator
{
    private const string CapitalLetters =
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    private const string SmallLetters =
        "abcdefghijklmnopqrstuvwxyz";
    private const string Digits = "0123456789";
    private const string SpecialChars =
        "~!@#$%^&*()_+=`{}[]|\\:;,.?<>";

    private const string AllChars =
        CapitalLetters + SmallLetters + Digits + SpecialChars;

    private static Random rnd = new Random();

    static void Main()
    {
        StringBuilder password = new StringBuilder();

        // Genera due lettere maiuscole casuali
        for (int i = 1; i <= 2; i++)
        {
            char capitalLetter = GenerateChar(CapitalLetters);
            InsertAtRandomPosition(password, capitalLetter);
        }

        // Genera due lettere minuscole casuali
        for (int i = 1; i <= 2; i++)
        {
            char smallLetter = GenerateChar(SmallLetters);
            InsertAtRandomPosition(password, smallLetter);
        }

        // Genera una cifra casuale
        char digit = GenerateChar(Digits);
        InsertAtRandomPosition(password, digit);

        // Genera tre caratteri speciali
        for (int i = 1; i <= 3; i++)
        {
            char specialChar = GenerateChar(SpecialChars);
            InsertAtRandomPosition(password, specialChar);
        }

        // Genera alcuni caratteri casuali aggiuntivi (tra 0 e 7)
        int count = rnd.Next(8);
        for (int i = 1; i <= count; i++)
        {
            char specialChar = GenerateChar(AllChars);
            InsertAtRandomPosition(password, specialChar);
        }

        Console.WriteLine(password);
    }

    private static void InsertAtRandomPosition(
        StringBuilder password, char character)
    {
        int randomPosition = rnd.Next(password.Length + 1);
        password.Insert(randomPosition, character);
    }

    private static char GenerateChar(string availableChars)
    {
        int randomIndex = rnd.Next(availableChars.Length);
        char randomChar = availableChars[randomIndex];
        return randomChar;
    }
}

Spieghiamo ora alcuni punti che potrebbero non essere chiari nel codice sorgente. Cominciamo dalla definizione delle costanti:

private const string CapitalLetters =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private const string SmallLetters =
    "abcdefghijklmnopqrstuvwxyz";
private const string Digits = "0123456789";
private const string SpecialChars =
    "~!@#$%^&*()_+=`{}[]|\\:;,.?<>";

private const string AllChars =
    CapitalLetters + SmallLetters + Digits + SpecialChars;

Le costanti in C# sono variabili immutabili i cui valori vengono assegnati durante l'inizializzazione nel codice sorgente del programma e, dopo di ciò, non possono più essere modificati. Sono dichiarate con il modificatore const. Si usano per definire un numero o una stringa che verranno poi riutilizzati molte volte nel programma. In questo modo si evita la ripetizione di determinati valori nel codice e questi valori possono essere modificati facilmente cambiando un solo punto del codice.

Ad esempio, se a un certo punto decidiamo che il carattere “,” (virgola) non debba essere impiegato nella generazione di una password, possiamo modificare una sola riga (la costante corrispondente) e il cambiamento si rifletterà ovunque la costante venga usata.

In C# le costanti sono scritte in PascalCase (le parole del nome, unite tra loro, iniziano ognuna con una lettera maiuscola e il resto in minuscolo). Impareremo di più sulle costanti nelle prossime lezioni quando parleremo della definizione di classi e oggetti.

Spieghiamo ora come funzionano le altre parti del programma. All'inizio, come variabile membro statica della classe RandomPasswordGenerator viene creato il generatore di numeri casuali rnd. Poiché rnd è definito nella classe (e non nel metodo Main()), è accessibile da tutta la classe (da ciascuno dei suoi metodi) e, essendo dichiarato static, è utilizzabile anche dai metodi statici. Così, ovunque il programma necessiti di un intero casuale, viene impiegato lo stesso generatore di numeri casuali, inizializzato al caricamento della classe RandomPasswordGenerator.

Il metodo GenerateChar() restituisce un carattere scelto casualmente in un insieme di caratteri passato come parametro. Funziona in modo molto semplice: seleziona una posizione casuale nell'insieme (tra 0 e il numero di caratteri – 1) e restituisce il carattere a quell'indice.

Anche il metodo InsertAtRandomPosition() non è complicato: sceglie una posizione casuale nell'oggetto StringBuilder ricevuto e vi inserisce il carattere restituito. Presteremo particolare attenzione alla classe StringBuilder quando parleremo delle stringhe in C#.

Ecco un output di esempio del programma di generazione delle password appena analizzato (l'output varia a ogni esecuzione, per sua natura casuale):

8p#Rv*Y1{tN4