Se c'è una cosa che odio e che trovo noiosa è
implementare l'interfaccia ICloneable sui propri business
object. Nulla di complicato, parecchio di noioso, e molto, anche. In fin dei
conti si tratta di creare un solo metodo Clone, di creare un
nuovo oggetto dello stesso tipo della classe in cui ci si trova, assegnare una
ad una tutte le proprietà e ritornare un oggetto con la classica return.
Perchè non farlo fare al nostro bravo (ed estensibile) class designer?
L'estensibilità del class designer di VS2005
Mi sono ispirato all'articolo scritto dal
nostro Marco
e pubblicato su UGIdotNET. Mi sono
cimentato anche io e credo di aver raggiunto un primo risultato che voglio farvi
vedere per ricevere commenti, critiche e migliorie. Non sto qui a dilungarmi
troppo, d'altronde Marco l'ha spiegato più che bene. Lo dirò solo in breve per
chi capita qui per caso, per tutti gli altri documentatevi seguendo l'ottimo
articolo già citato prima.
Introduzione
Si tratta di creare una classe che eredita
da ClassDiagramCommand e di fare l'override di due metodi:
OnInvoke e OnUpdateStatus. Quest'ultimo serve
per attivare o no il nostro comando nuovo che, come nell'articolo, viene
aggiunto al menù Add del class designer che trovate quando cliccate con il
pulsante destro su un membro. Nel mio caso, il comando si attiva quando
cliccate su una proprietà nel class designer. Quindi:
protected override void OnUpdateStatus()
{
this.Visible = this.Enabled = false;
if (Selection.SelectedObjects.Count == 1)
{
selectedProperty = Selection.SelectedObjects[0] as ClrProperty;
if (selectedProperty != null)
this.Visible = this.Enabled = true;
}
}
Attraverso l'oggetto Selection rilevo quanti oggetti
ho selezionato. Se ne ho selezionato uno solo, e il cast a
ClrProperty mi dà un riferimento diverso da null, allora attivo il comando. Fare l'override del metodo
OnInvoke è molto più
interessante, perchè ci permette fisicamente di scrivere codice
che attraverso un object model dedicato (piuttosto ricco ed articolato, devo dire) modifica
il codice sottostante, creando quindi il nuovo metodo Clone() e via dicendo.
Vediamo nel dettaglio:
protected override void OnInvoke()
{
selectedType = Selection.FindFirst<ClrProperty>().ClrType;
bool ret = selectedType.ImplementsInterface(INTERFACE_NAME);
if(ret)
{
MessageBox.Show("This class already implements ICloneable interface!", "Class Designer Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
else
{
// Qui modifico la classe
}
}
L'oggetto Selection espone un metodo
FindFirst che fa uso di generics. Nel codice qui sopra, faccio
una cosa molto semplice: la selezione è su una proprietà (by-design, è stata una
scelta mia), quindi da questa ottengo un riferimento al tipo (proprietà
ClrType). Il tipo in questo caso non è nient'altro che la classe
a cui appartiene la proprietà selezionata. L'oggetto selectedType è quindi di
tipo ClrType. Detto questo, controllo attraverso il
metodo ImplementsInterface se la classe implementa già
l'interfaccia ICloneable. Se mi ritorna true, allora
avviso l'utente e la finiamo qui. Altrimenti, proseguiamo con tutto il
resto.
Creiamo il codice del metodo Clone
Una volta ottenuto il
riferimento al type su cui stiamo lavorando, aggiungere il metodo è di per sè
molto semplice. L'object model ci permette di fare tutto senza grossi
problemi:
ClrMethod m = selectedType.AddClrMethod(METHOD_NAME);
Selection.DocData.Synchronize();
La seconda riga allinea il file .cs della classe con il metodo
Clone appena creato. In questo metodo, l'implementazione di
questo metodo non fa altro che sollevare un'Exception. Andiamo oltre. Ottengo un
riferimento ad un oggetto CodeElement che punti al metodo
Clone: per farlo faccio un loop e controllo la proprietà
Name di ciascun CodeElement.
Per scrivere realmente il codice di Clone, ho scritto una function privata
createMemberCode, che fra le altre cose fa quanto segue:
- dichiara un oggetto ret che verrà ritornato dal metodo
- ret è dello stesso tipo della classe di cui stiamo implementando
l'interfaccia ICloneable
- fa un loop su tutte le proprietà definite nella classe e le riassegna
all'oggetto ret, ottenendo così un clone vero e proprio
- infine, faccio una semplice return(ret);
Fatto questo, fatto tutto. Ci sono alcune cose che devo mettere a
posto. Prima cosa: se la
classe implementa ICloneable, la sua dichiarazione deve
indicarlo, cosa che per adesso non riesco a fare. Difatti, la MessageBox di
conferma avvisa lo sviluppatore che deve scrivere a mano ": ICloneable" nella
dichiarazione della classe. Seconda
cosa: se aggiungiamo una proprietà alla classe dopo aver
implementato l'interfaccia, il metodo Clone non è più allineato
e quindi la nuova proprietà non viene clonata. Terza
cosa: il clone non fa viene creato tramite una
deep-copy: se una proprietà è a sua volta un oggetto di un'altra
classe, devo chiamare la relativa Clone. Quarta
cosa: tutto quello che adesso non mi viene
in mente.
Download dei sorgenti
Il download dei sorgenti è disponibile qui. E' sufficiente compilarlo e copiare i
files ICloneableInterface.* nella directory
%APP_DATA%\Microsoft\MSEnvShared\Addins. Lo miglioriamo
insieme?