C#

GLI ENUMERATIVI

Argomento di questo paragrafo è un tipo di dato un po' particolare. Esso è infatti costituito da una coppia che permette di attribuire un nome ad uno o più valori numerici. E' una cosa un po' strana, in apparenza, ma in realtà risulta assai utile nel linguaggio tanto che all'interno del framework .Net stesso tale tipologia di dati è molto sfruttata. Alternativamente potete pensare a questo tipo come rappresentato da un insieme di coppie nome - valore, senza confondervi però con le hash table. A mio avviso la prima definizione è la più corretta, anche perchè stabilisce correttamente lo scopo del tipo. A livello espressivo è molto interessante questa possibilità che è prevista infatti in molti altri linguaggi. La leggibilità e la manutenibilità vi ringrazieranno. Inoltre, il compilatore sottopone a rigidi controlli le operazioni effettuate tramite gli enumerativi (o enums, nella dizione ufficiale) che sono tipi fortemente tipizzati e gestiti come "first class citizens" il che conferisce loro uno status che li rende, di fatto, molta potenti in termini di possibilità d'uso. Per qualche motivo, molti programmatori non professionisti snobbano un po' questo tipo di dati. Spesso mi sento dire che "in realtà non servono", il che mi pone dei dubbi sull'effettivo skill dell'interlocutore. Un enumerativo non è un enumeratore, ovvero un oggetto che implementa l'interfaccia IEnumerable e che vedremo in apposita sezione. Concludo questa breve presentazione segnalando che alla base di questo tipo c'è un classe, non la solita struct.
La parola chiave che definisc questo tipo è enum e la costruzione base si può fare ad esempio così:

public enum nome-enumerativo [: tipo]
{
Elemento-1 [= valore],
Elemento-2 [= valore],
...,
Elemento-n [= valore][,]
}

Ciò che sta tra parentesi quadrate è opzionale. Il separatore degli elementi interni è la virgola, come si vede, che può essere omessa dopo ultimo elemento, come nell'esempio. Il punto principale è che a ciascun elemento è abbinato, implicitamente o esplicitamente, come vedremo, un numero che può essere di uno dei seguenti tipi: byte, sbyte, short, ushort, int, uint, long, o ulong. Il tipo di deafult è int e il valore abbinato al primo elemento, se non diversamente specificato come vedremo tra breve, è 0.
Tornando alla nostra definizione formale con un esempio, la visione completa di esso è quella del piccolo programma seguente, con i valori numerici esplicitati che corrispondono al default imposto al tipo:

  Esempio 9.1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using System;

public enum Direzione
{
  Nord = 0,
  Sud = 1,
  Est = 2,
  Ovest = 3
}

class Test
{
  public static void Main()
  {
    Direzione dir1 = Direzione.Nord;
    Console.WriteLine("Direzione di marcia: " + dir1);
    Console.WriteLine("Indice della direzione: " + (int)dir1);
  }
}


Come da definizione formale, l'attribuzione dei valori potrebbe non essere esplicitata. Dalla riga 3 alla 9 abbiamo la definizione del nostro enumeratore che viene poi invocato alla 15, come è evidente. Proprio quest'ultima riga può lasciare un po' perplessi... ma che ogni elemento sia di tipo "Direzione" è facilmente comprovabile se stamperete
dir1.GetType().
Ne parleremo ancora ma, come prima cosa, chiudiamo il discorso relativo ai valori numerici accettati.
Un primo modo di modificare il comportamento di default è attribuire un valore di partenza, ad esempio ridefinendo l'enumeratore dell'esempio 9.1 in questo modo:

public enum Direzione
{
Nord = 4,
Sud,
Est,
Ovest
}


al che automaticamente il codice

Direzione dir1 = Direzione.Ovest;
Console.WriteLine("Indice della direzione: " + (int)dir1);

esporrà a video il numero 7, in quanto Nord sarà abbinato al numero 4, Sud al 5, Est al 6, preseguendo sequenzialmente dal valore iniziale.
Possiamo anche definire un valore per ogni elemento:

public enum Direzione
{
Nord = 4,
Sud = 9,
Est = 0,
Ovest = 44
}

E ci spuò anche sbizzarrire, anche perchè non è obbligatorio assegnare un ordine crescente ai numeri:

public enum Direzione
{
Nord = 4,
Sud,
Est = 7,
Ovest
}

Come si può vedere se proverete ad utilizzare la definizione appena specificata, ogni elemento ha in abbinamento un valore sequenziale a quello specificato, implicitamente o esplicitamente, per l'elemento che lo precede. Insomma fate le vostre prove. E' possibile attribuire valori numerici uguali ma gli identificatori a sinistra devono essere sempre diversi. Ovvero:

SI NO
public enum Direzione
{
Nord = 6,
Sud = 6,
Est = 6,
Ovest = 6
}
public enum Direzione
{
Nord,
Nord,
Est,
Ovest
}

L'esempio di sinistra funziona quello di destra no a causa della presenza di due voci "Nord". Ovviamente non è buona pratica avere valori numerici uguali all'interno di un enumerativo in quanto tali valori possono essere usati per recupare gli identificativi corrispondenti; in caso di valori uguali non siamo in grado di prevedere quale identificativo otterremo come risultato e il compilatore non può aiutarci in questo senso.
Per quanto riguarda il tipo dei numeri che vogliamo usare possiamo specificarlo nella definizione stessa dell'enumerativo. In pratica il default si presenterebbe, se esplicitato così:

public enum Direzione : int

altrimenti, volendo ad esempio usare il tipo byte, dovremmo scrivere:

public enum Direzione : byte

ovviamente attenzione ai valori che devono rispettare il range definito dal tipo. Ad esempio:

