Technology Experience

Contenuti gestiti da Igor Damiani
posts - 949, comments - 2741, trackbacks - 15120

My Links

News

  • Questo blog si propone di raccogliere riflessioni, teoriche e pratiche, su tutto quello che riguarda il world-computing che mi sta attorno: programmazione in .NET, software attuale e futuro, notizie provenienti dal web, tecnologia in generale, open-source.

    L'idea è quella di lasciare una sorta di patrimonio personale, una raccolta di idee che un giorno potrebbe farmi sorridere, al pensiero di dov'ero e cosa stavo facendo.

    10/05/2005,
    Milano

Archives

Post Categories

Generale

[70-536, #07] Intro ai Generics: riflessioni e considerazioni sul tema

Prologo
Lo dico onestamente: mi sono preso il mio tempo per studiare i generics, ma non ho ancora avuto modo di utilizzarli praticamente sul campo. Io personalmente consiglio questo documento tratto da MSDN: sono 45 pagine stampate, ed illustra vantaggi dei generics, come creare classi generiche, come porre vincoli, come si comporta Reflection, cosa vuol dire avere method e delegate generici, e così via.

Quindi, piuttosto che affrontare un argomento di questo calibro con così poca esperienza, preferisco ritardarlo e scrivere qualcosa più avanti, quando avremo già visto altri concetti più pratici e quando avremo un bel blocco di C# funzionante e che compila correttamente. Una intro voglio farla comunque, soprattutto per chi oggi non ha ancora preso in mano VS2005 e magari non sa di cosa si tratta e vuole partire da zero. Speriamo di riuscire ad incuriosirvi e a suscitare un po' di interesse verso la programmazione generica.

Introduzione
I generics ci permettono di scrivere classi generiche, ovvero che potenzialmente possono lavorare su un tipo di dati che non è definito all'interno della classe stessa, quanto invece dalla classe che na fa uso. Il documento che ho preso come riferimento fa un esempio molto semplice, quanto efficace.

Supponiamo di voler implementare una classe Stack, con i classici metodi Push(x) e Pop(). In questo caso, abbiamo due diverse alternative, a seconda del tipo di dati che vogliamo gestire con la nostra classe. Vediamoli con attenzione e capiamo quali sono vantaggi e svantaggi:

  1. Io non so a priori quali oggetti andrò a mettere nello stack. In questo caso, al suo interno la classe fa uso di una struttura object[]: chiamando il metodo Push(x) un certo numero di volte, posso memorizzare la prima volta un numero, poi una stringa, poi una classe e così via. Il metodo Pop() ritorna anch'esso un object, proprio perchè tecnicamente nello stack potrei avere di tutto.
  2. Io so che nel mio stack andrò a mettere soltanto un tipo di dati specifico, ad esempio int. Bene: l'implementazione farà uso di un int[], il metodo Push consentirà solamente l'inserimento di int, e il metodo Pop() ritornerà sempre e soltanto oggetti int.

Diciamo subito che dal punto di vista delle prestazioni, il metodo (2) è sicuramente più efficace. Il compilatore non ha a che fare con object generici, ma sa che ha a che fare con int. Quindi, in fase di compilazione vengono verificati i tipi (strong-typed), ci viene in aiuto l'Intellisense, le performance sono ottimali. Nel primo caso invece dobbiamo continuamente castare al tipo di dati corretto (casting a tutto andare, con boxing/unboxing continui), e di conseguenza potremmo avere exception a run-time che per definizione non siamo riusciti a prevenire durante la scrittura del codice.

Adesso facciamo un piccolo esperimento: riprendete i concetti espressi dalla soluzione (2) ed immaginatevi il codice C# corrispondente. Immaginatevi un int[], un metodo void Push(int Value) ed un metodo int Pop(). Ora, fate un bel replace di int con una bella T. Cosa abbiamo ottenuto? Semplice: la nostra classe Stack è diventata generica, ovvero è in grado di gestire qualsiasi tipo di dato previsto da .NET. Avremo un T[], avremo un metodo void Push(T value) ed un metodo T Pop(). T è in pratica un placeholder, che verrà sostituito in fase di compilazione con il tipo di dati effettivo.

public class Stack<T>
{
    T[] oggetti;
    
    
public void Push(T elemento)
        { }
        
    
public T Pop()
        { }
}

Il tipo di dato reale viene stabilito dal client di questa classe Stack: in fase di dichiarazione, possiamo scrivere una cosa del tipo:

Stack<string> stack = new Stack<string>();
Stack<Book> stack2 = 
new Stack<Book>();
Stack<
int> stack3 = new Stack<int>();

Oltre ai vantaggi citati prima, possiamo dedurne subito un altro: la classe Stack è fisicamente soltanto una. Nei nostri progetti VS2005, avremo un solo file Stack.cs, che ha la capacità di gestire - in base a come viene dichiarata - un tipo piuttosto che un altro. Se trovassimo un bug, o se volessimo modificare la classe, lo faremmo in un punto solo. Nel metodo (2) citato sopra, avremmo avuto una classe per ogni tipo di dato, il che significa duplicazione di codice, scarsa manutenzione, etc. etc.

Generico è bello, ma troppo generico stroppia!
Sembra uno scioglilingua.
Un'idea che mi sono fatto, confermata dalle ricerche che ho fatto su google, è che lo scopo principale dei Generics è quello di creare classi per l'implementazione delle strutture dati di base (stack, queue, tree, list, collection, etc.): classi che per loro natura possono e devono essere utili indipendentemente da cosa contengono, anzi...il cui scopo principale è quello di contenere.

Beh, io sarò anche un Igor qualunque, ma c'è qualcosa che non mi convince. Il "troppo generico" è troppo estremista, e ci impedisce di scrivere funzionalità davvero utili. Faccio un esempio: all'interno di una classe generica, non possiamo usare l'operatore '+', perchè è un'operatore che in alcuni casi potrebbe non funzionare, o non avere senso. Nella scrittura del codice in una classe generica, il tipo T viene visto come object, e quindi non abbiamo proprietà o metodi specifici. Mi si sono aperti gli occhi però quando ho visto l'utilizzo della keywork where, che ci permette di creare constraints (vincoli, per dirla all'italiana) sul tipo, obbligandolo per esempio a dover implementare una determinata interfaccia. Se noi sappiamo che il tipo T implementa l'interfaccia ICollection, sappiamo che dispone di una property Count, e così via per tutte le interfacce di .NET e quelle eventualmente implementate da noi nel nostro codice.

Quindi, mi piace pensare che i Generics possano in qualche modo essere utili nel caso in cui abbia un set di classi (leggesi: Book, Magazine, Newspaper, Journal) che implementano tutte una certa interfaccia (leggesi: IReadingOnPaper), e che quindi so per definizione che rispondono a determinati requisiti (leggesi: .Title, .Author, .InLoan, etc), indipendentemente dal tipo.

powered by IMHO 1.2

Print | posted on giovedì 19 gennaio 2006 14:08 | Filed Under [ Esame 70-536 ]

Feedback

Gravatar

# re: [70-536, #07] Intro ai Generics: riflessioni e considerazioni sul tema

bel post (come sempre d'altra parte) Igor!
Piccola nota. Dobbiamo iniziare a scrivere post più brevi io e te.. rischiamo di riempire sempre le prime pagine quando si posta a poca distanza :S :D
19/01/2006 14:21 | alessio.marziali
Gravatar

# [70-536, #10] Implementare le interfacce ICollection, IList e ICloneable

01/02/2006 17:17 | Technology Experience
Comments have been closed on this topic.

Powered by:
Powered By Subtext Powered By ASP.NET