C#Programmazione

Tipi generici e collezioni

I generici permettono di definire tipi e metodi generici, per i quali è possibile specificare parametri tipo che sono segnaposto e verranno sostituiti dai nomi dei tipi da utilizzare. Le categorie di generics sono: classi, metodi,  interfacce, struct e delegate.

Classi

I tipi vengono indicati tra parentesi angolari e il nome inizia con la lettera T e il nome deve far capire il ruolo dei parametri.

I tipi generici possono essere nidificati, per esempio la Lista<T>  può essere costruita utilizzando come tipo T dei suoi elementi una Rubrica<int, string>

Possiamo semplificare la nomenclatura di tipi generici complessi utilizzando using per creare un alias:

using ListRubrica = System.Collections.Generic.List<System.Collections.Generic.Dictionary<int, string>>;

ListRubrica listaR = new ListRubrica();

Come una qualunque classe può essere ereditata, può aggiungere nuovi parametri. Specificando i tipi al posto dei parametri non è più una classe generica.

Nel costruttore della classe possiamo inizializzare  i campi utilizzando l’operatore default, che restituisce il valore predefinito di un tipo qualunque seguendo le seguenti regole:

  • per un tipo riferimento restituisce null;
  • per un tipo valore numerico restituisce zero;
  • per un tipo valore struct inizializza i membri con null o zero;

I parametri tipo possono essere vincolati in maniera che richiedono apposite caratteristiche.  Per indicare un vincolo usiamo la parola chiave where, nel caso di più parametri ognuno di essi può avere una propria clausola where senza usare dei separatori. Se invece per un parametro di tipo bisogna rispettare più vincoli li dobbiamo separare con la virgola dopo la parola where.

class Generica<T, U> where T: class where U: Interfaccia

T deve essere un tipo riferimento mentre U deve implementare l’interfaccia specificata.

La classe figlia, ereditata dalla classe madre, deve indicare i vincoli della classe madre.

La ListaFiglia eredita il parametro e lo rinomina, il vincolo AltraClasse è compatibile con quello della classe Lista in quanto è più stringente.

Metodi

Sono usati quando si vuole rendere generico un algoritmo. I parametri tipo vengono indicati fra parentesi angolari dopo il nome del metodo e prima della lista dei parametri formali. In questo esempio il metodo Swap scambia due oggetti di un dato tipo T.

Anche per i metodi possiamo definire i vincoli utilizzando la clausola where, come per le classi.

Interfacce

Consentono di scrivere interfacce in cui le firme dei metodi che esse definiscono contengono parametri di tipo, la sintassi è uguale alle precedenti. Definiamo un’interfaccia che scambia due parametri:

Possiamo utilizzare l’interfaccia in una classe generica:

Oppure utilizzarla per una o più classi non generiche:

Struct

Anche le strutture possono essere implementate in maniera generica, seguendo le regole già viste.

Per quanto riguarda i Delegate sarà approfondito in un capitolo a parte.

Covarianza e controvarianza

sono due concetti che descrivono le conversioni di tipo e viceversa. Possiamo definire varianti una interfaccia generica o un delegate generico se il parametro di tipo è dichiarato covariante o controvariante.

Ad esempio IEnumerable<T> è una interfaccia covariante, IEnumerable<string> è convertibile in IEnumerable<object> in quanto string è derivato da object.

Possiamo marcare i parametri tipo di interfacce e delegate generici, la parola chiave out serve per la covariante mentre in per la controvariante.

Collezioni in .NET

le principali interfacce messe a disposizione sono:

  • IEnumerable<T> – funzionalità per enumerare gli elementi;
  • ICollection<T> – funzionalità per manipolare collezioni;
  • IList<T> – funzionalità per l’accesso diretto agli elementi;
  • IReadOnlyList<T> – versione a sola lettura di IList;
  • IDictionary<TKey, TValue> – implementare collezioni di coppie chiave valore;
  • IReadOnlyDictionary<K, V> – la versione a sola lettura di IDictionary;
  • ISet<T> – insiemi che possono contenere oggetti distinti;

Supponiamo che vogliamo scorrere un insieme di elementi

La classe che incapsula al suo interno un ogetto prevede all’interno il metodo GetEnumerator per restituire una sua istanza

Ed infine una classe che implementa l’interfaccia IEnumerator dove poter tener traccia dell’oggetto tramite una sorta di cursore.

Tramite gli iteratori possiamo realizzare l’esempio precedente in modo più semplice e flessibile, viene utilizzato una o più istruzioni yield.

Se si ha la necessità di gestire strutture di dati che contengono oggetti di tipo differente possiamo usare la classe Tuple. Esistono otto differenti versioni, da uno a sette elementi mentre l’ottava versione prevede sette elementi e l’ottavo elemento permette di innestare un’altra tuple.