public enum Direzione : byte
{
Nord = 6,
Sud = 6,
Est = 6,
Ovest = 1000
}

La definizione è formalmente corretta ma il programma non compila perchè il valore 1000 attribuito ad Ovest è al fuori del range dei byte.

Nel programma 9.1, alla riga, abbiamo visto il richiamo di un enum. Questa impostazione è un po' particolare e per comprenderla, sia pure in modo un po, come dire, "maccheronico", dovete pensare ad essa come un contenitore; definendo un enumerativo noi creiamo un contenitore all'interno del quale infiliamo delle coppie valore - nome raggruppate dal nome attribuito all'enumerativo definito. Nel nostro esempio, il 9.1, "Direzione" è il nome che raggruppa le coppie nome = valore in definito dopo di esso, e ciascun elemento è distinto dagli altri tramite ciò che appare alla sinistra, ecco perchè non ve ne possono essere due uguali. Con un po' di pratica di verrà naturale usarli.
Vedremo tra poco un elenco di metodi e proprietà degli enumerativi ma vorrei anticiparne un paio che saranno probabilmente i più utili nella pratica: GetNames e GetValues, il cui uso è abbastanza intuitivo e permettono di recuperare gli identificativi e i relativi valori. Vediamo il semplice esempio seguente:

  Esempio 9.2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using System;

public enum Direzione : byte
{
  Nord = 1,
  Sud = 8,
  Est = 9,
  Ovest = 0
}

class Test
{
  public static void Main()
  {
    foreach(String s in Enum.GetNames(typeof(Direzione)))
    Console.Write(s + " ");
    Console.WriteLine();
    foreach(byte s in Enum.GetValues(typeof(Direzione)))
    Console.Write(s + " ");
  }
}

L'output è interessante:

Ovest Nord Sud Est
0 1 8 9

Notate qualcosa riguardo all'ordinamento? Esatto, segue i valori numerici.
La cosa più interessante però è senz'altro risalire all'identificativo partendo da un valore e viceversa. Vediamo come si può fare:

  Esempio 9.3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
using System;

public enum Day_Of_Week
{
Lunedi = 1,
Martedi,
Mercoledi,
Giovedi,
Venerdi,
Sabato,
Domenica
}

class Test
{
  public static void Main()
  {
    Console.Write("Scegli il giorno: 1-7: ");
    int x = int.Parse(Console.ReadLine());
    Day_Of_Week giorno = (Day_Of_Week)x;
    switch(giorno)
    {
      case Day_Of_Week.Lunedi: Console.WriteLine("Che schifo...");
      break;
      case Day_Of_Week.Martedi: Console.WriteLine("Mamma mia...");
      break;
      case Day_Of_Week.Mercoledi: Console.WriteLine("Non finisce mai...");
      break;
      case Day_Of_Week.Giovedi: Console.WriteLine("Stanchezza...");
      break;
      case Day_Of_Week.Venerdi: Console.WriteLine("Dai che si siamo...");
      break;
      case Day_Of_Week.Sabato: Console.WriteLine("Riposo...");
      break;
      case Day_Of_Week.Domenica: Console.WriteLine("Sport!...");
      break;
      default: Console.WriteLine("Sei cotto eh? Da 1 a 7....");
      break;
    }
  }
}

Nell'esempio qui sopra entra anche il concetto di cast, che incontreremo altrove. La riga critica è la 20.
Se invece volessimo effettuare l'input considerando il "nome" allora l'sitruzione sarebbe analoga, anche se all'apparenza un po' più complessa:

Day_Of_Week giorno = (Day_Of_week)Enum.Parse(TypeOf(Day_Of_Week), x)

Veniamo ora ad un carrellata sui principali metodi che ci offre la classe enum:

CompareTo Confronta due elementi restituendo -1, 0, 1 a seconda se sia maggiore l secondo, siano uguali, sia maggiore il primo.


Equals Booleano, restituisce true se i due elementi sono uguali, false altrimenti.


Format Converte un enumerativo in stringa seguendo un certo formato


GetName Statico, da non confondersi col già citato GetNames, recupera il nome dell'identificativo che ha un dato valore.
using System;

public enum Numeri
{
  Uno = 1,
  Due,
  Tre,
  Quattro,
}

class Test
{
  public static void Main()
  {
    Console.WriteLine(Enum.GetName(typeof(Numeri), 3));
  }
}


GetNames Visto in precedenza


GetTypeCode Restituisce il tipo base dell'enumeratore

using System;

public enum Numeri
{
  Uno = 1,
  Due,
  Tre,
  Quattro,
}

class Test
{
  public static void Main()
  {
    Numeri n = new Numeri();
    Console.WriteLine(n.GetTypeCode());
  }
}


GetUnderlyingType Come GetTyperCode restituisce, sia pure in modo un po' più contorto, il tipo base dell'enumerativo:

using System;

public enum Numeri
{
  Uno = 1,
  Due,
  Tre,
  Quattro,
}

class Test
{
  public static void Main()
  {
    Numeri n = new Numeri();
    Type tipo = n.GetType();
    Console.WriteLine(Enum.GetUnderlyingType(tipo));
  }
}


GetValues Già visto in precedenza


IsDefined Statico, bolean, indica se un certo valore appartiene all'enumerativo

using System;

public enum Numeri
{
  Uno = 1,
  Due,
  Tre,
  Quattro,
}

class Test
{
  public static void Main()
  {
    Console.WriteLine(Enum.IsDefined(typeof(Numeri), "Uno"));
  }
}


Parse Converte una stringa in un valore accettabile per l'enumerativo, come abbiamo già visto nella riga di codice che segue l'esempio 9.3


ToObject(enum, tiponumerico)
Converte un numero, di qualsiasi tipo, in formato adatto per un enumerativo