Data: Agosto-2002
Autore: (luKa)
Ambiente: .NET Framework v1.0
Download Esempi
Introduzione
Il .NET Framework dispone di un'ampia varietà di collezioni
eppure esistono situazioni in cui è conveniente implementarsi una propria
collezione, questo tutorial guida all'implementazione consapevole di collezioni
personalizzate.
L'implementazione di nuove collezioni viene descritto in modo estremamente
pratico: il tutorial è da leggere tenendo il codice degli esempi
citati costantemente a portata di mano per poterlo ispezionare ed eseguire
passo-passo.
Ciò a cui punta il tutorial è spiegare il
perché
lasciando alla documentazione di riferimento ufficiale approfondire
il
come.
Una volta letto il tutorial e compreso gli esempi si sarà in
grado di determinare quando è conveniente implemetare una propria
collezione, scegliere la tecnica di implementazione adatta e quindi procedere
all'implementazione.
Perché implementare una collezione
Il .NET Framework mette a disposizione collezioni, che fanno uso
di algoritmi efficienti, con una varietà tale da soddisfare le
esigenze di chi privilegia tempi di accesso ottimi, di chi privilegia
tempi di inserimento/modifica ottimi e di chi privilegia il minimo consumo
di memoria.
Ma se il .Net Framework è la panacea per tutti i nostri
mali, perché spendere tempo ed energie per implementare altre
collezioni???
Ci sono due ragioni per cui uno sviluppatore può desiderare
di implementare una sua collezione:
- Per implementare un oggetto di Business contenitore
Nella implementazione di oggetti che appartengono al livello di
astrazione applicativo spesso è necessario implementare un oggetto
che è anche un contenitore; in UML ciò viene modellato come
una relazione di aggregazione, di contenimento o una relazione semplice
mentre in concreto si può pensare alle relazioni tra Ordini e Ordine,
tra Ordine e RigaOrdine e tra Cliente e Condizioni di Pagamento.
Dall'oggetto contenitore si vuole poter accedere ad un elemento,
ciclare tutti gli elementi ed aggiungere o rimuovere elementi, cioè
ciò che fa una collezione, ma una collezione che deve operare
esclusivamente con elementi di un determinato tipo (per esempio RigaOrdine)
e allo stesso tempo fornire altri servizi (per esempio la persistenza,
la ricerca con condizioni di filtro o specifiche regole di Business).
- Per implementare una collezione di Value-Type efficente
Le collezioni fornite dal .Net Framework possono essere utilizzate
per contenere elementi di qualsiasi tipo. A questo scopo il tipo utilizzato
per rappresentare un elemento nei metodi delle collezioni è appunto
Object che è la radice di tutti i tipi dato. Tuttavia Object è
anche un Reference-Type (quei tipi dato che vengono passati come parametro
o assegnati per riferimento), quindi se gli elementi inseriti nella collezione
sono Value-Type (quei tipi dato che se passati come parametro o
assegnati vengono copiati per valore) avverranno continue operazioni di Boxing
e Unboxing (cioè le conversioni necessarie per poter trattare i
Value-Type come oggetti) con sensibile peggioramento dei tempi di elaborazione.
Quindi le collezioni fornite dal .Net Framework potrebbero non essere indicate.
Implementare una collezione con l'ereditarietà
Un modo per implementare una collezione personalizzata in C# è
quello di sfruttare l'ereditarietà a partire da classi base opportunamente
fornite dal Framework nel namespace System.Collections e System.Collections.Specialized.
Le classi base
astratte forniscono già l'implementazione
di tutti i metodi necessari, mentre per i metodi di tali classi ecco
la casistica:
- i metodi che sono virtuali permettendo al programmatore
di farne l'Override per personalizzarne l'implementazione come
desiderato;
- oppure i metodi della classe base sono implementati esplicitamente
su un'altra interfaccia lasciando allo sviluppatore la possibilità
di implementare la versione tipizzata sull'interfaccia principale;
- oppure è necessario che il programmatore faccia l'hide
di un metodo indicando il modificatore new per tale metodo.
La collezione così implementata potrà essere fortemente
tipizzata e anche fornire ogni altro servizio desiderato, per esempio
la persistenza, la ricerca con condizioni di filtro, il vincolo a specifiche
regole di Business, etc.
Segue una lista delle classi base e relativi ambiti di utilizzo:
Si vedano i seguenti esempi:
-
COLLECTIONBASE.CS
che eredita da CollectionBase.
E' disponibile un'utility per generare automaticamente il
codice:
CollGen.zip per la Beta2
e
CollGen.zip per la Rel 1 (in locale)
-
READONLYCB.CS
che eredita da ReadOnlyCollectionBase.
-
DICTIONARYBASE.CS
che eredita da DictionaryBase.
Implementare una collezione con il contenimento
Un altro metodo per implementare una collezione consiste nel contenimento
ossi implementare autonomamente tutti i metodi necessari alla collezione
(senza ereditarli) utilizzando al proprio interno le strutture dati utili
a facilitare tale collezione. Per esempio un Array tipizzato o una collezione
non tipizzata tra quelle fornite dal Framework.
I metodi da implementare sono determinati dalle interfacce che una collezione
deve necessariamente implementare per essere una collezione. In particolare
tutte le collezioni devono implementare l'interfaccia
IEnumerable
che può essere estesa da
ICollection. Si distinguono
inoltre i due gruppi di collezioni che implementano due interfacce
derivate da
ICollection:
IDictionary e
IList
.
Per supportare la creazione di collezioni per contenimento è disponibile
l'Add-In di VS.NET open-source .NET CollectionGen.
Notizie sull'Add-In sono disponibili in:
http://www.sellsbrothers.com/tools/default.aspx
È possibile scaricare l'Add-In da:
http://www.sellsbrothers.com/tools/collectiongen.zip
(in locale)
A proposito delle collezioni implementate per contenimento si vedano gli
esempi:
-
INNERARRAY.CS
mostra l'implemetazione fornita dall'Add-In di una collezione di tipo IList
con un Array tipizzato.
-
INNERHASH.CS
mostra l'implemetazione fornita dall'Add-In di una collezione di tipo IDictionary
con una HashTable.
Scegliere tra le due tecniche di implementazione
Nella maggior parte delle situazioni l'implementazione per ereditarietà
è sufficiente, vi sono comunque situazioni in cui è necessaria
l'implementazione per contenimento.
In generale la relazione di l'ereditarietà è preferibile
per relazioni strutturali e immutabili nel tempo del tipo Is-A. Mentre una
relazione di contenimento (che può nascondere il tipo contenuto) è
preferibile per relazioni non strutturali del tipo Has-A e quindi è
più flessibile. Segue un breve confronto tra le due tecniche applicate
all'implementazione di collezioni:
- Con l'ereditarietà è necessario scrivere meno codice
- Con il contenimento è possibile migliorare le prestazioni
impiegando internamente un Array tipizzato e quindi evitare Boxing e Unboxing
dei Value-Type
- Il contenimento è più flessibile in quanto permette
per esempio che una classe implementi 2 collezioni su due interfacce diverse
- Il contenimento protegge da eventuali modifiche
future che MS
potrebbe (non dovrebbe ma...) apportare all'interfaccia della classe base
Sintetizzando si ottiene questo albero di decisione:
- Se si deve implementare una collezione di Value-Type (structure
comprese) in cui le prestazioni sono determinanti usale il contenimento
- altrimenti... se è necessario implementarte due o più
collezioni nella medesima classe usare il contenimento
- altrimenti... usare l'ereditarietà
Nel dubbio... usare l'ereditarietà.