SlideShare a Scribd company logo
 Vb.net
A1. Introduzione
Benvenuti, aspir anti pr ogr ammator i! In questa guida dalla lunghezza chiolemtr ica impar er ete cosa significa e cosa
compor ta pr ogr ammar e, e tutti i tr ucchi e gli espedienti per costr uir e solide e sicur e applicazioni.




Una veloc e panoramic a sulla programmazione
La pr ogr ammazione è quella disciplina dell'infor matica che si occupa di idear e, costr uir e e mantener e il softw ar e.
Queste sono le tr e pr incipali divisioni che si possono oper ar e all'inter no di questa speciale e affascinante br anca
dell'ingegner ia. Infatti, un buon pr ogr ammator e deve pr ima di tutto analizzar e il pr oblema, quindi pensar e a una
possibile soluzione, se esiste, e costr uir e mentalmente un'ipotetica str uttur a del softw ar e che dovr à impegnar si a
scr iver e: questa par te della pr ogettazione si chiama analisi. Successivamente, si viene alla fase più tecnica, e che
implica una conoscenza dir etta del linguaggio di pr ogr ammazione usato: in questa guida, mi occuper ò di descr iver e il
Visual Basic .NET. Una volta sviluppato il pr ogr amma, lo si deve testar e per tr ovar e eventuali malfunzionamenti (bugs)
- che, per inciso, si manifestano solo quando non dovr ebber o - e, come ultima oper azione, bisogna attuar e una
manutenzione per iodica dello stesso, od or ganizzar e un efficiente sistema di aggior namento. Inutile dir e che l'ultima
fase è necessar ia solo nel caso di gr andi applicazioni commer ciali e non cer tamente nel contesto di piccoli pr ogr ammi
amator iali.
Pr ima di iniziar e, una br eve sintesi di alcuni dettagli tecnici: i ter mini da conoscer e, e gli ambienti di sviluppo da
usar e.




Alc uni termini da c onosc ere
Co dice so r g ente o so r g ente: l'insieme di tutte le istr uzioni che il pr ogr ammator e scr ive e fa eseguir e al
pr ogr amma. Il file testuale che contiene tali istr uzioni viene esso stesso chiamato sor gente
Co m pilato r e: il softw ar e utilizzato per cr ear e il pr ogr amma finito (un eseguibile *.ex e) a par tir e dal solo codice
sor gente
Debug g er : il softw ar e usato per l'analisi e la r isoluzione degli er r or i (bugs) all'inter no di un pr ogr amma;
Par o le r iser v ate o k eyw o r ds: di solito vengono evidenziate dai compilator i in un color e diver so e sono par ole
pr edefinite intr inseche del linguaggio, che ser vono per scopi ben pr ecisi.




Ambiente di sviluppo
L'ambiente di sviluppo che pr ender ò come r ifer imento per questa guida è Visual Basic Ex pr ess 2008 (scar icabile dal
Sito Ufficiale della M icr o so ft; se si ha un pr ofilo Passpor t.NET è possibile r egistr ar e il pr odotto e ottener e una
ver sione completa). Potete comunque scar icar e Shar pDevelop da qui (vedi sezione dow nloads), un pr ogr amma gr atis e
molto buono (tr over ete una r ecensione nella sezione Sofw tar e di Pier oTofy.it r edatta da me e HeDo qui). Dato che le
ver sioni pr ecedenti della guida, dalle quali è r ipr esa la maggior anza dei sor genti pr oposti, sono state r edatte
pr endendo come esempio Visual Basic Ex pr ess 2005, potete scar icar e anche quello da qui.
A2. Classi, Moduli e Namespace

Objec t Oriented Programming
I linguaggi .NET sono orien tati agli oggetti e così lo è anche VB.NET. Questo appr occio alla pr ogr ammazione ha avuto
molto successo negli ultimi anni e si basa fondamentalmente sui concetti di astr azione, oggetto e inter azione fr a
oggetti. A lor o volta, questi ultimi costituiscono un potente str umento per la modellizzazione e un nuovo modo di
avvicinar si alla r isoluzione dei pr oblemi. La par ticolar e mentalità che questa linea di sviluppo adotta è favor evole alla
r appr esentazione dei dati in modo ger ar chico, e per questo motivo il suo par adig m a di pr ogr ammazione - ossia
l'insieme degli str umenti concettuali messi a disposizione dal linguaggio e il modo in cui il pr ogr ammator e concepisce
l'applicativo - è definito da tr e concetti car dine: l'er editar ietà, il po lim o r fism o e l'incapsulam ento . Molto pr esto
ar r iver emo ad osser var e nel par ticolar e le car atter istiche di ognuno di essi, ma pr ima vediamo di iniziar e con
l'intr odur r e l'entità fondamentale che si pone alla base di tutti questi str umenti: la classe.




Le Classi
Come dicevo, una car atter istica par ticolar e di questa categor ia di linguaggi è che essi sono basati su un unico
impor tantissimo concetto fondamentale: gli o g g etti, i quali vengono r appr esentati da classi. Una classe non è altr o che
la r appr esentazio ne - o v v iam ente astr atta - di qualco sa di co ncr eto , mentr e l'oggetto sar à una concr etizzazione
di questa r appr esentazione (per una discussione più appr ofondita sulla differ enza tr a classe e oggetto, veder e capitolo
A7). Ad esempio, in un pr ogr amma che deve gestir e una videoteca, ogni videocassetta o DVD è r appr esentato da una
classe; in un pr ogr amma per la fattur azione dei clienti, ogni cliente e ogni fattur a vengono r appr esentati da una
classe. Insomma, ogni cosa, ogni entità, ogni r elazione - per fino ogni er r or e - tr ova la sua r appr esentazione in una
classe.
Detto questo, viene spontaneo pensar e che, se ogni cosa è astr atta da una classe, questa classe dovr à anche contener e
dei dati su quella cosa. Ad esempio, la classe Utente dovr à contener e infor mazioni sul nome dell'utente, sulla sua
passw or d, sulla sua data di nascita e su molto altr o su cui si può sor volar e. Si dice che tutte queste infor mazioni sono
espo ste dalla classe: ognuna di esse, inoltr e, è r appr esentata da quello che viene chiamato m em br o. I membr i di una
classe sono tutti quei dati e quelle funzionalità che essa espone.
Per esser e usabile, per ò, una classe deve venir e pr ima dichiar ata, mediante un pr eciso codice. L'atto di dichiar ar e una
qualsiasi entità le per mette di iniziar e ad "esister e": il pr ogr ammator e deve infatti ser vir si di qualcosa che è già stato
definito da qualche par te, e senza di quello non può costr uir e niente. Con la par ola "entità" mi r ifer isco a qualsiasi cosa
si possa usar e in pr ogr ammazione: dato che le vostr e conoscenze sono limitate, non posso che usar e dei ter mini
gener ici e piuttosto vaghi, ma in br eve il mio lessico si far à più pr eciso. Nella pr atica, una classe si dichiar a così:

    1. Class [NomeClasse]
    2.     ...
    3. End Class

dove [NomeClasse] è un qualsiasi nome che potete decider e ar bitr ar iamente, a seconda di cosa debba esser e
r appr esentato. Tutto il codice compr eso tr a le par ole sopr a citate è inter no alla classe e si chiama co r po ; tutte le
entità esistenti nel cor po sono dei membr i. Ad esempio, se si volesse idealizzar e a livello di codice un tr iangolo, si
scr iver ebbe questo:

    1. Class Triangolo
    2.     ...
    3. End Class

Nel cor po di Tr iangolo si potr anno poi definir e tutte le infor mazioni che gli si possono attr ibuir e, come la lunghezza
dei lati, la tipologia, l'ampiezza degli angoli, ecceter a...




I Moduli
Nonostante il nome, i moduli non sono niente altr o che dei tipi speciali di classi. La differ enza sostanziale tr a i due
ter mini ver r à chiar ita molto più avanti nella guida, poiché le vostr e attuali competenze non sono sufficienti a un
completo appr endimento. Tuttavia, i moduli sar anno la tipologia di classe più usata in tutta la sezione A.




I Namespac e
Possiamo definir e classi e moduli come un ità fun zion ali: essi r appr esentano qualcosa, possono esser e usate,
manipolate, istanziate, dichiar ate, ecceter a... Sono quindi str umenti attivi di pr ogr ammazione, che ser vono a
r ealizzar e concr etamente azioni e a pr odur r e r isultati. I namespace, invece, appar tengono a tutt'altr o gener e di
categor ia: essi sono solo dei r aggr uppamenti "passivi" di classi o di moduli. Possiamo pensar e a un namespace come ad
una car tella, entr o la quale possono star e files, ma anche altr e car telle, ognuna delle quali r aggr uppa un par ticolar e
tipo di infor mazione. Ad esempio, volendo scr iver e un pr ogr amma che aiuti nel calcolo geometr ico di alcune figur e, si
potr ebbe usar e un codice str uttur ate come segue:

   01.   Namespace Triangoli
   02.       Class Scaleno
   03.       '...
   04.       End Class
   05.
   06.        Class Isoscele
   07.        '...
   08.        End Class
   09.
   10.       Class Equilatero
   11.       '...
   12.       End Class
   13.   End Namespace
   14.
   15.   Namespace Quadrilateri
   16.       Namespace Parallelogrammi
   17.           Class Parallelogramma
   18.           '...
   19.           End Class
   20.
   21.              Namespace Rombi
   22.                  Class Rombo
   23.                  '...
   24.                  End Class
   25.
   26.                Class Quadrato
   27.                '...
   28.                End Class
   29.           End Namespace
   30.       End Namespace
   31.   End Namespace

Come si vede, tutte le classi che r appr esentano tipologie di tr iangoli (Scaleno, Isoscele, Equilater o) sono all'inter no del
namespace Tr iangoli; allo stesso modo esiste anche il namespace Quadr ilater i, che contiene al suo inter no un altr o
namespace Par allelogr ammi, poiché tutti i par allelogr ammi sono quadr ilater i, per definizione. In quest'ultimo esiste la
classe Par allelogr amma che r appr esenta una gener ica figur a di questo tipo, ma esiste ancor a un altr o namespace
Rombi: come noto, infatti, tutti i r ombi sono anche par allelogr ammi.
Dall'esempio si osser va che i namespace categor izzano le unità funzionali, dividendole in insiemi di per tinenza. Quando
un namespace si tr ova all'inter no di un altr o namespace, lo si definisce nidificato: in questo caso, Par alleloogr ammi e
Rombi sono namespace nidificati. Altr a cosa: al contr ar io della classi, gli spazi di nomi (italianizzazione dell'inglese
name-space) non possiedono un "cor po", poiché questo ter mine si può usar e solo quando si par la di qualcosa di attivo;
per lo stesso motivo, non si può neanche par lar e di membr i di un namespace.
A3. Panoramica sul Framework .NET
Come ho spiegato nel pr ecedente capitolo, il concetto più impor tante della pr ogr ammazione ad oggetti è la classe.
Quindi, per scr iver e i nostr i pr ogr ammi, utilizzer emo sempr e, bene o male, queste entità. Ma non è possibile pensar e
che si debba scr iver e tutto da zer o: per i pr ogr ammator i .NET, esiste un vastissimo inventar io di classi già pr onte,
r aggr uppate sotto una tr entina di namespace fondamentali. L'insieme di tutti questi str umenti di pr ogr ammazione è il
Fr am ew o r k .NET, l'ossatur a pr incipale su cui si r eggono tutti i linguaggi basati sulla tecnologia .NET (di cui Vb.NET è
solo un esponente, accanto al più usato C# e agli altr i meno noti, come J#, F#, Delphi per .NET, ecceter a...). Sar ebbe
tuttavia r iduttivo descr iver e tale piattafor ma come un semplice agglomer ato di libr er ie (vedi oltr e), quando essa
contempla meccanismi assai più complessi, che sovr intendono alla gener ale esecuzione di tutte le applicazioni .NET.
L'inter a str uttur a del Fr amew or k si pr esente come str atificata in diver si livelli:




1. Sistema operativo
Il Fr amew or k .NET pr esenta una str uttur a str atificata, alla base della quale r isiede il sistema oper ativo, Window s. Più
pr ecisamente, si consider a il sistema oper ativo e l'API (Application Pr ogr amming Inter face) di Window s, che espone
tutti i metodi r esi disponibili al pr ogr ammator e per svolger e un dato compito.




2. Common Language Runtime
Un gr adino più in su c'è il Common Language Runtime (CLR), r esponsabile dei ser vizi basilar i del Fr amew or k, quali la
gestione della memor ia e la sua liber azione tr amite il meccanismo di Gar bage Collection (vedi capitolo r elativo), la
gestione str uttur ata delle eccezioni (er r or i) e il multithr eading. Nessuna applicazione inter agisce mai dir ettamente
con il CLR, ma tutte sono allo stesso modo contr ollate da esso, come se fosse il lor o super visor e. Pr opr io per questo si
definisce il codice .NET M an aged o Safe ("Gestito" o "Sicur o"), poichè questo str ato del Fr amew or k gar antisce che non
vengano mai eseguite istr uzioni dannose che possano mandar e in cr ash il pr ogr amma o il sistema oper ativo stesso. Al
contr ar io, il codice Unmanaged o Unsafe può eseguir e oper azioni r ischiose per il computer : sor genti pr odotti in Vb6 o
C++ possono pr odur r e tale tipo di codice.




3. Base Class Library
Lo str ato successivo è denominato Base Class Libr ar y (BCL): questa par te contiene tutti i tipi e le classi disponibili nel
Fr amew or k (il che cor r isponde in numer o a diver se migliaia di elementi), r aggr uppati in una tr entina di file pr incipali
(assembly). In questi ultimi è compr esa la definizione della classe System.Object, dalla quale der iva pr essochè ogni altr a
classe. I dati contenuti nella BCL per mettono di svolger e ogni oper azione possibile sulla macchina.




4. X ML
Successivamente tr oviamo i dati, le r isor se. Per salvar e i dati viene usato quasi sempr e il for mato XM L (eXtensible
Mar kup Language), che utilizza dei tag spesso nidificati per contener e i campi necessar i. La str uttur a di questo tipo di
file, inoltr e, è adatta alla r appr esentazione ger ar chica, un metodo che nell'ambiente .net è impor tantissimo. I file di
configur azione e quelli delle opzioni impostate dell'utente, ad esempio, vengono salvati in for mato XML. Anche la nuova
tecnologia denominata Window s Pr esentation Foundation (WPF), intr odotta nella ver sione 3.5 del Fr amew or k, che
per mette di cr ear e contr olli dalla gr afica accattivante e str avagante, si basa su un linguaggio di contr assegno (di
mar kup) sur r ogato dell'XML.
5. W indow s Forms e ASP.NET
Al livello super ior e tr oviamo ASP.NET e Window s For ms, ossia le inter facce gr afiche che r icopr ono il codice
dell'applicazione ver a e pr opr ia. La pr ima è una tecnologia pensata per lo sviluppo sul Web, mentr e la seconda for nisce
sostanzialmente la possibilità di cr ear e una inter faccia gr afica (Gr aphical User Inter face, GUI) in tutto e per tutto
uguale a quella classica, a finestr e, dei sistemi oper ativi Window s. La costr uzione di una Window s For m (ossia una
singola finestr a) è semplice e avviene come nel Vb classico, e chi sta leggendo questa guida per passar e dal VB6 al
VB.NET lo sapr à bene: si pr endono uno o più contr olli e li si tr ascinano sulla super ficie della finestr a, dopodichè si scr ive
il codice associato ad ognuno dei lor o eventi.




6. Common Language Spec ific ations
Il penultimo stadio della str atificazione del Fr amew or k coincide con le Common Language Specifications (CLS), ossia un
insieme di specifiche che definiscono i r equisiti minimi r ichiesti a un linguaggio di pr ogr ammazione per esser e
qualificato come .NET. Un esempio di tali dir ettive: il linguaggio deve saper e gestir e tipi base come str inghe e numer i
inter i, vettor i e collezioni a base zer o e deve saper pr ocessar e un'eccezione scatenata dal Fr amew or k.




7. Linguaggi .NET
In cima alla str uttur a ci sono tutti i linguaggi .net: Vb, C#, J#, ecceter a.




V ersioni del Framew ork
Con il passar e degli anni, a par tir e dal 2002, Micr osoft ha r ilasciato ver sioni successive del Fr amew or k .NET e ognuna
di queste r elease ha intr odotto nuovi concetti di pr ogr ammazione e nuove possibilità per lo sviluppator e.
Par allelamente all'uscita di queste nuove ver sioni, sono state cr eate anche edizioni successive del linguaggio VB.NET,
ognuna delle quali è stata natur almente accostata alla ver sione del Fr amew or k su cui si r eggeva. Ecco una r apida
panor amica dell'evoluzione del linguaggio:

       VB2002: si basa sulla ver sione 1.0 del Fr amew or k
       VB2003: si basa sulla ver sione 1.1 del Fr amew or k
       VB2005: si basa sulla ver sione 2.0 del Fr amew or k. Questa è la ver sione maggior mente utilizzata in questa
       guida, sebbene cer ti capitoli si concentr er anno sull'intr oduzione di alcuni nuovi aspetti por tati da VB2008
       VB2008: si basa sulla ver sione 3.5 del Fr amew or k. La ver sione 3.0 si fondava ancor a sulla 2.0 del CLR e per ciò le
       modifiche consistevano sostanzialmente nell'aggiunta di alcuni componenti e nell'appor to di diver se miglior ie e
       cor r ezioni
       VB2010: si basa sulla ver sione 4.0 del Fr amew or k
A4. Utilizzo base dell'IDE

IDE? Me lo sono dimentic ato a c asa...
Non vi pr eoccupate: se avete seguito tutti i capitoli fino a questo punto, siete già un possesso di un IDE: Visual Basic
2005 (o 2008) Ex pr ess. L'acr onimo IDE significa Integr ated Development Envir onment ("ambiente di sviluppo integr ato")
ed indica un softw ar e che aiuta il pr ogr ammator e nella stesur a del codice. Il softw ar e che vi ho consigliato for nisce,
sebbene sia la ver sione fr ee, un numer o molto alto di str umenti e tools. In pr imis, contiene, ovviamente, un editor di
codice sor gente, pr ogettato in modo da evidenziar e in modo differ ente le keyw or ds e da suppor tar e molte funzioni di
r icer ca e r aggr uppamento che vedr emo in seguito. Accanto a questo, i pr incipali componenti che non possono mancar e
in un IDE sono il compilator e ed il debugger , di cui ho dato una veloce definizione nel capitolo intr oduttivo. Il pr imo ha
lo scopo di legger e il sor gente scr itto dal pr ogr ammator e e pr odur r e da questo un eseguibile: i passi che vengono
por tati a ter mine dur ante un pr ocesso di compilazione sono in r ealtà più di uno (di solito compilazion e e lin kin g), ma
molto spesso si semplifica il tutto par lando semplicemente di compilazione. Il secondo, invece, è il pr ogr amma che vi
dar à più filo da tor cer e, anche se in r ealtà sar à il vostr o miglior e aiutante (diciamo che vi sfinir à a fin di bene): il
debugger ha la funzione di analizzar e e segnalar e i bugs (bachi, er r or i) che si ver ificano dur ante l'esecuzione; assieme
ad un r appor to dettagliato del tipo di er r or e ver ificatosi, segnala par allelamente anche il punto del codice che ha dato
pr oblemi, in modo da r ender e molto più semplice individuar e e cor r egger e la falla.




Funzionamento del c ompilatore .NET
Il compilator e è, come già detto, quel softw ar e necessar io a "tr asfor mar e" il codice sor gente scr itto in un deter minato
linguaggio in un pr ogr amma eseguibile. Nor malmente, un compilator e pr odur r ebbe un applicativo tr aducendo le
istr uzioni testuali intr odotte dal pr ogr ammator e in linguaggio macchina, ossia una ser ie di bit univocamente
inter pr etabile dal pr ocessor e. I compilator i .NET, invece, hanno un compor tamento differ ente, in quanto il lor o output
non è un "nor male pr ogr amma" scr itto in linguaggio macchina, ma si tr atta di una ser ie di istr uzioni codificate in un
altr o linguaggio speciale, chiamato IL (Inter mediate Language). Come sugger isce il nome, esso si tr ova ad un livello
inter medio tr a la macchina e l'astr azione: è super ior e r ispetto al pur o codice binar io, ma allo stesso tempo è un
gr adino più sotto r ispetto ai linguaggi .NET. Venendo a conoscenza di queste infor mazioni, dovr ebbe sor ger e
spontaneamente una domanda: come fa allor a un pr ogr amma .NET ad esser e eseguito? La r isposta è semplice: è lo
stesso Fr amew or k che si occupa di inter pr etar ne le istr uzioni e di eseguir le, sempr e sotto la super visione del CLR. Per
questo motivo, si hanno tr e impor tanti conseguenze:

       Non è possibile far cor r er e un'applicazione .NET su una macchina spr ovvista del Fr amew or k;
       Il codice .NET è sempr e sicur o;
       Un pr ogr amma .NET è sempr e disassemblabile: su questo punto mi soffer mer ò in seguito.




Creare una Console A pplic ation
Nei pr ossimi capitoli inizer ò ad intr odur r e la sintassi del linguaggio, ossia le r egole da r ispettar e quando si scr ive un
codice. Per tutti gli esempi della sezione A, far ò uso di applicazioni conso le (avete pr esente la finestr ella con lo sfondo
ner o?), che lavor ano in DOS. Per cr ear e una Applicazione Console bisogna selezionar e dal menù File del compilator e, la
voce New Pr oject, e quindi sceglier e il tipo di applicazione desider ata. Dopodichè, il compilator e scr iver à
aumaticamente alcune r ighe di codice pr eimpostate, che possono esser e simili a queste:

 Module Module1
Sub Main()

     End Sub
 End Module




Nello scr eenshot pr oposto qui sopr a si possono veder e le tr e ar ee in cui è solitamente divisa l'inter faccia del
compilator e: non vi pr eoccupate se la vostr a appar e differ ente, poiché, essendo modificabile a piacimento, la mia
potr ebbe esser e diver sa dal layout pr eimpostato del compilator e. Per or a, le finestr e impor tanti sono due: quella del
codice, dove andr emo a scr iver e le istr uzioni, e quella degli er r or i, dove potr ete tener e costantemente sott'occhio se
avete commesso degli er r or i di sintassi. Nello scr eenshot la seconda di queste non è visibile, ma la si può por tar e in
pr imo piano tenendo pr emuto Ctr l e digitando in successione "" ed "E".
Per quanto r iguar da il codice che appar e, ho già specificato in pr ecedenza che i moduli sono dei tipi speciali di classe, e
fin qui vi baster à saper e questo. Quello che potr este non conoscer e è la par te di sor gente in cui appaiono le par ole Sub
ed End Sub: anche in questo caso, la tr attazione par ticolar e di queste keyw or ds sar à r imandata più in là. Per or a
possiamo consider ar e la Sub Main() come il pr ogr amma inter o: ogni cosa che viene scr itta tr a "Sub Main()" ed "End Sub"
ver r à eseguita quando si pr emer à il pulsante Star t (il tr iangolino ver de in alto sulla bar r a degli str umenti), o in
alter nativa F5.




Compilazione del programma finito
Una volta finito di scr iver e il codice e di testar lo usando le funzioni dell'IDE (ivi compr esa l'esecuzione in modalità debug
pr emendo F5), sar à necessar io cr ear e il pr ogr amma finito. Quello che avete eseguito fin'or a non er a altr o che una
ver sione più lenta e meno ottimizzata del softw ar e scr itto, poiché c'er a bisogno di contr ollar e tutti gli er r or i e i bugs,
impiegando tempo e spazio per memor izzar e le infor mazioni r elative al debug, appunto. Per cr ear e l'applicazione
r eale finita, è necessar io compilar e il codice in modalità r elease. Apr ite la scheda delle pr opr ietà di pr ogetto, dal menù
pr incipale Pr oject > [NomePr ogetto] Pr oper ties (l'ultima voce del sottomenù); selezionate la scheda Compile e cambiate
il campo Configur ation su Release, quindi pr emete Build > Build Pr oject (Build è sempr e una voce del menù pr incipale).
Tr over ete l'eseguibile compilato nella car tella DocumentiVisual Studio 2008Pr ojects[Nome pr ogetto]binRelease.
A5. Variabili e costanti

Le variabili
Una var iabile è uno spazio di memor ia RAM (Random Access Memor y) in cui vengono allocati dei dati dal pr ogr amma, ed
è possibile modificar ne od ottener ne il valor e facendo r ifer imento ad un nome che si definisce ar bitr ar iamente. Questo
nome si dice anche iden tificatore (o, più r ar amente, mn emon ico), e può esser e costituito da un qualunque insieme di
car atter i alfanumer ici e under scor e: l'unica condizione da r ispettar e per cr ear e un nome valido è che questo non può
iniziar e con un numer o. Per esempio "Pippo", "_Pluto", "Mar io78" o anche "_12345" sono identificator i validi, mentr e
"0Luigi" non lo è. Il pr incipale scopo di una var iabile è contener e dati utili al pr ogr amma; tali dati possono r isieder e in
memor ia per un tempo più o meno lungo, a seconda di quando una var iabile viene cr eata o distr utta: ogni var iabile,
comunque, cessa di esister e nel momento in cui il pr ogr amma viene chiuso. Essa, inoltr e, può contener e una
gr andissima var ità di tipi di dato diver si: dai numer i alle str inghe (testo), dalle date ai valor i booleani, per allar gar si
poi a tipi più ampi, in gr ado di r appr esentar e un inter o file. Ma pr ima di ar r ivar e a spiegar e tutto questo, bisogna
analizzar e in che modo si dichiar a una var iabile. La dichiar azione, tanto di una costante quanto di una classe, è l'atto
definitivo con cui si stabilisce l'esistenza di un'entità e la si r ende disponibile o accessibile alle altr i par ti del
pr ogr amma. Ogni cosa, per esser e usata, deve pr ima esser e dichiar ata da qualche par te: questa oper azione equivale,
ad esempio, a definir e un concetto in matematica: la definizione è impor tantissima.
Ecco un semplice esempio:

   01. Module Module1
   02.     Sub Main()
   03.         Dim Ciao As Int16
   04.         Ciao = 78
   05.         Ciao = Ciao + 2
   06.         Console.WriteLine(Ciao)
   07.         Console.Readkey()
   08.     End Sub
   09. End Module

Facendo cor r er e il pr ogr amma avr emo una scher mata ner a su cui viene visualizzato il numer o 80. Per chè? Or a
vediamo.
Come avr ete notato, le var iabili si dichiar ano in un modo specifico, usando le keyw or ds Dim e A s:

    1. Dim [nome] As [tipo]

Dove [nome] è l'identificator e con cui ci si r ifer isce ad una var iabile e [tipo] il tipo di dato contenuto nella var iabile.
Esistono molteplici tipi di var iabile fr a cui è possibile sceglier e. Ecco un elenco dei tipi base (che sono consider ati
keyw or ds):

       Byte: inter o a 8 bit che può assumer e valor i da 0 a 255;
       Char : valor e a 8 bit che può assumer e i valor i di ogni car atter e della tastier a (compr esi quelli speciali);
       Int16 o Sho r t: inter o a 16 bit che può assumer e valor i da -32768 a +32767;
       Int32 o Integ er : inter o a 32 bit da -2147483648 a +2147483647;
       Int64 o Lo ng : inter o a 64 bit da cir ca -922000000000000000 a +9220000000000000000;
       Sing le: decimale da cir ca -3,4e+38 a +3,4e+38, con un inter vallo minimo di cir ca 1,4e-45;
       Do uble: decimale da cir ca -1,79e+308 a +1,79e+308, con un inter vallo minimo di cir ca 4,9e-324;
       Bo o lean: dato a 4 bytes che può assumer e due valor i, Tr ue (ver o) e False (falso). Nonostante la limitatezza del
       suo campo di azione, che concettualmente potr ebbe r estr inger si ad un solo bit, il tipo Boolean occupa 32bit di
       memor ia: sono quindi da evitar e gr andi quantità di questo tipo;
       Str ing : valor e di minimo 10 bytes, composto da una sequenza di car atter i. Se vogliamo, possiamo assimilar lo ad
un testo;
       Object: r appr esenta un qualsiasi tipo (ma non è un tipo base).

I tipi base vengono detti anche ato m ici o pr im itiv i, poiché non possono esser e ulter ior mente scomposti. Esistono,
quindi, anche tipi der iv ati, appar tenenti a svar iate tipologie che analizzer emo in seguito, fr a cui si annover ano anche
le classi: ogni tipo der ivato è scomponibile in un insieme di tipi base.
Or a, quindi, possiamo estr apolar e delle infor mazioni in più dal codice pr oposto: dato che segue la keyw or d Dim, "Ciao"
è l'identificator e di una var iabile di tipo Int16 (infatti dopo As è stato specificato pr opr io Int16). Questo significa che
"Ciao" può contener e solo numer i inter i che, in valor e assoluto, non super ino 32767. Ovviamente, la scelta di un tipo di
dato piuttosto che un altr o var ia in funzione del compito che si intende svolger e: maggior e è la pr ecisione e l'or dine di
gr andezza dei valor i coinvolti e maggior e sar à anche l'uso di memor ia che si dovr à sostener e. Continuando a legger e,
si incontr a, nella r iga successiva, un'assegnazione, ossia una oper azione che pone nella var iabile un cer to valor e, in
questo caso 78; l'assegnazione avviene mediante l'uso dell'oper ator e uguale "=". L'istr uzione successiva è simile a questa,
ma con una sostanziale differ enza: il valor e assegnato alla var iabile è influenzato dalla var iabile stessa. Nell'esempio
pr oposto, il codice:

    1. Ciao = Ciao + 2

ha la funzione di incr ementar e di due unità il contenuto di Ciao. Questa istr uzione potr ebbe sembr ar e algebr icamente
scor r etta, ma bisogna r icor dar e che si tr atta di un comando (e non di un'equazione): pr ima di scr iver e nella cella di
memor ia associata alla var iabile il numer o che il pr ogr ammator e ha designato, il pr ogr amma r isolve l'espr essione a
destr a dell'uguale sostituendo ad ogni var iabile il suo valor e, e ottenendo, quindi, 78 + 2 = 80. Le ultime due r ighe,
invece, fanno visualizzar e a scher mo il contenuto di Ciao e fer mano il pr ogr amma, in attesa della pr essione di un
pulsante.
Come si è visto dall'esempio pr ecedente, con le var iabili di tipo numer ico si possono eseguir e oper azioni ar itmetiche.
Gli oper ator i messi a disposizione dal Fr amew or k sono:

       + : addizione;
       - : sottr azione;
       * : pr odotto;
       / : divisione;
        : divisione tr a inter i (r estituisce come r isultato un numer o inter o a pr escinder e dal tipo degli oper andi, che
       possono anche esser e decimali);
       Mod : r estituisce il r esto di una divisione inter a;
       = : assegna alla var iabile posta a sinistr a dell'uguale il valor e posto dopo l'uguale;
       & : concatena una str inga con un numer o o una str inga con un'altr a str inga.

   01. Module Module1
   02.     Sub Main()
   03.         'Interi
   04.         Dim Intero, Ese As Int16
   05.         'Decimale
   06.         Dim Decimale As Single
   07.         'Booleano
   08.         Dim Vero As Boolean
   09.         'Stringa
   10.         Dim Frase As String
   11.
   12.         Intero = 90
   13.         Ese = Intero * 2 / 68
   14.         Intero = Ese - Intero * Intero
   15.         Decimale = 90.76
   16.         Decimale = Ese / Intero
   17.         Vero = True
   18.         Frase = "Ciao."
   19.         'L'operatore "+" tra stringhe concatena due stringhe. Dopo la
   20.
'prossima istruzione, la variabile Frase conterrà:
   21.         ' "Buon giornoCiao"
   22.         Frase = "Buon giorno" + "Ciao"
   23.         'L'operatore "&" può concatenare qualsiasi dato e
   24.         'restituisce una stringa. Dopo la prossima istruzione, la
   25.         'variabile Frase conterrà:
   26.         ' "Il valore decimale è: -0,0003705076"
   27.         Frase = "Il valore decimale è: " & Decimale
   28.     End Sub
   29. End Module

Esistono poi degli speciali oper ator i di assegnamento, che velocizzano l'assegnazione di valor i, alcuni sono:

   01. Module Module1
   02.   Sub Main()
   03.     Dim V, B As Int32
   04.
   05.     V += B 'Equivale a        V   =   V   +   B
   06.     B -= V 'Equivale a        B   =   B   -   V
   07.     V *= B 'Equivale a        V   =   V   *   B
   08.     B /= V 'Equivale a        B   =   B   /   V
   09.   End Sub
   10. End Module

Le fr asi poste dopo un apice (') sono dette co m m enti e ser vono per spiegar e cosa viene scr itto nel codice. Il contenuto
di un commento NON influisce in nessun modo su ciò che è scr itto nel sor gente, ma ha una funzione ESCLUSIVAMENTE
esplicativa.




Le c ostanti
Abbiamo visto che il valor e delle var iabili può esser e modificato a piacimento. Ebbene quello delle costanti, come il
nome sugger isce, no. Esistono per semplificar e le oper azioni. Per esempio, invece di digitar e 3,1415926535897932 per
il Pi g r eco , è possibile dichiar ar e una costante di nome Pi che abbia quel valor e ed utilizzar la nelle espr essioni. La
sintassi per dichiar ar e una costante è la seguente:

    1. Const [nome] As [tipo] = [valore]

Ci sono due lampanti differ enze r ispetto al codice usato per dichiar ar e una var iabile. La pr ima è, ovviamente, l'uso
della keyw or d Con s t al posto di Dim; la seconda consiste nell'assegnazione posta subito dopo la dichiar azione. Infatti,
una costante, per esser e tale, dev e contener e qualcosa: per questo motivo è o bblig ato r io specificar e sempr e, dopo
la dichiar azione, il valor e che la costante assumer à. Questo valor e non potr à mai esser e modificato.
Esempio:

   01. Module Module1
   02.   Sub Main()
   03.     Const Pi As Single = 3.1415926535897932
   04.     Dim Raggio, Area As Double
   05.
   06.     'Questa istruzione scrive sul monitor il messaggio posto tra
   07.     'virgolette nelle parentesi
   08.     Console.WriteLine("Inserire il raggio di un cerchio:")
   09.
   10.     'Questa istruzione leggè un valore immesso dalla tastiera e
   11.     'lo deposita nella variabile Raggio
   12.     Raggio = Console.ReadLine
   13.     Area = Raggio * Raggio * Pi
   14.
   15.     Console.WriteLine("L'Area è: " & Area)
   16.
   17.     'Questa istruzione ferma il programma in attesa della pressione
   18.     'di un pulsante
   19.     Console.ReadKey()
   20.   End Sub
   21. End Module
N.B.: a causa della lor o stessa natur a, le costanti NON possono esser e inizializzate con un valor e che dipenda da una
funzione. Scr ivo questo appunto per pur a utilità di consultazione: anche se or a potr à non r isultar e chiar o, vi capiter à
più avanti di imbatter vi in er r or i del gener e:

    1. Const Sqrt2 As Single = Math.Sqrt(2)

Sqr t2 dovr ebbe esser e una costante numer ica decimale che contiene la r adice quadr ata di due. Sebbene il codice
sembr i cor r etto, il compilator e segnaler à come er r or e l'espr essione Math.Sqr t(2), poiché essa è una funzione, mentr e
dopo l'uguale è r ichiesto un valor e sempr e costante. Il codice cor r etto è

    1. Const Sqrt2 As Single = 1.4142135




Le istruzioni
Tutti i comandi che abbiamo impar tito al computer e che abbiamo gener icamente chiamato con il nome di istr uzioni
(come Console.Wr iteLine()) hanno dei nomi più specifici: sono pr o cedur e o funzio ni, in sostanza sottopr ogr ammi già
scr itti. Pr ocedur e e funzioni possono esser e globalmente indicate con la par ola m eto do . I metodi accettano dei
par am etr i passatigli tr a par entesi: se i par ametr i sono di più di uno vengono separ ati da vir gole. I par ametr i
ser vono per comunicar e al metodo i dati sui quali questo dovr à lavor ar e. La differ enza tr a una pr ocedur a e una
funzione r isiede nel fatto che la pr ima fa semplicemente eseguir e istr uzioni al computer , mentr e la seconda r estituise
un valor e. Ad esempio:

   01. Module Module1
   02.     Sub Main()
   03.         Dim F As Double
   04.
   05.         'Questa è una funzione che restituisce la radice quadrata di 56
   06.         F = Math.Sqrt(56)
   07.
   08.         'Questa è una procedura che scrive a video un messaggio
   09.         Console.WriteLine("La radice di 56 è " & F)
   10.         Console.ReadKey()
   11.     End Sub
   12. End Module

Anche i metodi ver r anno tr attai successivamente in dettaglio.
A6. Tipi Reference e tipi Value

Tutti i tipi di var iabile che possono esser e cr eati si r aggr uppano sotto due gr andi categor ie: Refer ence e Value. I pr imi
si compor tano come oggetti, mentr e i secondi r appr esentano tipi scalar i o numer ici, ma vediamo di metter e un po'
or dine in tutti questi concetti.
P.S.: per una miglior e compr esione di questo capitolo, consiglio solo a chi ha già esper ienza nel campo della
pr ogr ammazione (in qualsiasi altr o linguaggio) di legger e questo ar tico lo sull'utilizzo della memor ia da par te di un
pr ogr amma.




Differenza tra Classi e Oggetti
All'inizio della guida mi sono soffer mato ad elogiar e le classi e la lor o enor me impor tanza nell'ambiente .NET.
Successivamente ho fatto menzione al tipo System.Object e al fatto che ogni cosa sia un oggetto. La differ enza tr a
o g g etto e classe è di vitale impor tanza per capir e come vanno le cose nell'ambito della pr ogr ammazione OO. Una
classe r appr esenta l'astr azione di qualcosa di concr eto; un oggetto, invece, è qualcosa di concr eto e viene
r appr esentato da una classe. Per far e un esempio banale, sappiamo benissimo che esiste il concetto di "uomo", ma ogni
individuo sul pianeta, pur mantenendo alcune car atter istiche simili e costanti, è differ ente r ispetto agli altr i. Facendo
un par allelismo con la pr ogr ammazione, quindi, il singolo individuo, ad esempio io stesso, è un oggetto, mentr e il
gener ale concetto di "uomo" che ognuno di noi conosce è la classe. Se qualcuno dei lettor i ha studiato filosofia,
r iconoscer à in questa differ enza la stessa che Platone identificava nella discr epanza tr a mondo sensibile e Iper ur anio.
Avendo ben chiar i questi concetti, si può or a intr odur r e un po' di ger go tecnico. Ogni oggetto è anche detto istanza
della classe che lo r appr esenta (voi siete istanze della classe Uomo XD) e is tan ziare un oggetto significa cr ear lo.

    1. 'New serve per creare fisicamente degli oggetti in memoria
    2. Dim O1 As New Object
    3. Dim O2 As New Object

O1 e O2 sono entr ambe istanze della classe Object, ma sono diver si fr a di lor o: in comune hanno solo l'appar tenenza allo
stesso tipo.
N.B.: come si è notato, "tipo" e "classe" sono ter mini spesso equivalenti, ma non gener alizzate questa associazione.




Tipi Referenc e
Ogni cosa nel Fr amew or k è un oggetto e la maggior par te di essi sono tipi r efer ence. Si dicono tipi r efer ence tutti
quei tipi che der ivano dir ettamente dalla classe System.Object (la "der ivazione" appar tiene a un concetto che spiegher ò
più avanti): questa classe è dichiar ata all'inter no di una libr er ia della Base Class Libr ar y, ossia l'ar chivio di classi del
Fr amew or k. Nel capitolo pr ecedente si è visto come sia possibile assegnar e un valor e ad una var iabile utilizzando
l'oper ator e uguale "=". Con questo meccanismo, un deter minato valor e viene depositato nella casella di memor ia che la
var iabile occupa. Ebbene, facendo uso dei tipi r efer ence, questo non avviene. Quando si utilizza l'uguale per assegnar e
un valor e a tali var iabili, quello che effettivamente viene r iposto nella lor o par te di memor ia è un puntator e inter o a
32bit (su sistemi oper ativi a 32bit). Per chi non lo sapesse, un puntator e è una speciale var iabile che, invece di
contener e un pr opr io valor e, contiene l'indir izzo di un'ar ea di memor ia contenente altr i dati. Il puntator e viene
memor izzato come al solito sullo stack , mentr e il ver o oggetto viene cr eato e deposto in un'ar ea di memor ia
differ ente, detta heap m anag ed, dove esiste sotto la super visione del CLR. Quando una var iabile di questo tipo viene
impostata a Nothing (una costante che vedr emo tr a poco), la par te dell'heap managed che l'oggetto occupa viene
r ilasciata dur ante il pr ocesso di g ar bag e co llectio n ("r accolta dei r ifiuti"). Tuttavia, ciò non avviene subito, poichè il
meccanismo del Fr amew or k fa in modo di avviar e la gar bage collection solo quando è necessar io, quindi quando la
memor ia comincia a scar seggiar e: supponendo che un pr ogr amma abbia r elativamente pochi oggetti, questi
potr ebber o "viver e" indistur bati fino alla fine del pr ogr amma anche dopo esser e stati lo g icam ente distr utti, il che
significa che è stato eliminato manualmente qualsiasi r ifer imento ad essi (vedi par agr afo successivo). Data
l'impossibilità di deter minar e a pr ior i quando un oggetto ver r à distr utto, si ha un fenomeno che va sotto il nome di
finalizzazio ne no n deter m inistica (il ter mine "finalizzazione" non è casule: veder e il capitolo sui distr uttor i per
maggior i infor mazioni).




Nothing
Nothing è una costante di tipo r efer ence che r appr esenta l'assenza di un oggetto piuttosto che un oggetto nullo. Infatti,
por r e una var iabile oggetto uguale a Nothing equivale a distr ugger la logicamente.

    1. Dim O As New Object 'L'oggetto viene creato
    2. O = Nothing 'L'oggetto viene logicamente distrutto

La distr uzione logica non coincide con la distr uzione fisica dell'oggetto (ossia la sua r imzione dalla memor ia), poiché,
come detto pr ima, il pr ocesso di liber azione della memor ia viene avviato solo quando è necessar io. Non è possibile
assegnar e Nothing a un tipo value, ma è possibile usar e speciali tipi value che suppor tano tale valor e: per ulter ior i
dettagli, veder e "Tipi Nullable".




Tipi V alue
Ogni tipo v alue der iva dalla classe System.ValueType, che der iva a sua volta da System.Object, ma ne r idefinisce i
metodi. Ogni var iabile di questo tipo contiene effettivamente il pr opr io valor e e non un puntator e ad esso. Inoltr e,
esse hanno dei vantaggi in ter mini di memor ia e velocità: occupano in gener e meno spazio; data la lor o posizione sullo
stack non vi è bisogno di r efer enziar e un puntator e per ottener e o impostar ne i valor i (r efer enziar e un puntator e
significa r ecar si all'indir izzo di memor ia puntato e legger ne il contenuto); non c'è necessità di occupar e spazio nello
heap managed: se la var iabile viene distr utta, cessa di esister e all'istante e non si deve attuar e nessuna oper azione di
r ilascio delle r isor se. Notar e che non è possibile distr ugger e logicamente una var iabile value, fatta eccezione per cer ti
tipi der ivati.




Is e =
Nel lavor ar e con tipi r efer ence e value bisogna pr estar e molta attenzione a quando si utilizzano gli oper ator i di
assegnamento. Come già detto, i r efer ence contengono un puntator e, per ciò se si scr ive questo codice:

    1. Dim O1, O2 As Object
    2. '...
    3. O1 = O2

quello che O2 conter r à non sar à un valor e identico a O1, ma un puntator e alla stessa ar ea di memor ia di O1. Questo
pr ovoca un fatto str ano, poichè sia O1 che O2 puntano alla stessa ar ea di memor ia: quindi O1 e O2 so no lo stesso
o g g etto , soltanto r ifer ito con nomi difer si. In casi simili, si può utilizzar e l'oper ator e Is per ver ificar e che due
var iabili puntino allo stesso oggetto:

    1. 'Scrive a schermo se è vero oppure no che
    2. 'O1 e O2 sono lo stesso oggetto
    3. Console.WriteLine(O1 Is O2)

La scr itta che appar ir à sullo scher mo sar à "Tr ue", ossia "Ver o". Utilizzar e Is per compar ar e un oggetto a Nothing
equivale a ver ificar e che tale oggetto sia stato distr utto.
Questo NON avviene per i tipi value: quando ad un tipo value si assegna un altr o valor e con l'oper ator e =, si passa
effettivamente una co pia del valor e. Non è possibile utilizzar e Is con i tipi value poichè Is è definito solo per i
r efer ence.




Boxing e Unboxing
Consider iamo il seguente codice:

    1. Dim I As Int32 = 50
    2. Dim O As Object
    3. O = I

I è un tipo value, mentr e O è un tipo r efer ence. Quello che succede dietr o le quinte è semplice: il .NET cr ea un nuovo
oggetto, per ciò un tipo r efer ence, con il r ispettivo puntator e, e quindi gli assegna il valor e di I: quando il pr ocesso è
finito assegna il puntator e al nuovo oggetto a O. Questa conver sione spr eca tempo e spazio nello heap managed e viene
definita come bo xing . L'oper azione inver sa è l'unboxing e consiste nell'assegnar e un tipo r efer ence a un tipo value. Le
oper azioni che si svolgono sono le stesse, ma al contr ar io: entr ambe spr ecano tempo e cpu, quindi sono da evitar e se
non str ettamente necessar ie. Quando si può sceglier e, quindi, sono meglio di tipi value.




Una pr ecisazione: in tutti i pr ossimi capitoli capiter à fr equentemente che io dica cose del tipo "la var iabile X è un
oggetto di tipo Str ing" oppur e "le due var iabili sono lo stesso oggetto". Si tr atta solo di una via più br eve per evitar e il
for malismo tecnico, poiché, se una var iabile è dichiar ata di tipo r efer ence, essa è pr opr iamente un riferimen to
all'oggetto e non un oggetto. Gli oggetti "vivono" indistur bati nell'heap managed, quel magico posto che nessuno conosce:
noi possiamo solo usar e r ifer imenti a tali oggetti, ossia possiamo solo indicar li ("eccolo là! guar da! l'hai visto? ma sì,
pr opr io là! non lo vedi?").
A7. Il costrutto If

Capita spessissimo di dover eseguir e un contr ollo per ver ificar e se vigono cer te condizioni. È possibile attuar e tale
oper azione tr amite un co str utto di co ntr o llo , la cui for ma più comune e diffusa è il costr utto If. Questo per mette di
contr ollar e se una condizione è ver a. Ad esempio: in un pr ogr amma che calcoli l'ar ea di un quadr ato si deve impor r e di
visualizzar e un messaggio di er r or e nel caso l'utente immetta una misur a negativa, poichè, come è noto, non esistono
lati la cui misur a è un numer o negativo:

   01. Module Module1
   02.     Sub Main()
   03.         Dim Lato As Single
   04.
   05.         Console.WriteLine("Inserire il lato di un quadrato:")
   06.         Lato = Console.ReadLine
   07.
   08.         If Lato < 0 Then 'Se Lato è minore di 0...
   09.              Console.WriteLine("Il lato non può avere una misura negativa!")
   10.         Else 'Altrimenti, se non lo è...
   11.              Console.WriteLine("L'area del quadrato è: " & Lato * Lato)
   12.         End If 'Fine controllo
   13.
   14.         Console.ReadKey()
   15.     End Sub
   16. End Module

Come sicur amente avr ete intuito, questo contr ollo si può associar e al costr utto italiano "Se avviene qualcosa Allor a fai
questo Altr imenti fai quell'altr o". Si può eseguir e qualsiasi tipo di compar azione tr a If e Then utilizzando i seguenti
oper ator i di confr onto:

       > : maggior e
       < : minor e
       = : uguaglianza
       <> : diver so
       >= : maggior e o uguale
       <= : minor e o uguale
       Is : identicità (solo per tipi r efer ence)
       IsNot : negazione di Is (solo per tipi r efer ence)

Ma l'impor tante è r icor dar si di attener si a questa sintassi:

    1. If [Condizione] Then
    2.   [istruzioni]
    3. Else
    4.   [istruzioni alternative]
    5. End If




If nidific ati
Quando si tr ova un costr utto If all'inter no di un altr o costr utto If, si dice che si tr atta di un Co str utto If Nidificato .
Questo avviene abbastanza spesso, specie se si ha bisogno di far e contr olli multipli:

   01. Module Module1
   02.     Sub Main()
   03.         Dim Numero As Int16
   04.
   05.
Console.WriteLine("Inserisci un numero:")
   06.         Numero = Console.ReadLine
   07.
   08.         If Numero > 0 Then
   09.              If Numero < 5 Then
   10.                  Console.WriteLine("Hai indovnato il numero!")
   11.              End If
   12.         Else
   13.              Console.WriteLine("Numero errato!")
   14.         End If
   15.
   16.         Console.ReadKey()
   17.     End Sub
   18. End Module

Se il numer o inser ito da tastier a è compr eso fr a 0 e 5, estr emi esclusi, allor a l'utente ha indovinato il numer o,
altr imenti no. Si può tr ovar e un numer o illimitato di If nidificati, ma è meglio limitar ne l'uso e, piuttosto, far e utilizzo
di co nnettiv i lo g ici.




I c onnettivi logic i
I connettivi logici sono 4: And, Or , Xor e Not. Ser vono per costr uir e contr olli complessi. Di seguito un'illustr azione del
lor o funzionamento:

        If A And B : la condizione r isulta ver ificata se sia A che B sono ver e co ntem po r aneam e nte
        If A Or B : la condizione r isulta ver ificata se è ver a alm eno una delle due condizioni
        If A Xor B: la condizione r isulta ver a se una so la delle due condizioni è ver a
        If Not A: la condizione r isulta ver ificata se è falsa

Un esempio pr atico:

   01. Module Module1
   02.     Sub Main()
   03.         Dim a, b As Double
   04.
   05.         Console.WriteLine("Inserire i lati di un rettangolo:")
   06.         a = Console.ReadLine
   07.         b = Console.ReadLine
   08.
   09.         'Se tutti e due i lati sono maggiori di 0
   10.         If a > 0 And b > 0 Then
   11.              Console.WriteLine("L'area è: " & a * b)
   12.         Else
   13.              Console.WriteLine("Non esistono lati con misure negative!")
   14.         End If
   15.     Console.Readkey()
   16.     End Sub
   17. End Module




Continuare il c ontrollo: ElseIf
Nei pr ecedenti esempi, la seconda par te del costr utto è sempr e stata Els e, una par ola r iser vata che indica cosa far e se
n on si ver ifica la condizione pr oposta dalla pr ima par te. Il suo valor e è, quindi, di pur a alter nativa. Esiste, tuttavia,
una var iante di Else che consente di continuar e con un altr o contr ollo senza dover r icor r er e ad If nidificati (a cui è
sempr e meglio supplir e con qualcosa di più or dinato). Ammettiamo, ad esempio, di aver e un codice 'autocr itico' simile:

   01. Module Module1
   02.     Sub Main()
   03.         Dim Voto As Single
   04.
   05.         Console.WriteLine("Inserisci il tuo voto:")
   06.
Voto = Console.ReadLine
   07.
   08.         If Voto < 3 Then
   09.              Console.WriteLine("Sei senza speranze!")
   10.         Else
   11.              If Voto < 5 Then
   12.                   Console.WriteLine("Ancora un piccolo sforzo...")
   13.              Else
   14.                   If Voto < 7 Then
   15.                        Console.WriteLine("Stai andando discretamente")
   16.                   Else
   17.                        If Voto < 9 Then
   18.                             Console.WriteLine("Molto bene, continua così")
   19.                        Else
   20.                             Console.WriteLine("Sei praticamente perfetto!")
   21.                        End If
   22.                   End If
   23.              End If
   24.         End If
   25.
   26.         Console.ReadKey()
   27.     End Sub
   28. End Module

E' abbastanza disor dinato... La var iante ElseIf è molto utile per miglior e la leggibilità del codice:

   01. Module Module1
   02.     Sub Main()
   03.         Dim Voto As Single
   04.
   05.         Console.WriteLine("Inserisci il tuo voto:")
   06.         Voto = Console.ReadLine
   07.
   08.         If Voto < 3 Then
   09.              Console.WriteLine("Sei senza speranze!")
   10.         ElseIf Voto < 5 Then
   11.              Console.WriteLine("Ancora un piccolo sforzo...")
   12.         ElseIf Voto < 7 Then
   13.              Console.WriteLine("Stai andando discretamente")
   14.         ElseIf Voto < 9 Then
   15.              Console.WriteLine("Molto bene, continua così")
   16.         Else
   17.              Console.WriteLine("Sei praticamente perfetto!")
   18.         End If
   19.
   20.         Console.ReadKey()
   21.     End Sub
   22. End Module

Notate che tutti gli ElseIf fanno par te dello s tes s o costr utto: mentr e nell'esempio ogni If nidificato er a un blocco a sé
stante, dotato infatti di un pr opr io End If, in questo caso ogni alter nativa-selettiva fa comunque par te dell'unico If
iniziale, pr otr atto solamente un poco più a lungo.




Bloc c hi di istruzioni
Fino a questo punto, gli esempi pr oposti non hanno mai dichiar ato una var iabile dentr o un costr utto If, ma solo
all'inizio del pr ogr amma, dopo Sub Main(). È possibile dichiar ar e var iabili in altr i punti del codice che non siano all'inizio
della Sub? Cer tamente sì. A differ enza di altr i, i linguaggi .NET per mettono di dichiar ar e var iabili in qualunque punto
del sor gente, dove occor r e, evitando un gigantesco agglomer ato di dichiar azioni iniziali, for temente disper sive per chi
legge. Questo è un gr ande vantaggio, ma bisogna far e attenzione ai blocchi di codice. Con questo ter mine ci si r ifer isce
a par ti del sor gente compr ese tr a due par ole r iser vate, che in VB di solito sono accoppiate in questo modo:

    1. [Keyword]
    2.   'Blocco di codice
    3. End [Keyword]
Ad esempio, tutto il codice compr eso tr a Sub ed End Sub costituisce un blocco, così come lo costituisce quello compr eso
tr a If ed End If (se non vi è un Else), tr a If ed Else o addir ttur a tr a Module ed End Module. Facendo questa distinzione
sar à facile intuir e che una var iabile dichiar ata in un blocco no n è v isibile al di fuor i di esso. Con questo voglio dir e
che la sua dichiar azione vale solo all'inter no di quel blocco. Ecco una dimostr azione:

   01. Module Module1
   02.     Sub Main()
   03.         'a, b e c fanno parte del blocco delimitato da Sub ...
   04.         'End Sub
   05.         Dim a, b, c As Single
   06.
   07.         'Semplice esempio di risoluzione di equazione di
   08.         'secondo grado
   09.         Console.WriteLine("Equazione: ax2 + bx + c = 0")
   10.         Console.WriteLine("Inserisci, in ordine, a, b e c:")
   11.         a = Console.ReadLine
   12.         b = Console.ReadLine
   13.         c = Console.ReadLine
   14.
   15.         If a = 0 Then
   16.              Console.WriteLine("L'equazione si abbassa di grado")
   17.              Console.ReadKey()
   18.              'Con Exit Sub si esce dalla Sub, che in questo caso
   19.              'coincide con il programma. Equivale a terminare
   20.              'il programma stesso
   21.              Exit Sub
   22.         End If
   23.
   24.         'Anche delta fa parte del blocco delimitato da Sub ...
   25.         'End Sub
   26.         Dim delta As Single = b ^ 2 - 4 * a * c
   27.
   28.         'Esistono due soluzioni distinte
   29.         If delta > 0 Then
   30.              'Queste variabili fanno parte del blocco di If ...
   31.              'ElseIf
   32.              Dim x1, x2 As Single
   33.              'È possibile accedere senza problemi alla variabile
   34.              'delta, poiché questo blocco è a sua volta
   35.              'all'interno del blocco in cui è dichiarato delta
   36.              x1 = (-b + Math.Sqrt(delta)) / (2 * a)
   37.              x2 = (-b - Math.Sqrt(delta)) / (2 * a)
   38.              Console.WriteLine("Soluzioni: ")
   39.              Console.WriteLine("x1 = " & x1)
   40.              Console.WriteLine("x2 = " & x2)
   41.
   42.         'Esiste una soluzione doppia
   43.         ElseIf delta = 0 Then
   44.              'Questa variabile fa parte del blocco ElseIf ... Else
   45.              Dim x As Single
   46.              x = -b / (2 * a)
   47.              Console.WriteLine("Soluzione doppia: ")
   48.              Console.WriteLine("x = " & x)
   49.
   50.         'Non esistono soluzioni in R
   51.         Else
   52.              Console.WriteLine("Non esistono soluzioni in R")
   53.         End If
   54.
   55.         Console.ReadKey()
   56.     End Sub
   57. End Module

Se in questo codice, pr ima del Console.ReadKey(), finale pr ovassimo a usar e una fr a le var iabili x , x 1 o x 2, otter r emmo
un er r or e:
Questo succede per chè nessuna var iabile dichiar ata all'inter no di un blocco è accessibile al di fuor i di esso. Con questo
schemino r udimentale sar à più facile capir e:




Le fr ecce ver di indicano che un codice può acceder e a cer te var iabili, mentr e quelle r osse indicano che non vi può
acceder e. Come salta subito agli occhi, sono per messe tutte le r ichieste che vanno dall'inter no di un blocco ver so
l'ester no, mentr e sono pr oibite tutte quelle che vanno dall'ester no ver so l'inter no. Questa r egola vale sempr e, in
qualsiasi cir costanza e per qualsiasi tipo di blocco: non ci sono eccezioni.
A8. Il costrutto Select Case

Abbiamo visto nel capitolo pr ecedente come si possa far pr ocessar e al computer un contr ollo per ver ificar e cer te
condizioni. Supponiamo, or a, di aver e 20 contr olli di uguaglianza del tipo:

   01.   '...
   02.   If A = 1 Then
   03.     'istruzioni
   04.   End If
   05.   If A = 2 Then
   06.     'istruzioni
   07.   End If
   08.   If A = 3 Then
   09.     'istruzioni
   10.   End If
   11.   'eccetera

In questo caso il costr utto If diventa non solo noioso, ma anche ingombr ante e disor dinato. Per eseguir e questo tipo di
contr olli multipli esiste un costr utto apposito, Select Case, che ha questa sintassi:

   01. '...
   02. Select Case [Nome variabile da analizzare]
   03.   Case [valore1]
   04.      'istruzioni
   05.   Case [valore2]
   06.      'istruzioni
   07.   Case [valore3]
   08.      'istruzioni
   09. End Select

Questo tipo di contr ollo r ende molto più linear e, semplice e veloce il codice sor gente. Un esempio:

   01. Module Module 1
   02.   Sub Main()
   03.     Dim a, b As Double
   04.         Dim C As Byte
   05.
   06.         Console.WriteLine("Inserire due numeri: ")
   07.         a = Console.ReadLine
   08.         b = Console.ReadLine
   09.         Console.WriteLine("Inserire 1 per calcolare la somma, 2 per la differenza, 3 per il
                  prodotto, 4 per il quoziente:")
   10.         C = Console.ReadLine
   11.
   12.         Select Case C
   13.              Case 1
   14.                  Console.WriteLine(a + b)
   15.              Case 2
   16.                  Console.WriteLine(a - b)
   17.              Case 3
   18.                  Console.WriteLine(a * b)
   19.              Case 4
   20.                  Console.WriteLine(a / b)
   21.         End Select
   22.
   23.         Console.ReadKey()
   24.     End Sub
   25. End Module

Molto semplice, ma anche molto efficace, specialmente utile nei pr ogr ammi in cui bisogna consider ar e par ecchi valor i.
Anche se nell'esempio ho utilizzato solamente numer i, è possibile consider ar e var iabili di qualsiasi tipo, sia base
(str inghe, date), sia der ivato (str uttur e, classi). Ad esempio:

    1.
Dim S As String
    2. '...
    3. Select Case S
    4.      Case "ciao"
    5.          '...
    6.      Case "buongiorno"
    7.          '...
    8. End Select




V arianti del c ostrutto
Anche in questo caso, esistono numer ose var ianti, che per mettono non solo di ver ificar e uguaglianze come nei casi
pr ecedenti, ma anche di contr ollar e disuguaglianze e analizzar e insiemi di valor i. Ecco una lista delle possibilità:

       Uso della v ir g o la
       La vir gola per mette di definir e non solo uno, ma molti valor i possibili in un solo Case. Ad esempio:

          01.   Dim A As Int32
          02.   '...
          03.   Select Case A
          04.        Case 1, 2, 3
          05.            'Questo codice viene eseguito             solo se A
          06.            'contiene un valore pari a 1,             2 o 3
          07.        Case 4, 6, 9
          08.            'Questo codice viene eseguito             solo se A
          09.            'contiene un valore pari a 4,             6 o 9
          10.   End Select

       Il codice sopr a pr oposto con Select equivale ad un If scr itto come segue:

            1. If A = 1 Or A = 2 Or A = 3 Then
            2.     '...
            3. ElseIf A = 4 Or A = 6 Or A = 9 Then
            4.     '...
            5. End If

       Uso di To
       Al contr ar io, la keyw or d To per mette di definir e un ran ge di valor i, ossia un inter vallo di valor i, per il quale la
       condizione r isulta ver ificata se la var iabile in analisi r icade in tale inter vallo.

            1. Select Case A
            2.     Case 67 To 90
            3.         'Questo codice viene eseguito solo se                  A
            4.         'contiene un valore compreso tra 67 e                  90 (estremi inclusi)
            5.     Case 91 To 191
            6.         'Questo codice viene eseguito solo se                  A
            7.         'contiene un valore compreso tra 91 e                  191
            8. End Select

       Questo cor r isponde ad un If scr itto come segue:

            1. If A >= 67 And A <= 90 Then
            2.     '...
            3. ElseIf A >= 91 And A <= 191 Then
            4.     '...
            5. End If

       Uso di Is
       Is è usato in questo contesto per ver ificar e delle condizioni facendo uso di nor mali oper ator i di confr onto
       (meggior e, minor e, diver so, ecceter a...). L'Is usato nel costr utto Select Case non ha assolutamente niente a che
       veder e con quello usato per ver ificar e l'identicità di due oggetti: ha lo stesso nome, ma la funzione è
       completamente differ ente.

          01.
Select Case A
   02.     Case Is >= 6
   03.         'Questo codice viene eseguito solo se A
   04.         'contiene un valore maggiore o uguale di 6
   05.     Case Is > 1
   06.         'Questo codice viene eseguito solo se A
   07.         'contiene un valore maggiore di 1 (e minore di 6,
   08.         'dato che, se si è arrivati a questo Case,
   09.         'significa che la condizione del Case precedente non
   10.         'è stata soddisfatta)
   11. End Select

Il suo equivalente If:

    1. If A >= 6 Then
    2.     '...
    3. ElseIf A > 1 Then
    4.     '...
    5. End If

Uso di Else
Anche nel Select è lecito usar e Else: il Case che include questa istr uzione è solitamente l'ultimo di tutte le
alter native possibili e pr escr ive di eseguir e il codice che segue solo se tutte le altr e condizioni non sono state
soddisfatte:

   01. Select Case A
   02.     Case 1, 4
   03.         'Questo codice viene eseguito solo se A
   04.         'contiene 1 o 4
   05.     Case 9 To 12
   06.         'Questo codice viene eseguito solo se A
   07.         'contiene un valore compreso tra 9 e 12
   08.     Case Else
   09.         'Questo codice viene eseguito solo se A
   10.         'contiene un valore minore di 9 o maggiore di 12,
   11.         'ma diverso da 1 e 4
   12. End Select

Uso delle pr ecedenti alter nativ e in co m binazione
Tutti i modi illustr ati fino ad or a possono esser e uniti in un solo Case per ottener e potenti condizioni di
contr ollo:

    1. Select Case A
    2.     Case 7, 9, 10 To 15, Is >= 90
    3.         'Questo codice viene eseguito solo se A
    4.         'contiene 7 o 9 o un valore compreso tra 10 e 15
    5.         'oppure un valore maggiore o uguale di 90
    6.     Case Else
    7.         '...
    8. End Select
A9. I costrutti iterativi: Do Loop

Abbiamo visto che esistono costr utti per ver ificar e condizioni, o anche per ver ificar e in modo semplice e veloce molte
ugualiglianze. Or a vedr emo i cicli o costr utti iter ativi (dal latino iter , itiner is = "viaggio", ma anche "per la seconda
volta"). Essi hanno il compito di r ipeter e un blocco di istr uzioni un numer o deter minato o indeter minato di volte. Il
pr imo che analizzer emo è, appunto, il costr utto Do Loop, di cui esistono molte var ianti. La più semplice è ha questa
sintassi:

    1. Do
    2.    'istruzioni
    3. Loop

Il suo compito consiste nel r ipete delle istr uzioni compr ese tr a Do e Loop un numer o infinito di volte: l'unico modo per
uscir e dal ciclo è usar e una speciale istr uzione: "Ex it Do", la quale ha la capacità di inter r omper e il ciclo all'istante ed
uscir e da esso. Questa semplice var iante viene usata in un numer o r idotto di casi, che si possono r icondur r e
sostanzialmente a due: quando si lavor a con la gr afica e le libr er ie Dir ectX, per disegnar e a scher mo i costanti
cambiamenti del mondo 2D o 3D; quando è necessar io ver ificar e le condizioni di uscita dal ciclo all'inter no del suo blocco
di codice. Ecco un esempio di questo secondo caso:

   01. Module Module1
   02.
   03.     Sub Main()
   04.         Dim a, b As Single
   05.
   06.         Do
   07.              'Pulisce lo schermo
   08.              Console.Clear()
   09.              'L'underscore serve per andare a capo nel codice
   10.              Console.WriteLine("Inserire le misure di base e altezza " & _
   11.                  "di un rettangolo:")
   12.              a = Console.ReadLine
   13.              b = Console.ReadLine
   14.
   15.              'Controlla che a e b non siano nulli. In quel caso, esce
   16.              'dal ciclo. Se non ci fosse questo If in mezzo al codice,
   17.              'verrebbe scritto a schermo il messaggio:
   18.              ' "L'area del rettangolo è: 0"
   19.              'cosa che noi vogliamo evitare. Se si usasse un'altra
   20.              'variante di Do Loop, questo succederebbe sempre. Ecco
   21.              'perchè, in questa situazione, è meglio
   22.              'servirsi del semplice Do Loop
   23.              If a = 0 Or b = 0 Then
   24.                  Exit Do
   25.              End If
   26.
   27.              Console.WriteLine("L'area del rettangolo è: " & (a * b))
   28.              Console.ReadKey()
   29.         Loop
   30.     End Sub
   31.
   32. End Module

Le altr e ver sioni del costr utto, invece, sono le seguenti:

            1. Do
            2.     'istruzioni
            3. Loop While [condizione]

       Esegue le istr uzioni specificate fintanto che una condizione r imane valida, ma tutte le istr uzioni vengono
       eseguite almeno una volta, poichè While si tr ova dopo Do. Esempio:
01. Module Module1
   02.     Sub Main()
   03.         Dim a As Int32 = 0
   04.
   05.         Do
   06.              a += 1
   07.         Loop While (a < 2) And (a > 0)
   08.         Console.WriteLine(a)
   09.
   10.         Console.ReadKey()
   11.     End Sub
   12. End Module

Il codice scr iver à a scher mo "2".

    1. Do While [condizione]
    2.      'istruzioni
    3. Loop

Esegue le istr uzioni specificate fintanto che una condizione r imane valida, ma se la condizione non è valida
all'inizio, non viene eseguita nessuna istr uzione nel blocco. Esempio:

   01. Module Module1
   02.     Sub Main()
   03.         Dim a As Int32 = 0
   04.
   05.         Do While (a < 2) And (a > 0)
   06.              a += 1
   07.         Loop
   08.         Console.WriteLine(a)
   09.
   10.         Console.ReadKey()
   11.     End Sub
   12. End Module

Il codice scr iver à a scher mo "0". Bisogna notar e come le stesse condizioni del caso pr ecedente, spostate da dopo
Loop a dopo Do, cambino il r isultato di tutto l'algor itmo. In questo caso, il codice nel ciclo non viene neppur e
eseguito per chè la condizione nel While diventa subito falsa (in quanto a = 0, e la pr oposizione "a < 0" r isulta
falsa). Nel caso pr ecedente, invece, il blocco veniva eseguito almeno una volta poiché la condizione di contr ollo si
tr ovava dopo di esso: in quel caso, a er a or mai stato incr ementato di 1 e per ciò soddisfaceva la condizione
affinché il ciclo continuasse (fino ad ar r ivar e ad a = 2, che er a il r isultato visualizzato).

    1. Do
    2.     'istruzioni
    3. Loop Until [condizione]

Esegue le istr uzioni specificate fino a che non viene ver ificata la condizione, ma tutte le istr uzioni vengono
eseguite almeno una volta, poichè Until si tr ova dopo Do. Esempio:

   01. Module Module1
   02.     Sub Main()
   03.         Dim a As Int32 = 0
   04.
   05.         Do
   06.              a += 1
   07.         Loop Until (a <> 1)
   08.         Console.WriteLine(a)
   09.
   10.         Console.ReadKey()
   11.     End Sub
   12. End Module

A scher mo appar ir à "2".

    1. Do Until [condizione]
    2.     'istruzioni
    3.
Loop

       Esegue le istr uzioni specificate fino a che non viene soddisfatta la condizione, ma se la condizione è valida
       all'inizio, non viene eseguita nessuna istr uzione del blocco. Esempio:

          01. Module Module1
          02.     Sub Main()
          03.         Dim a As Int32 = 0
          04.
          05.         Do Until (a <> 1)
          06.              a += 1
          07.         Loop
          08.         Console.WriteLine(a)
          09.
          10.         Console.ReadKey()
          11.     End Sub
          12. End Module

       A scher mo appar ir à "0".

Un piccolo esempio finale:

   01. Module Module1
   02.     Sub Main()
   03.         Dim a, b, c As Int32
   04.         Dim n As Int32
   05.
   06.         Console.WriteLine("-- Successione di Fibonacci --")
   07.         Console.WriteLine("Inserire un numero oltre il quale terminare:")
   08.         n = Console.ReadLine
   09.
   10.         If n = 0 Then
   11.              Console.WriteLine("Nessun numero della successione")
   12.              Console.ReadKey()
   13.              Exit Sub
   14.         End If
   15.
   16.         a = 1
   17.         b = 1
   18.         Console.WriteLine(a)
   19.         Console.WriteLine(b)
   20.         Do While c < n
   21.              c = a + b
   22.              b = a
   23.              a = c
   24.              Console.WriteLine(c)
   25.         Loop
   26.
   27.         Console.ReadKey()
   28.     End Sub
   29. End Module




Suggerimen to
Per impostar e il valor e di Default (ossia il valor e pr edefinito) di una var iabile si può usar e questa sintassi:

    1. Dim [nome] As [tipo] = [valore]

Funziona solo per una var iabile alla volta. Questo tipo di istr uzione si chiama in izializzazion e in -lin e.
A10. I costrutti iterativi: For

Dopo aver visto costr utti iter ativi che eseguono un ciclo un numer o indeter minato di volte, è ar r ivato il momento di
analizzar ne uno che, al contr ar io, esegue un deter minato numer o di iter azioni. La sintassi è la seguente:

    1. Dim I As Int32
    2.
    3. For I = 0 To [numero]
    4.   'istruzioni
    5. Next

La var iabile I, usata in questo esempio, viene definita co ntator e e, ad ogni step, ossia ogni volta che il blocco di
istr uzioni si r ipete, viene automaticamente incr ementata di 1, sicchè la si può usar e all'inter no delle istr uzioni come
un ver o e pr opr io indice, per r ender e conto del punto al quale l'iter azione del For è ar r ivata. Bisogna far notar e che il
tipo usato per la var iabile contator e non deve sempr e esser e Int32, ma può var iar e, spaziando tr a la vasta gamma di
numer i inter i, con segno e senza segno, fino anche ai numer i decimali. Un esempio:

   01. Module Module1
   02.     Sub Main()
   03.         Dim a As Int32
   04.
   05.         'Scrive 46 volte (da 0 a 45, 0 compreso, sono 46 numeri)
   06.         'a schermo 'ciao'
   07.         For a = 0 To 45
   08.              Console.WriteLine("ciao")
   09.         Next
   10.
   11.         Console.ReadKey()
   12.     End Sub
   13. End Module

Ovviamente il valor e di par tenza r imane del tutto ar bitr ar io e può esser e deciso ed inizializzato ad un qualsiasi
valor e:

   01. Module Module1
   02.     Sub Main()
   03.         Dim a, b As Int32
   04.
   05.         Console.WriteLine("Inserisci un numero pari")
   06.         b = Console.ReadLine
   07.
   08.         'Se b non è pari, ossia se il resto della divisione
   09.         'b/2 è diverso da 0
   10.         If b Mod 2 <> 0 Then
   11.              'Lo fa diventare un numero pari, aggiungendo 1
   12.              b += 1
   13.         End If
   14.
   15.         'Scrive tutti i numeri da b a b+20
   16.         For a = b To b + 20
   17.              Console.WriteLine(a)
   18.         Next
   19.
   20.         Console.ReadKey()
   21.     End Sub
   22. End Module

Intr oduciamo or a una piccola var iante del pr ogr amma pr ecedente, nella quale si devono scr iver e solo i numer i par i da
b a b+20. Esistono due modi per r ealizzar e quanto detto. Il pr imo è abbastanza intuitivo, ma meno r affinato, e
consiste nel contr ollar e ad ogni iter azione la par ità del contator e:

    1.
For a = b To b + 20
    2.      If a Mod 2 = 0 Then
    3.          Console.WriteLine(a)
    4.      End If
    5. Next

Il secondo, invece, è più elegante e usa una ver sione "ar r icchita" della str uttur a iter ativa For , nella quale viene
specificato che l'incr emento del contator e non deve più esser e 1, ma bensì 2:

    1. For a = b To b + 20 Step 2
    2.   Console.WriteLine(a)
    3. Next

Infatti, la par ola r iser vata Step posta dopo il numer o a cui ar r ivar e (in questo caso b+20) indica di quanto deve esser e
aumentata la var iabile contator e del ciclo (in questo caso a) ad ogni step. L'incr emento può esser e un valor e inter o,
decimale, positivo o negativo, ma, cosa impor tante, deve sempr e appar tener e al r aggio d'azione del tipo del
contator e: ed esempio, non si può dichiar ar e una var iabile contator e di tipo Byte e un incr emento di -1, poichè Byte
compr ende solo numer i positivi (invece è possibile far lo con SByte, che va da -127 a 128). Allo stesso modo non si
dovr ebber o specificar e incr ementi decimali con contator i inter i.


Suggerimen to
Se non si vuole cr ear e una var iabile apposta per esser e contator e di un ciclo for , si può inzializzar e dir ettamente una
var iabile al suo inter no in questo modo:

    1.   For [variabile] As [tipo] = [valore] To [numero]
    2.     'istruzioni
    3.   Next
    4.   'Che, se volessimo descrivere con un esempio, diverrebbe così:
    5.   For H As Int16 = 78 To 108
    6.     'istruzioni
    7.   Next
A11. Gli Array - Parte I

Array a una dimensione
Fino a questo momento abbiamo avuto a che far e con var iabili "singole". Con questo voglio dir e che ogni identificator e
dichiar ato puntava ad una cella di memor ia dove er a contenuto un solo valor e, leggibile e modificabile usando il nome
specificato nella dichiar azione della var iabile. L'esempio classico che si fa in questo contesto è quello della scatola, dove
una var iabile viene, appunto, assimilata ad una scatola, il cui contenuto può esser e pr eso, modificato e r eimmesso
senza pr oblemi.




Allo stesso modo, un ar r ay è un insieme di scatole, tutte una vicina all'altr a (tanto nell'esempio quando nella posizione
fisica all'inter no della memor ia), a for mar e un'unica fila che per comodità si indica con un solo nome. Per distinguer e
ogni "scompar to" si fa uso di un numer o inter o (che per convenzione è un inter o a 32 bit, ossia Integer ), detto indice.
Tutti i linguaggi .NET utilizzano sempr e un indice a base 0: ciò significa che si inizia a contar e da 0 anziché da 1:




La sintassi usata per dichiar ar e un ar r ay è simile a quella usata per dichiar ar e una singola var iabile:

    1. Dim [nome]([numero elementi - 1]) As [tipo]

La differ enza tr a le due r isiede nelle par entesi tonde che vengono poste dopo il nome della var iabile. Tr a queste
par entesi può anche esser e specificato un numer o (sempr e inter o, ovviamente) che indica l'indice massimo a cui si può
ar r ivar e: dato che, come abbiamo visto, gli indici sono sempr e a base 0, il numer o effettivo di elementi pr esenti nella
collezione sar à di un'unità super ior e r ispetto all'indice massimo. Ad esempio, con questo codice:

    1. Dim A(5) As String

il pr ogr ammator e indica al pr ogr amma che la var iabile A è un ar r ay contenente questi elementi:

    1. A(0), A(1), A(2), A(3), A(4), A(5)

che sono per la pr ecisione 6 elementi. Ecco un listato che esemplifica i concetti fin'or a chiar iti:

   01. Module Module1
   02.     Sub Main()
   03.         'Array che contiene 10 valori decimali, rappresentanti voti
   04.         Dim Marks(9) As Single
   05.         'Questa variabile terrà traccia di quanti voti
   06.         'l'utente avrà immesso da tastiera e permetterà di
   07.         'calcolarne una media
   08.         Dim Index As Int32 = 0
   09.
   10.         'Mark conterrà il valore temporaneo immesso
   11.         'da tastiera dall'utente
   12.         Dim Mark As Single
   13.         Console.WriteLine("Inserisci un altro voto (0 per terminare):")
   14.         Mark = Console.ReadLine
   15.
   16.         'Il ciclo finisce quando l'utente immette 0 oppure quando
   17.         'si è raggiunto l'indice massimo che è
   18.         'possibile usare per identificare una cella dell'array
   19.         Do While (Mark > 0) And (Index < 10)
   20.              'Se il voto immesso è maggiore di 0, lo memorizza
   21.              'nell'array e incrementa l'indice di 1, così da
   22.              'poter immagazzinare correttamente il prossimo voto nell'array
   23.              Marks(Index) = Mark
   24.              Index += 1
   25.
   26.              Console.WriteLine("Inserisci un altro voto (0 per terminare):")
   27.              Mark = Console.ReadLine
   28.         Loop
   29.         'Decrementa l'indice di 1, poiché anche se l'utente
   30.         'ha immesso 0, nel ciclo precedente, l'indice era stato
   31.         'incrementato prevedendo un'ulteriore immissione, che,
   32.         'invece, non c'è stata
   33.         Index -= 1
   34.
   35.         'Totale dei voti
   36.         Dim Total As Single = 0
   37.         'Usa un ciclo For per scorrere tutte le celle dell'array
   38.         'e sommarne i valori
   39.         For I As Int32 = 0 To Index
   40.              Total += Marks(I)
   41.         Next
   42.
   43.         'Mostra la media
   44.         Console.WriteLine("La tua media è: " & (Total / (Index + 1)))
   45.         Console.ReadKey()
   46.     End Sub
   47. End Module

Il codice potr ebbe non appar ir e subito chiar o a pr ima vista, ma attr aver so uno sguar do più attento, tutto si far à più
limpido. Di seguito è scr itto il flusso di elabor azione del pr ogr amma ammettendo che l'utente immetta due voti:

       Richiede un voto da tastier a: l'utente immette 5 (Mar k = 5)
       Mar k è maggior e di 0
               Inser isce il voto nell'ar r ay: Mar ks(Index ) = Mar ks(0) = 5
               Incr ementa Index di 1: Index = 1
Entr ambe le condizioni non sono ver ificate: Mar k <> 0 e Index < 9. Il ciclo continua
         Richiede un voto da tastier a: l'utente immette 10 (Mar k = 10)
         Mar k è maggior e di 0
                Inser isce il voto nell'ar r ay: Mar ks(Index ) = Mar ks(1) = 10
                Incr ementa Index di 1: Index = 2
         Entr ambe le condizioni non sono ver ificate: Mar k <> 0 e Index < 9. Il ciclo continua
         Richiede un voto da tastier a: l'utente immette 0 (Mar k = 0)
         Mar k è uguale a 0: il codice dentr o if non viene eseguito
         Una delle condizioni di ar r esto è ver ificata: Mar k = 0. Il ciclo ter mina
         Decr ementa Index di 1: Index = 1
         Somma tutti i valor i in Mar ks da 0 a Index (=1): Total = Mar ks(0) + Mar ks(1) = 5 + 10
         Visualizza la media: Total / (Index + 1) = 15 / (1 + 1) = 15 / 2 = 7.5
         Attende la pr essione di un tasto per uscir e

È anche possibile dichiar ar e ed inizializzar e (ossia r iempir e) un ar r ay in una sola r iga di codice. La sintassi usata è la
seguente:

    1. Dim [nome]() As [tipo] = {elementi dell'array separati da virgole}

Ad esempio:

    1. Dim Parole() As String = {"ciao", "mouse", "penna"}

Questa sintassi br eve equivale a questo codice:

    1.    Dim Parole(2) As String
    2.    Parole(0) = "ciao"
    3.    Parole(1) = "mouse"
    4.    Parole(2) = "penna"

Un'ulter ior e sintassi usata per dichiar ar e un ar r ay è la seguente:

    1. Dim [nome] As [tipo]()

Quest'ultima, come vedr emo, sar à par ticolar mente utile nel gestir e il tipo r estituito da una funzione.




Array a più dimensioni
Gli ar r ay a una dimensione sono contr addistinti da un singolo indice: se volessimo par agonar li ad un ente geometr ico,
sar ebber o assimilabili ad una r etta, estesa in una sola dimensione, in cui ogni punto r appr esenta una cella dell'ar r ay.
Gli ar r ay a più dimensioni, invece, sono contr addistinti da più di un indice: il numer o di indici che identifica
univocamente un elemento dell'ar r ay di dice r ang o . Un ar r ay di r ango 2 (a 2 dimensioni) potr à, quindi, esser e
par agonato a un piano, o ad una gr iglia di scatole estesa in lunghezza e in lar ghezza. La sintassi usata è:

    1. Dim [nome]( , ) As [tipo] 'array di rango 2
    2. Dim [nome]( , , ) As [tipo] 'array di rango 3

Ecco un esempio che consider a un ar r ay di r ango 2 come una matr ice quadr ata:

   01. Module Module1
   02.     Sub Main()
   03.         'Dichiara e inizializza un array di rango 2. Dato che
   04.         'in questo caso abbiamo due dimensioni, e non una sola,
   05.         'non si può specificare una semplice lista di
   06.         'valori, ma una specie di "tabella" a due entrate.
   07.         'Nell'esempio che segue, ho creato una semplice
   08.         'tabella a due righe e due colonne, in cui ogni cella
   09.         'è 0.
   10.
Dim M(,) As Single = _
   11.              {{0, 0}, _
   12.              {0, 0}}
   13.         'Bisogna notare il particolare uso delle graffe: si
   14.         'considera l'array di rango 2 come un array i cui
   15.         'elementi sono altri array
   16.
   17.         Console.WriteLine("Inserire gli elementi della matrice:")
   18.         For I As Int32 = 0 To 1
   19.              For J As Int32 = 0 To 1
   20.                   Console.Write("Inserire l'elemento (" & I & ", " & J & "): ")
   21.                   M(I, J) = Console.ReadLine
   22.              Next
   23.         Next
   24.
   25.         Dim Det As Single
   26.         Det = M(0, 0) * M(1, 1) - M(0, 1) * M(1, 0)
   27.         Console.WriteLine("Il determinante della matrice è: " & Det)
   28.
   29.         Console.ReadKey()
   30.     End Sub
   31. End Module

Rappr esentando gr aficamente l'ar r ay M, potr emmo disegnar lo così:




Ma il computer lo può anche veder e in questo modo, come un ar r ay di ar r ay:
Come si vede dal codice di inizializzazione, seppur concettualmente diver si, i due modi di veder e un ar r ay sono
compatibili. Tuttavia, bisogna chiar ir e che so lo e so ltanto in questo caso, le due visioni sono conciliabili, poiché un
ar r ay di r ango 2 e un ar r ay di ar r ay sono, dopo tutto, due entità ben distinte. Infatti, esiste un modo per dichiar ar e
ar r ay di ar r ay, come segue:

    1. Dim [nome]()() As [tipo] 'array di array

E se si pr ova a far e una cosa del gener e:

    1.   Dim A(,) As Int32
    2.   Dim B()() As Int32
    3.   '...
    4.   A = B

Si r iceve un er r or e esplicito da par te del compilator e.




Ridimensionare un array
Può capitar e di dover modificar e la lunghezza di un ar r ay r ispetto alla dichiar azione iniziale. Per far e questo, si usa la
par ola r iser vata ReDim, da non confonder e con la keyw or d Dim: hanno due funzioni totalmente differ enti. Quando si
r idimensiona un ar r ay, tutto il suo contenuto viene cancellato: per evitar e questo inconveniente, si deve usar e
l'istr uzione ReDim Pres erve, che tuttavia ha pr estazioni molto scar se a causa dell'eccessiva lentezza. Entr ambe le
istr uzioni der ivano dal Visual Basic classico e non fanno par te, per tanto, della sintassi .NET, sebbene continuino ad
esser e molto usate, sia per comodità, sia per abitudine. Il metodo più cor r etto da adottar e consiste nell'usar e la
pr ocedur a Ar r ay.Resize. Eccone un esempio:

   01. Module Module1
   02.     Sub Main()
   03.         Dim A() As Int32
   04.         Dim n As Int32
   05.
   06.         Console.WriteLine("Inserisci un numero")
   07.         n = Console.ReadLine
   08.
   09.         'Reimposta la lunghezza di a ad n elementi
   10.         Array.Resize(A, n)
   11.
   12.         'Calcola e memorizza i primi n numeri pari (zero compreso)
   13.         For I As Int16 = 0 To n - 1
   14.              A(I) = I * 2
   15.         Next
   16.
   17.         Console.ReadKey()
   18.     End Sub
   19. End Module

La r iga Ar r ay.Resize(A, n) equivale, usando ReDim a:

    1. ReDim A(n - 1)

Per r idimensionar e un ar r ay a più dimensioni, la faccenda si fa abbastanza complessa. Per pr ima cosa, non si può
utilizzar e Ar r ay.Resize a meno che non si utilizzi un ar r ay di ar r ay, ma anche in quel caso le cose non sono semplici.
Infatti, è possibile stabilir e la lunghezza di una sola dimensione alla volta. Ad esempio, avendo un ar r ay M di r ango 2
con nove elementi, r aggr uppati in 3 r ighe e 3 colonne, non si può semplicemente scr iver e:

    1. ReDim M(2, 2)
per chè, così facendo, solo la r iga 2 ver r à r idimensionata a 3 elementi, mentr e la 0 e la 1 sar anno vuote. Il codice da
usar e, quindi, è:

    1. ReDim M(0, 2)
    2. ReDim M(1, 2)
    3. ReDim M(2, 2)

In questo modo, ogni "r iga" viene aggiustata alla lunghezza giusta.
A12. Gli Array - Parte II

Il c ostrutto iterativo For Eac h
Questo costr utto iter ativo è simile al nor male For , ma, invece di aver e una var iabile contator e numer ica, ha una
var iabile contator e di var io tipo. In sostanza, questo ciclo iter a attr aver so una ar r ay o una collezione di altr o gener e,
selezionando, di volta in volta, l'elemento che si tr ova alla posizione cor r ente nell'ar r ay. Il suo funzionamento intr inseco
è tr oppo complesso da spiegar e or a, quindi lo affr onter ò solamente nei capitoli dedicati alle inter facce, in par ticolar e
par lando dell'inter faccia IEnumer able. La sintassi è la seguente:

    1. Dim A As [tipo]
    2. For Each A In [array/collezione]
    3.      'istruzioni
    4. Next

Ovviamente anche in questo caso, come nel nor male For , è possibile inizializzar e una var iabile contator e all'inter no del
costr utto:

    1. For Each A As [tipo] in [array/collezione] ...

Esempio:

   01. Module Module1
   02.     Sub Main()
   03.         Dim Words() As String = {"Questo", "è", "un", "array", "di", "stringhe"}
   04.
   05.         For Each Str As String In Words
   06.              Console.Write(Str & " ")
   07.         Next
   08.
   09.         'A schermo apparirà la frase:
   10.         ' "Questo è un array di stringhe "
   11.
   12.         Console.ReadKey()
   13.     End Sub
   14. End Module

Per aver e un ter mine di par agone, il semplicissimo codice pr oposto equivale, usando un for nor male, a questo:

    1.   'Words.Length restituisce il numero di elementi
    2.   'presenti nell'array Words
    3.   For I As Int32 = 0 To Words.Length - 1
    4.        Console.Write(Words(I) & " ")
    5.   Next




Gli array sono un tipo referenc e
Diver samente da come accade in altr i linguaggi, gli ar r ay sono un tipo r efer ence, indipendentemente dal tipo di dati
da essi contenuto. Ciò significa che si compor tano come ho spiegato nel capitolo "Tipi r efer ence e tipi value": l'ar ea di
memor ia ad essi associata non contiene il lor o valor e, ma un puntator e alla lor o posizione nell'heap managed. Questo
significa che l'oper ator e = tr a due ar r ay non copia il contenuto di uno nell'altr o, ma li r ende identici, ossia lo stesso
oggetto. Per lo stesso motivo, è anche lecito distr ugger e logicamente un ar r ay ponendolo uguale a Nothing: questa
oper azione può salvar e un discr eto ammontar e di memor ia, ad esempio quando si usano gr andi ar r ay per la lettur a di
file binar i, ed è sempr e bene annullar e un ar r ay dopo aver lo usato.

   01. Module Module1
   02.     Sub Main()
   03.
'A e B sono due array di interi
  04.         Dim A() As Int32 = {1, 2, 3}
  05.         Dim B() As Int32 = {4, 5, 6}
  06.
  07.         'Ora A e B sono due oggetti diversi e contengono
  08.         'numeri diversi. Questa riga stamperà sullo
  09.         'schermo "False", infatti A Is B = False
  10.         Console.WriteLine(A Is B)
  11.         'Adesso poniamo A uguale a B. Dato che gli array
  12.         'sono un tipo reference, da ora in poi, entrambi
  13.         'saranno lo stesso oggetto
  14.         A = B
  15.         'Infatti questa istruzione stamperà a schermo
  16.         ''"True", poiché A Is B = True
  17.         Console.WriteLine(A Is B)
  18.         'Dato che A e B sono lo stesso oggetto, se modifichiamo
  19.         'un valore dell'array riferendoci ad esso con il nome
  20.         'B, anche richiamandolo con A, esso mostrerà
  21.         'che l'ultimo elemento è lo stesso
  22.         B(2) = 90
  23.         'Su schermo apparirà 90
  24.         Console.WriteLine(A(2))
  25.
  26.         Dim C() As Int32 = {7, 8, 9}
  27.         B = C
  28.         'Ora cosa succede?
  29.
  30.         Console.ReadKey()
  31.     End Sub
  32. End Module

Ecco come appar e la memor ia dopo l'assegnazione A = B:




Ed ecco come appar e dopo l'assegnazione B = C:
Come si vede, le var iabili contengono solo l'indir izzo degli oggetti effettivi, per ciò ogni singola var iabile (A, B o C) può
puntar e allo stesso oggetto ma anche a oggetti diver si: se A = B e B = C, non è ver o che A = C, come si vede dal gr afico.
L'indir izzo di memor ia contenuto in A non cambia se non si usa esplicitamente un oper ator e di assegnamento.
Se state leggendo la guida un capitolo alla volta, potete fer mar vi qui: il pr ossimo par agr afo è utile solo per
consultazione.




Manipolazione di array
La classe System.Ar r ay contiene molti metodi statici utili per la manipolazione degli ar r ay. I più usati sono:

       Clear (A, I, L) : cancella L elementi a par tir e dalla posizione I nell'ar r ay A
       Clone() : cr ea una coppia esatta dell'ar r ay
       Constr ainedCopy(A1, I1, A2, I2, L) : copia L elementi dall'ar r ay A1 a par tir e dall'indice I1 nell'ar r ay A2, a par tir e
       dall'indice I2; se la copia non ha successo, ogni cambiamento sar à annullato e l'ar r ay di destinazione non subir à
       alcun danno
       Copy(A1, A2, L) / CopyTo(A1, A2) : il pr imo metodo copia L elementi da A1 a A2 a par tir e dal pr imo, mentr e il
       secondo fa una copia totale dell'ar r ay A1 e la deposita in A2
       Find / FindLast (A, P(Of T)) As T : cer ca il pr imo elemento dell'ar r ay A per il quale la funzione gener ic Of T
       assegnata al delegate P r estituisce un valor e Tr ue, e ne r itor na il valor e
       Find(A, P(Of T)) As T() : cer ca tutti gli elementi dell'ar r ay A per i quali la funzione gener ic Of T assegnata al
       delegate P r estituisce un valor e Tr ue
       FindIndex / FindLastIndex (A, P(Of T)) As Int32 : cer ca il pr imo o l'ultimo elemento dell'ar r ay A per il quale la
       funzione gener ic Of T assegnata al delegate P r estituisce un valor e Tr ue, e ne r itor na l'indice
       For Each(A(Of T)) : esegue un'azione A deter minata da un delegate Sub per ogni elemento dell'ar r ay
       GetLength(A) : r estituisce la dimensione dell'ar r ay
       Index Of(A, T) / LastIndex Of(A, T) : r estituisce il pr imo o l'ultimo indice dell'oggetto T nell'ar r ay A
       Rever se(A) : inver te l'or dine di tutti gli elementi nell'ar r ay A
       Sor t(A) : or dina alfabeticamente l'ar r ay A. Esistono 16 ver sioni di questa pr ocedur a, tr a le quali una accetta
come secondo par ametr o un oggetto che implementa un'inter faccia ICompar er che per mette di decider e come
       or dinar e l'ar r ay

Molti di questi metodi, come si è visto, compr endono ar gomenti molto avanzati: quando sar ete in gr ado di
compr ender e i Gener ics e i Delegate, r itor nate a far e un salto in questo capitolo: scopr ir ete la potenza di questi
metodi.
A13. I Metodi - Parte I

Anatomia di un metodo
Il Fr amew or k .NET mette a disposizione dello sviluppator e un enor me numer o di classi contenenti metodi davver o utili,
già scr itti e pr onti all'uso, ma solo in pochi casi questi bastano a cr ear e un'applicazione ben str uttur ata ed elegante.
Per questo motivo, è possibile cr ear e nuovi metodi - pr ocedur e o funzioni che siano - ed usar li comodamente nel
pr ogr amma. Per lo più, si cr ea un metodo per separ ar e logicamente una cer ta par te di codice dal r esto del sor gente:
questo ser ve in pr imis a r ender e il listato più leggibile, più consultabile e meno pr olisso, ed inoltr e ha la funzione di
r acchiuder e sotto un unico nome (il nome del metodo) una ser ie più o meno gr ande di istr uzioni.
Un metodo è costituito essenzialmente da tr e par ti:

        Nome : un identificator e che si può usar e in altr e par ti del pr ogr amma per in vocare il metodo, ossia per
        eseguir e le istr uzioni di cui esso consta;
        Elenco dei par ametr i : un elenco di var iabili attr aver so i quali il metodo può scambiar e dati con il pr ogr amma;
        Cor po : contiene il codice effettivo associato al metodo, quindi tutte le istr uzioni e le oper azioni che esso deve
        eseguir e

Ma or a scendiamo un po' più nello specifico...




Proc edure senza parametri
Il caso più semplice di metodo consiste in una pr ocedur a senza par ametr i: essa costituisce, gr osso modo, un
sottopr ogr amma a sè stante, che può esser e r ichiamato semplicemente scr ivendone il nome. La sua sintassi è molto
semplice:

    1. Sub [nome]()
    2.     'istruzioni
    3. End Sub

Cr edo che vi sia subito balzato agli occhi che questo è esattamente lo stesso modo in cui viene dichiar ata la Sub Main:
per tanto, or a posso dir lo, Main è un metodo e, nella maggior par te dei casi, una pr ocedur a senza par ametr i (ma si
tr atta solo di un caso par ticolar e, come vedr emo fr a poco). Quando il pr ogr amma inizia, Main è il pr imo metodo
eseguito: al suo inter no, ossia nel suo cor po, r isiede il codice del pr ogr amma. Inoltr e, poiché facenti par ti del nover o
delle entità pr esenti in una classe, i metodi sono membr i di classe: devono, per ciò, esser e dichiar ati a livello di clas s e.
Con questa locuzione abbastanza comune nell'ambito della pr ogr ammazione si intende l'atto di dichiar ar e qualcosa
all'inter no del cor po di una classe, ma fuor i dal cor po di un qualsiasi suo membr o. Ad esempio, la dichiar azione seguente
è cor r etta:

   01. Module Module1
   02.     Sub Esempio()
   03.         'istruzioni
   04.     End Sub
   05.
   06.     Sub Main()
   07.         'istruzioni
   08.     End Sub
   09. End Module

mentr e la pr ossima è SBAGLI ATA:

    1. Module Module1
    2.     Sub Main()
    3.
Sub Esempio()
    4.             'istruzioni
    5.         End Sub
    6.         'istruzioni
    7.     End Sub
    8. End Module

Allo stesso modo, i metodi sono l'unica categor ia, oltr e alle pr opr ietà e agli oper ator i, a poter contener e delle
istr uzioni: sono str umenti "attivi" di pr ogr ammazione e solo lor o possono eseguir e istr uzioni. Quindi astenetevi dallo
scr iver e un abominio del gener e:

    1. Module Module1
    2.     Sub Main()
    3.         'istruzioni
    4.     End Sub
    5.     Console.WriteLine()
    6. End Sub

E' totalmente e concettualmente sbagliato. Ma or a veniamo al dunque con un esempio:

   01. Module Module1
   02.     'Dichiarazione di una procedura: il suo nome è "FindDay", il
   03.     'suo elenco di parametri è vuoto, e il suo corpo è
   04.     'rappresentato da tutto il codice compreso tra "Sub FindDay()"
   05.     'ed "End Sub".
   06.     Sub FindDay()
   07.         Dim StrDate As String
   08.         Console.Write("Inserisci giorno (dd/mm/yyyy): ")
   09.         StrDate = Console.ReadLine
   10.
   11.         Dim D As Date
   12.         'La funzione Date.TryParse tenta di convertire la stringa
   13.         'StrDate in una variabile di tipo Date (che è un tipo
   14.         'base). Se ci riesce, ossia non ci sono errori nella
   15.         'data digitata, restituisce True e deposita il valore
   16.         'ottenuto in D; se, al contrario, non ci riesce,
   17.         'restituisce False e D resta vuota.
   18.         'Quando una data non viene inizializzata, dato che è un
   19.         'tipo value, contiene un valore predefinito, il primo
   20.         'Gennaio dell'anno 1 d.C. a mezzogiorno in punto.
   21.         If Date.TryParse(StrDate, D) Then
   22.              'D.DayOfWeek contiene il giorno della settimana di D
   23.              '(lunedì, martedì, eccetera...), ma in un
   24.              'formato speciale, l'Enumeratore, che vedremo nei
   25.              'prossimi capitoli.
   26.              'Il ".ToString()" converte questo valore in una
   27.              'stringa, ossia in un testo leggibile: i giorni della
   28.              'settimana, però, sono in inglese
   29.              Console.WriteLine(D.DayOfWeek.ToString())
   30.         Else
   31.              Console.WriteLine(StrDate & " non è una data valida!")
   32.         End If
   33.     End Sub
   34.
   35.     'Altra procedura, simile alla prima
   36.     Sub CalculateDaysDifference()
   37.         Dim StrDate1, StrDate2 As String
   38.         Console.Write("Inserisci il primo giorno (dd/mm/yyyy): ")
   39.         StrDate1 = Console.ReadLine
   40.         Console.Write("Inserisci il secondo giorno (dd/mm/yyyy): ")
   41.         StrDate2 = Console.ReadLine
   42.
   43.         Dim Date1, Date2 As Date
   44.
   45.         If Date.TryParse(StrDate1, Date1) And _
   46.             Date.TryParse(StrDate2, Date2) Then
   47.              'La differenza tra due date restituisce il tempo
   48.              'trascorso tra l'una e l'altra. In questo caso noi
   49.              'prendiamo solo i giorni
   50.              Console.WriteLine((Date2 - Date1).Days)
   51.
Else
   52.                Console.WriteLine("Inserire due date valide!")
   53.            End If
   54.        End Sub
   55.
   56.        Sub Main()
   57.            'Command è una variabile di tipo char (carattere) che
   58.            'conterrà una lettera indicante quale compito eseguire
   59.            Dim Command As Char
   60.
   61.             Do
   62.                    Console.Clear()
   63.                    Console.WriteLine("Qualche operazione con le date:")
   64.                    Console.WriteLine("- Premere F per sapere in che giorno " & _
   65.                        "della settimana cade una certa data;")
   66.                    Console.WriteLine("- Premere D per calcolare la differenza tra due date;")
   67.                    Console.WriteLine("- Premere E per uscire.")
   68.                    'Console.ReadKey() è la funzione che abbiamo sempre
   69.                    'usato fin'ora per fermare il programma in attesa della
   70.                    'pressione di un pulsante. Come vedremo fra breve, non
   71.                    'è necessario usare il valore restituito da una
   72.                    'funzione, ma in questo caso ci serve. Ciò che
   73.                    'ReadKey restituisce è qualcosa che non ho ancora
   74.                    'trattato. Per ora basti sapere che
   75.                    'Console.ReadKey().KeyChar contiene l'ultimo carattere
   76.                    'premuto sulla tastiera
   77.                    Command = Console.ReadKey().KeyChar
   78.                    'Analizza il valore di Command
   79.                    Select Case Command
   80.                        Case "f"
   81.                            'Invoca la procedura FindDay()
   82.                            FindDay()
   83.                        Case "d"
   84.                            'Invoca la procedura CalculateDaysDifference()
   85.                            CalculateDaysDifference()
   86.                        Case "e"
   87.                            'Esce dal ciclo
   88.                            Exit Do
   89.                        Case Else
   90.                            Console.WriteLine("Comando non riconosciuto!")
   91.                    End Select
   92.                    Console.ReadKey()
   93.            Loop
   94.        End Sub
   95. End    Module

In questo pr imo caso, le due pr ocedur e dichiar ate sono effettivamente sottopr ogr ammi a sé stanti: non hanno nulla in
comune con il modulo (eccetto il semplice fatto di esser ne membr i), né con Main, ossia non scambiano alcun tipo di
infor mazione con essi; sono come degli ingr anaggi sigillati all'inter no di una scatola chiusa. A questo r iguar do, bisogna
inser ir e una pr ecisazione sulle var iabili dichiar ate ed usate all'inter no di un metodo, qualsiasi esso sia. Esse si dicono
lo cali o tem po r anee, poiché esistono solo all'inter no del metodo e vengono distr utte quando il flusso di elabor azione
ne r aggiunge la fine. Anche sotto questo aspetto, si può notar e come le pr ocedur e appena stilate siano par ticolar mente
chiuse e r estr ittive. Tuttavia, si può benissimo far inter agir e un metodo con oggetti ed entità ester ne, e questo
appr occio è decisamente più utile che non il semplice impacchettar e ed etichettar e blocchi di istr uzioni in locazioni
distinte. Nel pr ossimo esempio, la pr ocedur a attinge dati dal modulo, poiché in esso è dichiar ata una var iabile a livello
di classe.

   01. Module Module1
   02.     'Questa variabile è dichiarata a livello di classe
   03.     '(o di modulo, in questo caso), perciò è accessibile
   04.     'a tutti i membri del modulo, sempre seguendo il discorso
   05.     'dei blocchi di codice fatto in precedenza
   06.     Dim Total As Single = 0
   07.
   08.     'Legge un numero da tastiera e lo somma al totale
   09.     Sub Sum()
   10.         Dim Number As Single = Console.ReadLine
   11.
Total += Number
   12.        End Sub
   13.
   14.        'Legge un numero da tastiera e lo sottrae al totale
   15.        Sub Subtract()
   16.            Dim Number As Single = Console.ReadLine
   17.            Total -= Number
   18.        End Sub
   19.
   20.        'Legge un numero da tastiera e divide il totale per
   21.        'tale numero
   22.        Sub Divide()
   23.            Dim Number As Single = Console.ReadLine
   24.            Total /= Number
   25.        End Sub
   26.
   27.        'Legge un numero da tastiera e moltiplica il totale
   28.        'per tale numero
   29.        Sub Multiply()
   30.            Dim Number As Single = Console.ReadLine
   31.            Total *= Number
   32.        End Sub
   33.
   34.        Sub Main()
   35.            'Questa variabile conterrà il simbolo matematico
   36.            'dell'operazione da eseguire
   37.            Dim Operation As Char
   38.            Do
   39.                 Console.Clear()
   40.                 Console.WriteLine("Risultato attuale: " & Total)
   41.                 Operation = Console.ReadKey().KeyChar
   42.                 Select Case Operation
   43.                     Case "+"
   44.                         Sum()
   45.                     Case "-"
   46.                         Subtract()
   47.                     Case "*"
   48.                         Multiply()
   49.                     Case "/"
   50.                         Divide()
   51.                     Case "e"
   52.                         Exit Do
   53.                     Case Else
   54.                         Console.WriteLine("Operatore non riconosciuto")
   55.                         Console.ReadKey()
   56.                 End Select
   57.            Loop
   58.        End Sub
   59. End    Module




Proc edure c on parametri
Avviandoci ver so l'inter azione sempr e maggior e del metodo con l'ambiente in cui esso esiste, tr oviamo le pr ocedur e
con par ametr i. Al contr ar io delle pr ecedenti, esse possono r icever e e scambiar e dati con il chiaman te: con quest'ultimo
ter mine ci si r ifer isce alla gener ica entità all'inter no della quale il metodo in questione è stato invocato. I par ametr i
sono come delle var iabili locali fittizie: esistono solo all'inter no del cor po, ma non sono dichiar ate in esso, bensì
nell'elenco dei par ametr i. Tale elenco deve esser e specificato dopo il nome del metodo, r acchiuso da una coppia di
par entesi tonde, e ogni suo elemento deve esser e separ ato dagli altr i da vir gole.

    1. Sub [nome](ByVal [parametro1] As [tipo], ByVal [parametro2] As [tipo], ...)
    2.     'istruzioni
    3. End Sub

Come si vede, anche la dichiar azione è abbastanza simile a quella di una var iabile, fatta eccezione per la par ola
r iser vata ByVal, di cui tr a poco vedr emo l'utilià. Per intr odur r e semplicemente l'ar gomento, facciamo subito un
esempio, r iscr ivendo l'ultimo codice pr oposto nel par agr afo pr ecedente con l'aggiunta dei par ametr i:

   01. Module Module1
   02.     Dim Total As Single = 0
   03.
   04.     Sub Sum(ByVal Number As Single)
   05.         Total += Number
   06.     End Sub
   07.
   08.     Sub Subtract(ByVal Number As Single)
   09.         Total -= Number
   10.     End Sub
   11.
   12.     Sub Divide(ByVal Number As Single)
   13.         Total /= Number
   14.     End Sub
   15.
   16.     Sub Multiply(ByVal Number As Single)
   17.         Total *= Number
   18.     End Sub
   19.
   20.     Sub Main()
   21.         'Questa variabile conterrà il simbolo matematico
   22.         'dell'operazione da eseguire
   23.         Dim Operation As Char
   24.         Do
   25.              Console.Clear()
   26.              Console.WriteLine("Risultato attuale: " & Total)
   27.              Operation = Console.ReadKey().KeyChar
   28.              Select Case Operation
   29.                  'Se si tratta di simboli accettabili
   30.                  Case "+", "-", "*", "/"
   31.                      'Legge un numero da tastiera
   32.                      Dim N As Single = Console.ReadLine
   33.                      'E a seconda dell'operazione, utilizza una
   34.                      'procedura piuttosto che un'altra
   35.                      Select Case Operation
   36.                           Case "+"
   37.                               Sum(N)
   38.                           Case "-"
   39.                               Subtract(N)
   40.                           Case "*"
   41.                               Multiply(N)
   42.                           Case "/"
   43.                               Divide(N)
   44.                      End Select
   45.                  Case "e"
   46.                      Exit Do
   47.                  Case Else
   48.                      Console.WriteLine("Operatore non riconosciuto")
   49.                      Console.ReadKey()
   50.              End Select
   51.         Loop
   52.     End Sub
   53. End Module

Richiamando, ad esempio, Sum(N) si invoca la pr ocedur a Sum e si assegna al par ametr o Number il valor e di N: quindi,
Number viene sommato a Total e il ciclo continua. Number , per ciò, è un "segnaposto", che r iceve solo dur ante
l'esecuzione un valor e pr eciso, che può anche esser e, come in questo caso, il contenuto di un'altr a var iabile. Nel ger go
tecnico, Number - ossia, più in gener ale, l'identificator e dichiar ato nell'elenco dei par ametr i - si dice par am etr o
for m ale, mentr e N - ossia ciò che viene concr etamente pas s ato al metodo - si dice par am etr o attuale. Non ho
volutamente assegnato al par ametr o attuale lo stesso nome di quello for male, anche se è del tutto lecito far lo: ho agito
in questo modo per far capir e che non è necessar io nessun legame par ticolar e tr a i due; l'unico vincolo che deve
sussister e r isiede nel fatto che par ametr o for male ed attuale abbiano lo stesso tipo. Quest'ultima asser zione, del r esto,
è abbastanza ovvia: se r ichiamassimo Sum("ciao") come far ebbe il pr ogr amma a sommar e una str inga ("ciao") ad un
numer o (Total)?
Or a pr oviamo a modificar e il codice pr ecedente r iassumendo tutte le oper azioni in una sola pr ocedur a, a cui, per ò,
vengono passati due par ametr i: il numer o e l'oper ator e da usar e.

   01. Module Module1
   02.     Dim Total As Single = 0
   03.
   04.     Sub DoOperation(ByVal Number As Single, ByVal Op As Char)
   05.         Select Case Op
   06.              Case "+"
   07.                  Total += Number
   08.              Case "-"
   09.                  Total -= Number
   10.              Case "*"
   11.                  Total *= Number
   12.              Case "/"
   13.                  Total /= Number
   14.         End Select
   15.     End Sub
   16.
   17.     Sub Main()
   18.         Dim Operation As Char
   19.         Do
   20.              Console.Clear()
   21.              Console.WriteLine("Risultato attuale: " & Total)
   22.              Operation = Console.ReadKey().KeyChar
   23.              Select Case Operation
   24.                  Case "+", "-", "*", "/"
   25.                       Dim N As Single = Console.ReadLine
   26.                       'A questa procedura vengono passati due
   27.                       'parametri: il primo è il numero da
   28.                       'aggiungere/sottrarre/moltiplicare/dividere
   29.                       'a Total; il secondo è il simbolo
   30.                       'matematico che rappresenta l'operazione
   31.                       'da eseguire
   32.                       DoOperation(N, Operation)
   33.                  Case "e"
   34.                       Exit Do
   35.                  Case Else
   36.                       Console.WriteLine("Operatore non riconosciuto")
   37.                       Console.ReadKey()
   38.              End Select
   39.         Loop
   40.     End Sub
   41. End Module




Passare parametri al programma da riga di c omando
Come avevo accennato in pr ecedenza, non è sempr e ver o che Main è una pr ocedur a senza par ametr i. Infatti, è
possibile dichiar ar e Main in un altr o modo, che le consente di ottener e infor mazioni quando il pr ogr amma viene
eseguito da r ig a di co m ando . In quest'ultimo caso, Main viene dichiar ata con un solo ar gomento, un ar r ay di str inghe:

   01. Module Module1
   02.     Sub Main(ByVal Args() As String)
   03.         If Args.Length = 0 Then
   04.              'Se la lunghezza dell'array è 0, significa che è vuoto
   05.              'e quindi non è stato passato nessun parametro a riga
   06.              'di comando. Scrive a schermo come utilizzare
   07.              'il programma
   08.              Console.WriteLine("Utilizzo: nomeprogramma.exe tuonome")
   09.         Else
   10.              'Args ha almeno un elemento. Potrebbe anche averne di
   11.              'più, ma a noi interessa solo il primo.
   12.              'Saluta l'utente con il nome passato da riga di comando
   13.              Console.WriteLine("Ciao " & Args(0) & "!")
   14.         End If
   15.         Console.ReadKey()
   16.     End Sub
   17.
End Module

Per pr ovar lo, potete usar e cmd.ex e, il pr ompt dei comandi. Io ho digitato:

    1. CD "C:UsersTotemDocumentsVisual Studio 2008ProjectsConsoleApplication2binDebug"
    2. ConsoleApplication2.exe Totem

La pr ima istr uzione per cambiar e la dir ector y di lavor o, la seconda l'invocazione ver a e pr opr ia del pr ogr amma, dove
"Totem" è l'unico ar gomento passatogli: una volta pr emuto invio, appar ir à il messaggio "Ciao Totem!". In alter nativa, è
possibile specificar e gli ar gomenti passati nella casella di testo "Command line ar guments" pr esente nella scheda Debug
delle pr opr ietà di pr ogetto. Per acceder e alle pr opr ietà di pr ogetto, cliccate col pulsante destr o sul nome del pr ogetto
nella finestr a a destr a, quindi scegliete Pr oper ties e r ecatevi alla tabella Debug:
A14. I Metodi - Parte II

By V al e By Ref
Nel capitolo pr ecedente, tutti i par ametr i sono stati dichiar anti anteponendo al lor o nome la keyw or d ByVal. Essa ha il
compito di comunicar e al pr ogr amma che al par ametr o for male deve esser e passata una co pia del par ametr o attuale.
Questo significa che qualsiasi codice sia scr itto entr o il cor po del metodo, ogni manipolazione e ogni oper azione
eseguita su quel par ametr o agisce, di fatto, su un 'altra variabile, tempor anea, e no n sul par ametr o attuale for nito.
Ecco un esempio:

   01. Module Module1
   02.     Sub Change(ByVal N As Int32)
   03.         N = 30
   04.     End Sub
   05.
   06.     Sub Main()
   07.         Dim A As Int32 = 56
   08.         Change(A)
   09.         Console.WriteLine(A)
   10.         Console.ReadKey()
   11.     End Sub
   12. End Module

A scher mo appar ir à la scr itta "56": A è una var iabile di Main, che viene passata come par ametr o attuale alla pr ocedur a
Change. In quest'ultima, N costituisce il par ametr o for male - il segnaposto - a cui, dur ante il passaggio dei par ametr i,
viene attr ibuita un copia del valor e di A. In definitiva, per N viene cr eata un'altr a ar ea di memor ia, totalmente
distinta, e per questo motivo ogni oper azione eseguita su quest'ultima non cambia il valor e di A. Di fatto, ByVal indica
di tr attar e il par ametr o come un tipo value (pas s aggio per valore).
Al contr ar io, ByRef indica di tr attar e il par ametr o come un tipo r efer ence (pas s aggio per in dirizzo). Questo significa
che, dur ante il passaggio dei par ametr i, al par ametr o for male non viene attr ibuito come valor e una coppia di quello
attuale, ma bensì viene for zato a puntar e alla sua stessa cella di memor ia. In questa situazione, quindi, anche i tipi
value come numer i, date e valor i logici, si compor tano come se fosser o oggetti. Ecco lo stesso esempio di pr ima, con
una piccola modifica:

   01. Module Module1
   02.     Sub Change(ByRef N As Int32)
   03.         N = 30
   04.     End Sub
   05.
   06.     Sub Main()
   07.         Dim A As Int32 = 56
   08.         Change(A)
   09.         Console.WriteLine(A)
   10.         Console.ReadKey()
   11.     End Sub
   12. End Module

Nel codice, la sola differ enza consiste nella keyw or d ByRef, la quale, tuttavia, cambia r adicalmente il r isultato. Infatti, a
scher mo appar ir à "30" e non "56". Dato che è stata applicata la clausola ByRef, N punta alla stessa ar ea di memor ia di A,
quindi ogni alter azione per petr ata nel cor po del metodo sul par ametr o for male si r iper cuote su quello attuale.
A questo punto è molto impor tante sottolinear e che i tipi r efer ence si compor tano SEM PRE allo stesso modo, anche se
vengono inser iti nell'elenco dei par ametr i accompagnati da ByVal. Eccone una dimostr azione:

   01. Module Module1
   02.     Dim A As New Object
   03.
   04.     Sub Test(ByVal N As Object)
   05.         Console.WriteLine(N Is A)
   06.
End Sub
   07.
   08.     Sub Main()
   09.         Test(A)
   10.         Console.ReadKey()
   11.     End Sub
   12. End Module

Se ByVal modificasse il compor tamento degli oggetti, allor a N conter r ebbe una copia di A, ossia un altr o oggetto
semplicemente uguale, ma non identico. Invece, a scher mo appar e la scr itta "Tr ue", che significa "Ver o", per ciò N e A
sono lo stesso oggetto, anche se N er a pr eceduto da ByVal.




Le funzioni
Le funzioni sono simili alle pr ocedur e, ma possiedono qualche car atter istica in più. La lor o sintassi è la seguente:

    1. Function [name]([elenco parametri]) As [tipo]
    2.     '...
    3.     Return [risultato]
    4. End Function

La pr ima differ enza che salta all'occhio è l'As che segue l'elenco dei par ametr i, come a sugger ir e che la funzione sia di
un cer to tipo. Ad esser e pr ecisi, quell'As non indica il tipo della funzione, ma piuttosto quello del suo r isultato. Infatti, le
funzioni r estituiscono qualcosa alla fine del lor o ciclo di elabor azione. Per questo motivo, pr ima del ter mine del suo
cor po, deve esser e posta almeno un'istr uzione Retur n, seguita da un qualsiasi dato, la quale for nisce al chiamante il
ver o r isultato di tutte le oper azioni eseguite. Non è un er r or e scr iver e funzioni pr ive dell'istr uzione Retur n, ma non
avr ebbe comunque senso: si dovr ebbe usar e una pr ocedur a in quel caso. Ecco un esempio di funzione:

   01. Module Module1
   02.     'Questa funzione calcola la media di un insieme
   03.     'di numeri decimali passati come array
   04.     Function Average(ByVal Values() As Single) As Single
   05.         'Total conterrà la somma totale di tutti
   06.         'gli elementi di Values
   07.         Dim Total As Single = 0
   08.         'Usa un For Each per ottenere direttamente i valori
   09.         'presenti nell'array piuttosto che enumerarli
   10.         'attraverso un indice mediante un For normale
   11.         For Each Value As Single In Values
   12.              Total += Value
   13.         Next
   14.         'Restituisce la media aritmetica, ossia il rapporto
   15.         'tra la somma totale e il numero di elementi
   16.         Return (Total / Values.Length)
   17.     End Function
   18.
   19.     Sub Main(ByVal Args() As String)
   20.         Dim Values() As Single = {1.1, 5.2, 9, 4, 8.34}
   21.         'Notare che in questo caso ho usato lo stesso nome
   22.         'per il parametro formale e attuale
   23.         Console.WriteLine("Media: " & Average(Values))
   24.         Console.ReadKey()
   25.     End Sub
   26. End Module

E un altr o esempio in cui ci sono più Retur n:

   01. Module Module1
   02.     Function Potenza(ByVal Base As Single, ByVal Esponente As Byte) As Double
   03.         Dim X As Double = 1
   04.
   05.         If Esponente = 0 Then
   06.              Return 1
   07.         Else
   08.              If Esponente = 1 Then
   09.
Return Base
   10.                   Else
   11.                     For i As Byte = 1 To Esponente
   12.                          X *= Base
   13.                     Next
   14.                     Return X
   15.                End If
   16.            End If
   17.        End Function
   18.
   19.        Sub Main()
   20.            Dim F As Double
   21.            Dim b As Single
   22.            Dim e As Byte
   23.
   24.            Console.WriteLine("Inserire base ed esponente:")
   25.            b = Console.ReadLine
   26.            e = Console.ReadLine
   27.            F = Potenza(b, e)
   28.            Console.WriteLine(b & " elevato a " & e & " vale " & F)
   29.            Console.ReadKey()
   30.        End Sub
   31. End    Module

In quest'ultimo esempio, il cor po della funzione contiene ben tr e Retur n, ma ognuno appar tiene a un path di co dice
differ ente. Path significa "per cor so" e la locuzione appena usata indica il flusso di elabor azione seguito dal pr ogr amma
per deter minati valor i di Base ed Esponente. Disegnando un diagr amma di flusso della funzione, sar à facile capir e come
ci siano tr e per cor si differ enti, ossia quando l'esponente vale 0, quando vale 1 e quando è maggior e di 1. È
sintatticamente lecito usar e due Retur n nello stesso path, o addir ittur a uno dopo l'altr o, ma non ha nessun senso logico:
Retur n, infatti, non solo r estituisce un r isultato al chiamante, ma ter mina anche l'esecuzione della funzione. A questo
pr oposito, bisogna dir e che esiste anche lo statement (=istr uzione) Exit Fun ction , che for za il pr ogr amma ad uscir e
immediatamente dal cor po della funzione: inutile dir e che è abbastanza per icoloso da usar e, poiché si cor r e il r ischio di
non r estituir e alcun r isultato al chiamante, il che può tr adur si in un er r or e in fase di esecuzione.
Come ultima postilla vor r ei aggiunger e che, come per le var ibili, non è str ettamente necessar io specificar e il tipo del
valor e r estituito dalla funzione, anche se è for temente consigliato: in questo caso, il pr ogr amma suppor r à che si tr atti
del tipo Object.




Usi partic olari delle funzioni
Ci sono cer te cir costanze in cui le funzioni possono differ ir e legger mente dal lor o uso e dalla lor o for ma consueti. Di
seguito sono elencati alcuni casi:

       Quando una funzione si tr ova a destr a dell'uguale, in qualsiasi punto di un'espr essione dur ante un assegnamento,
       ed essa non pr esenta un elenco di par ametr i, la si può invocar e senza usar e la coppia di par entesi. L'esempio
       classico è la funzione Console.Readline. L'uso più cor r etto sar ebbe:

            1. a = Console.ReadLine()

       ma è possibile scr iver e, come abbiamo fatto fin'or a:

            1. a = Console.ReadLine
Non è obbligator io usar e il valor e r estituito da una funzione: nei casi in cui esso viene tr alasciato, la si tr atta
       come se fosse una pr ocedur a. Ne è un esempio la funzione Console.ReadKey(). A noi ser ve per fer mar e il
       pr ogr amma in attesa della pr essione di un pulsante, ma essa non si limita a questo: r estituisce anche
       infor mazioni dettagliate sulle condizioni di pr essione e sul codice del car atter e inviato dalla tastier a. Tuttavia,
       a noi non inter essava usar e queste infor mazioni; così, invece di scr iver e un codice come questo:

           1. Dim b = Console.ReadKey()

       ci siamo limitati a:

           1. Console.ReadKey()

       Questa ver satilità può, in cer ti casi, cr ear e pr oblemi, poiché si usa una funzione convinti che sia una pr ocedur a,
       mentr e il valor e r estituito è impor tante per evitar e l'insor ger e di er r or i. Ne è un esempio la funzione
       IO.File.Cr eate, che vedr emo molto più in là, nella sezione E della guida.




V ariabili Static
Le var iabili Static sono una par ticolar e eccezione alle var iabili locali/tempor anee. Avevo chiar amente scr itto pochi
par agr afi fa che queste ultime esistono solo nel cor po del metodo, vengono cr eate al momento dell'invocazione e
distr utte al ter mine dell'esecuzione. Le Static, invece, possiedono soltanto le pr ime due car atter istiche: non vengono
distr utte alla fine del cor po, ma il lor o valor e si conser va in memor ia e r imane tale anche quando il flusso entr a una
seconda volta nel metodo. Ecco un esempio:

   01. Module Module1
   02.     Sub Test()
   03.         Static B As Int32 = 0
   04.         B += 1
   05.         Console.WriteLine(B)
   06.     End Sub
   07.
   08.     Sub Main(ByVal Args() As String)
   09.         For I As Int16 = 1 To 6
   10.              Test()
   11.         Next
   12.         Console.ReadKey()
   13.     End Sub
   14. End Module

Il pr ogr amma stamper à a scher mo, in successione, 1, 2, 3, 4, 5 e 6. Come volevasi dimostr ar e, nonostante B sia
tempor anea, mantiene il suo valor e tr a una chiamata e la successiva.
A15. I Metodi - Parte III

Parametri opzionali
Come sugger isce il nome stesso, i par ametr i opzionali sono speciali par ametr i che non è obbligator io specificar e
quando si invoca un metodo. Li si dichiar a facendo pr eceder e la clausola ByVal o ByRef dalla keyw or d Optional: inoltr e,
dato che un par ametr o del gener e può anche esser e omesso, bisogna necessar iamente indicar e un valor e pr edefinito
che esso possa assumer e. Tale valor e pr edefinito deve esser e una costante e, per questo motivo, se r icor date il
discor so pr ecedentemente fatto sull'assegnamento delle costanti, i par ametr i opzionali possono esser e solo di tipo base.
Ecco un esempio:

   01. Module Module1
   02.     'Disegna una barra "di caricamento" animata con dei trattini
   03.     'e dei pipe (|). Length indica la sua lunghezza, ossia quanti
   04.     'caratterei debbano essere stampati a schermo. AnimationSpeed
   05.     'è la velocità dell'animazione, di default 1
   06.     Sub DrawBar(ByVal Length As Int32, _
   07.         Optional ByVal AnimationSpeed As Single = 1)
   08.         'La variabile static tiene conto del punto a cui si è
   09.         'arrivati al caricamento
   10.         Static Index As Int32 = 1
   11.
   12.         'Disegna la barra
   13.         For I As Int32 = 1 To Length
   14.              If I > Index Then
   15.                   Console.Write("-")
   16.              Else
   17.                   Console.Write("|")
   18.              End If
   19.         Next
   20.
   21.         'Aumenta l'indice di uno. Notare il particolare
   22.         'assegnamento che utilizza l'operatore Mod. Finché
   23.         'Index è minore di Length, questa espressione equivale
   24.         'banalmente a Index + 1, poiché a Mod b = a se a < b.
   25.         'Quando Index supera il valore di Length, allora l'operatore
   26.         'Mod cambia le cose: infatti, se Index = Length + 1,
   27.         'l'espressione restituisce 0, che, sommato a 1, dà 1.
   28.         'Il risultato che otteniamo è che Index reinizia
   29.         'da capo, da 1 fino a Length.
   30.         Index = (Index Mod (Length + 1)) + 1
   31.         'Il metodo Sleep, che vedremo approfonditamente solo nella
   32.         'sezione B, fa attendere al programma un certo numero di
   33.         'millisecondi.
   34.         '1000 / AnimationSpeed provoca una diminuzione del tempo
   35.         'di attesa all'aumentare della velocità
   36.         Threading.Thread.CurrentThread.Sleep(1000 / AnimationSpeed)
   37.     End Sub
   38.
   39.     Sub Main()
   40.         'Disegna la barra con un ciclo infinito. Potete invocare
   41.         'DrawBar(20) tralasciando l'ultimo argomento e l'animazione
   42.         'sarà lenta poiché la velocità di default è 1
   43.         Do
   44.              Console.Clear()
   45.              DrawBar(20, 5)
   46.         Loop
   47.     End Sub
   48. End Module




Parametri indefiniti
Questo par ticolar e tipo di par ametr i non r appr esenta un solo elemento, ma bensì una collezione di elementi: infatti, si
specifica un par ametr o come indefinito quando non si sa a pr ior i quanti par ametr i il metodo r ichieder à. A sostegno di
questo fatto, i par ametr i indefiniti sono dichiar ati come ar r ay, usando la keyw or d Par amAr r ay inter posta tr a la
clausola ByVal o ByRef e il nome del par ametr o.

   01. Module Module1
   02.     'Somma tutti i valori passati come parametri.
   03.     Function Sum(ByVal ParamArray Values() As Single) As Single
   04.         Dim Result As Single = 0
   05.
   06.         For I As Int32 = 0 To Values.Length - 1
   07.              Result += Values(I)
   08.         Next
   09.
   10.         Return Result
   11.     End Function
   12.
   13.     Sub Main()
   14.         Dim S As Single
   15.
   16.         'Somma due valori
   17.         S = Sum(1, 2)
   18.         'Somma quattro valori
   19.         S = Sum(1.1, 5.6, 98.2, 23)
   20.         'Somma un array di valori
   21.         Dim V() As Single = {1, 8, 3.4}
   22.         S = Sum(V)
   23.     End Sub
   24. End Module

Come si vede, mediante Par amAr r ay, la funzione diventa capace si accettar e sia una lista di valor i specificata dal
pr ogr ammator e si un ar r ay di valor i, dato che il par ametr o indefinito, in fondo, è pur sempr e un ar r ay.
N.B.: può esister e uno e un solo par ametr o dichiar ato con Par amAr r ay per ciascun metodo, ed esso deve sempr e
esser e posto alla fine dell'elenco dei par ametr i. Esempio:

   01. Module Module1
   02.     'Questa funzione calcola un prezzo includendovi anche
   03.     'il pagamento di alcune tasse (non sono un esperto di
   04.     'economia, perciò mi mantengono piuttosto sul vago XD).
   05.     'Il primo parametro rappresenta il prezzo originale, mentre
   06.     'il secondo è un parametro indefinito che
   07.     'raggruppa tutte le varie tasse vigenti sul prodotto
   08.     'da acquistare che devono essere aggiunte all'importo
   09.     'iniziale (espresse come percentuali)
   10.     Function ApplyTaxes(ByVal OriginalPrice As Single, _
   11.         ByVal ParamArray Taxes() As Single) As Single
   12.         Dim Result As Single = OriginalPrice
   13.         For Each Tax As Single In Taxes
   14.              Result += OriginalPrice * Tax / 100
   15.         Next
   16.         Return Result
   17.     End Function
   18.
   19.     Sub Main()
   20.         Dim Price As Single = 120
   21.
   22.         'Aggiunge una tassa del 5% a Price
   23.         Dim Price2 As Single = _
   24.              ApplyTaxes(Price, 5)
   25.
   26.         'Aggiunge una tassa del 5%, una del 12.5% e una
   27.         'dell'1% a Price
   28.         Dim Price3 As Single = _
   29.              ApplyTaxes(Price, 5, 12.5, 1)
   30.
   31.         Console.WriteLine("Prezzo originale: " & Price)
   32.         Console.WriteLine("Presso con tassa 1: " & Price2)
   33.         Console.WriteLine("Prezzo con tassa 1, 2 e 3: " & Price3)
   34.
35.         Console.ReadKey()
   36.     End Sub
   37. End Module




Ric orsione
Si ha una situazione di r icor sione quando un metodo invoca se stesso: in questi casi, il metodo viene detto r icor sivo.
Tale tecnica possiede pr egi e difetti: il pr egio pr incipale consiste nella r iduzione dr astica del codice scr itto, con un
conseguente aumento della leggibilità; il difetto più r ilevante è l'uso spr opositato di memor ia, per evitar e il quale è
necessar io adottar e alcune tecniche di pr ogr ammazione dinamica. La r icor sione, se male usata, inoltr e, può facilmente
pr ovocar e il cr ash di un'applicazione a causa di un over flow               dello stack. Infatti, se un metodo continua
indiscr iminatamente a invocar e se stesso, senza alcun contr ollo per poter si fer mar e (o con costr utti di contr ollo
contenenti er r or i logici), continua anche a r ichieder e nuova memor ia per il passaggio dei par ametr i e per le var iabili
locali, oltr e che per l'invocazione stessa: tutte queste r ichieste finiscono per sovr accar icar e la memor ia tempor anea,
che, non r iuscendo più a soddisfar le, le deve r ifiutar e, pr ovocando il suddetto cr ash. Ma for se sono tr oppo pessimista:
non vor r ei che r inunciaste ad usar e la r icor sione per paur a di incor r er e in tutti questi spaur acchi: ci sono cer ti casi in
cui è davver o utile. Come esempio non posso che pr esentar e il classico calcolo del fatto r iale:

   01. Module Module1
   02.     'Notare che il parametro è di tipo Byte perchè il
   03.     'fattoriale cresce in modo abnorme e già a 170! Double non
   04.     'basta più a contenere il risultato
   05.     Function Factorial(ByVal N As Byte) As Double
   06.         If N <= 1 Then
   07.              Return 1
   08.         Else
   09.              Return N * Factorial(N - 1)
   10.         End If
   11.     End Function
   12.
   13.     Sub Main()
   14.         Dim Number As Byte
   15.
   16.         Console.WriteLine("Inserisci un numero (0 <= x < 256):")
   17.         Number = Console.ReadLine
   18.         Console.WriteLine(Number & "! = " & Factorial(Number))
   19.
   20.         Console.ReadKey()
   21.     End Sub
   22. End Module
A16. Gli Enumeratori

Gli enumer ator i sono tipi value par ticolar i, che per mettono di r aggr uppar e sotto un unico nome più costanti. Essi
vengono utilizzati sopr attutto per r appr esentar e opzioni, attr ibuti, car atter istiche o valor i pr edefiniti, o, più in
gener ale, qualsiasi dato che si possa "sceglier e" in un insieme finito di possibilità. Alcuni esempi di enumer ator e
potr ebber o esser e lo stato di un computer (acceso, spento, standby, iber nazione, ...) o magar i gli attr ibuti di un file
(nascosto, ar chivio, di sistema, sola lettur a, ...): non a caso, per quest'ultimo, il .NET impiega ver amente un
enumer ator e. Ma pr ima di andar e oltr e, ecco la sintassi da usar e nella dichiar azione:

    1. Enum [Nome]
    2.     [Nome valore 1]
    3.     [Nome valore 2]
    4.     ...
    5. End Enum

Ad esempio:

   01. Module Module1
   02.     'A seconda di come sono configurati i suoi caratteri, una
   03.     'stringa può possedere diverse denominazioni, chiamate
   04.     'Case. Se è costituita solo da caratteri minuscoli
   05.     '(es.: "stringa di esempio") si dice che è in Lower
   06.     'Case; al contrario se contiene solo maiuscole (es.: "STRINGA
   07.     'DI ESEMPIO") sarà Upper Case. Se, invece, ogni
   08.     'parola ha l'iniziale maiuscola e tutte le altre lettere
   09.     'minuscole si indica con Proper Case (es.: "Stringa Di Esempio").
   10.     'In ultimo, se solo la prima parola ha l'iniziale
   11.     'maiuscola e il resto della stringa è tutto minuscolo
   12.     'e questa termina con un punto, si ha Sentence Case
   13.     '(es.: "Stringa di esempio.").
   14.     'Questo enumeratore indica questi casi
   15.     Enum StringCase
   16.         Lower
   17.         Upper
   18.         Sentence
   19.         Proper
   20.     End Enum
   21.
   22.     'Questa funzione converte una stringa in uno dei Case
   23.     'disponibili, indicati dall'enumeratore. Il secondo parametro
   24.     'è specificato fra parentesi quadre solamente perchè
   25.     'Case è una keyword, ma noi la vogliamo usare come
   26.     'identificatore.
   27.     Function ToCase(ByVal Str As String, ByVal [Case] As StringCase) As String
   28.         'Le funzioni per convertire in Lower e Upper
   29.         'case sono già definite. E' sufficiente
   30.         'indicare un punto dopo il nome della variabile
   31.         'stringa, seguito a ToLower e ToUpper
   32.         Select Case [Case]
   33.              Case StringCase.Lower
   34.                  Return Str.ToLower()
   35.              Case StringCase.Upper
   36.                  Return Str.ToUpper()
   37.              Case StringCase.Proper
   38.                  'Consideriamo la stringa come array di
   39.                  'caratteri:
   40.                  Dim Chars() As Char = Str.ToLower()
   41.                  'Iteriamo lungo tutta la lunghezza della
   42.                  'stringa, dove Str.Length restituisce appunto
   43.                  'tale lunghezza
   44.                  For I As Int32 = 0 To Str.Length - 1
   45.                      'Se questo carattere è uno spazio oppure
   46.                      'è il primo di tutta la stringa, il
   47.                      'prossimo indicherà l'inizio di una nuova
   48.
'parola e dovrà essere maiuscolo.
   49.                             If I = 0 Then
   50.                                 Chars(I) = Char.ToUpper(Chars(I))
   51.                             End If
   52.                             If Chars(I) = " " And I < Str.Length - 1 Then
   53.                                 'Char.ToUpper rende maiuscolo un carattere
   54.                                 'passato come parametro e lo restituisce
   55.                                 Chars(I + 1) = Char.ToUpper(Chars(I + 1))
   56.                             End If
   57.                      Next
   58.                      'Restituisce l'array modificato (un array di caratteri
   59.                      'e una stringa sono equivalenti)
   60.                      Return Chars
   61.                 Case StringCase.Sentence
   62.                      'Riduce tutta la stringa a Lower Case
   63.                      Str = Str.ToLower()
   64.                      'Imposta il primo carattere come maiuscolo
   65.                      Dim Chars() As Char = Str
   66.                      Chars(0) = Char.ToUpper(Chars(0))
   67.                      Str = Chars
   68.                      'La chiude con un punto
   69.                      Str = Str & "."
   70.                      Return Str
   71.             End Select
   72.         End Function
   73.
   74.         Sub Main()
   75.             Dim Str As String = "QuEstA è una stRingA DI prova"
   76.
   77.             'Per usare i valori di un enumeratore bisogna sempre scrivere
   78.             'il nome dell'enumeratore seguito dal punto
   79.             Console.WriteLine(ToCase(Str, StringCase.Lower))
   80.             Console.WriteLine(ToCase(Str, StringCase.Upper))
   81.             Console.WriteLine(ToCase(Str, StringCase.Proper))
   82.             Console.WriteLine(ToCase(Str, StringCase.Sentence))
   83.
   84.             Console.ReadKey()
   85.         End Sub
   86. End     Module

L'enumer ator e Str ingCase offr e quattr o possibilità: Low er , Upper , Pr oper e Sentence. Chi usa la funzione è invitato a
sceglier e una fr a queste costanti, ed in questo modo non si r ischia di dimenticar e il significato di un codice. Notar e che
ho scr itto "invitato", ma non "obbligato", poichè l'Enumer ator e è soltanto un mezzo attr aver so il quale il
pr ogr ammator e dà nomi significativi a costanti, che sono pur sempr e dei numer i. A pr ima vista non si dir ebbe,
vedendo la dichiar azione, ma ad ogni nome indicato come campo dell'enumer ator e viene associato un numer o (sempr e
inter o e di solito a 32 bit). Per saper e quale valor e ciascun identificator e indica, basta scr iver e un codice di pr ova
come questo:

    1.   Console.WriteLine(StringCase.Lower)
    2.   Console.WriteLine(StringCase.Upper)
    3.   Console.WriteLine(StringCase.Sentence)
    4.   Console.WriteLine(StringCase.Proper)

A scher mo appar ir à

    1.   0
    2.   1
    3.   2
    4.   3

Come si vede, le costanti assegnate par tono da 0 per il pr imo campo e vengono incr ementate di 1 via via che si
pr ocede a indicar e nuovi campi. È anche possibile deter minar e esplicitamente il valor e di ogni identificator e:

    1. Enum StringCase
    2.     Lower = 5
    3.     Upper = 10
    4.     Sentence = 20
    5.
Proper = 40
    6. End Enum

Se ad un nome non viene assegnato valor e, esso assumer à il valor e del suo pr ecedente, aumentato di 1:

    1. Enum StringCase
    2.     Lower = 5
    3.     Upper '= 6
    4.     Sentence = 20
    5.     Proper '= 21
    6. End Enum

Gli enumer ator i possono assumer e solo valor i inter i, e sono, a dir la ver ità, dir ettamente der ivati dai tipi numer ici di
base. È, infatti, per fettamente lecito usar e una costante numer ica al posto di un enumer ator e e vicever sa. Ecco un
esempio lampante in cui utilizzo un enumer ator e indicante le note musicali da cui r icavo la fr equenza delle suddette:

   01. Module Module1
   02.     'Usa i nomi inglesi delle note. L'enumerazione inizia
   03.     'da -9 poiché il Do centrale si trova 9 semitoni
   04.     'sotto il La centrale
   05.     Enum Note
   06.         C = -9
   07.         CSharp
   08.         D
   09.         DSharp
   10.         E
   11.         F
   12.         FSharp
   13.         G
   14.         GSharp
   15.         A
   16.         ASharp
   17.         B
   18.     End Enum
   19.
   20.     'Restituisce la frequenza di una nota. N, in concreto,
   21.     'rappresenta la differenza, in semitoni, di quella nota
   22.     'dal La centrale. Ecco l'utilittà degli enumeratori,
   23.     'che danno un nome reale a ciò che un dato indica
   24.     'indirettamente
   25.     Function GetFrequency(ByVal N As Note) As Single
   26.         Return 440 * 2 ^ (N / 12)
   27.     End Function
   28.
   29.     'Per ora prendete per buona questa funzione che restituisce
   30.     'il nome della costante di un enumeratore a partire dal
   31.     'suo valore. Avremo modo di approfondire nei capitoli
   32.     'sulla Reflection
   33.     Function GetName(ByVal N As Note) As String
   34.         Return [Enum].GetName(GetType(Note), N)
   35.     End Function
   36.
   37.     Sub Main()
   38.         'Possiamo anche iterare usando gli enumeratori, poiché
   39.         'si tratta pur sempre di semplici numeri
   40.         For I As Int32 = Note.C To Note.B
   41.              Console.WriteLine("La nota " & GetName(I) & _
   42.                  " risuona a una frequenza di " & GetFrequency(I) & "Hz")
   43.         Next
   44.
   45.         Console.ReadKey()
   46.     End Sub
   47. End Module

È anche possibile specificar e il tipo di inter o di un enumer ator e (se Byte, Int16, Int32, Int64 o SByte, UInt16, UInt32,
UInt64) apponendo dopo il nome la clausola As seguita dal tipo:

    1. Enum StringCase As Byte
    2.     Lower = 5
    3.
Upper = 10
    4.     Sentence = 20
    5.     Proper = 40
    6. End Enum

Questa par ticolar ità si r ivela molto utile quando bisogna scr iver e enumer ator i su file in modalità binar ia. In questi
casi, essi r appr esentano solitamente un campo detto Flags, di cui mi occuper ò nel pr ossimo par agr afo.




Campi c odific ati a bit (Flags)
Chi non conosca il codice binar io può legger e un ar ticolo su di esso nella sezione FFS.
I campi codificati a bit sono enumer ator i che per mettono di immagazzinar e numer ose infor mazioni in pochissimo
spazio, anche in un solo byte! Di solito, tuttavia, si utilizzano tipi Int32 per chè si ha bisogno di un numer o maggior e di
infor mazioni. Il meccanismo è molto semplice. Ogni opzione deve poter assumer e due valor i, Ver o o Falso: questi
vengono quindi codificati da un solo bit (0 o 1), ad esempio:

    1. 00001101

Rappr esenta un inter o senza segno a un byte, ossia il tipo Byte: in esso si possono immagazzinar e 8 campi (uno per
ogni bit), ognuno dei quali può esser e acceso o spento. In questo caso, sono attivi solo il pr imo, il ter zo e il quar to
valor e. Per por tar e a ter mine con successo le oper azioni con enumer ator i pr ogettati per codificar e a bit, è necessar io
che ogni valor e dell'enumer ator e sia una potenza di 2, da 0 fino al numer o che ci inter essa. Il motivo è molto semplice:
dato che ogni potenza di due occupa un singolo spazio nel byte, non c'è per icolo che alcuna opzione si sovr apponga. Per
unir e insieme più opzioni bisogna usar e l'oper ator e logico Or . Un esempio:

   01. Module Module1
   02.     'È convenzione che gli enumeratori che codificano a bit
   03.     'abbiano un nome al plurale
   04.     'Questo enumeratore definisce alcuni tipi di file
   05.     Public Enum FileAttributes As Byte
   06.         '1 = 2 ^ 0
   07.         'In binario:
   08.         '00000001
   09.         Normal = 1
   10.
   11.         '2 = 2 ^ 1
   12.         '00000010
   13.         Hidden = 2
   14.
   15.         '4 = 2 ^ 2
   16.         '00000100
   17.         System = 4
   18.
   19.         '8 = 2 ^ 3
   20.         '00001000
   21.         Archive = 8
   22.     End Enum
   23.
   24.     Sub Main()
   25.         Dim F As FileAttributes
   26.         'F all'inizio è 0, non contiene niente:
   27.         '00000000
   28.
   29.         F = FileAttributes.Normal
   30.         'Ora F è 1, ossia Normal
   31.         '00000001
   32.
   33.         F = FileAttributes.Hidden Or FileAttributes.System
   34.         'La situazione diventa complessa:
   35.         'Il primo valore è   2: 000000010
   36.         'Il secondo valore è 4: 000000100
   37.         'Abbiamo già visto l'operatore Or: restituisce True se
   38.         'almeno una delle condizioni è vera: qui True è
   39.         '1 e False è 0:
   40.
'000000010 Or
   41.         '000000100 =
   42.         '000000110
   43.         'Come si vede, ora ci sono due campi attivi: 4 e 2, che
   44.         'corrispondono a Hidden e System. Abbiamo fuso insieme due
   45.         'attributi con Or
   46.
   47.         F = FileAttributes.Archive Or FileAttributes.System Or _
   48.             FileAttributes.Hidden
   49.         'La stessa cosa:
   50.         '00001000 Or
   51.         '00000100 Or
   52.         '00000010 =
   53.         '00001110
   54.     End Sub
   55. End Module

Or a sappiamo come immagazzinar e i campi, ma come si fa a legger li? Nel pr ocedimento inver so si una invece un And:

   01. Module Module1
   02.     Sub Main()
   03.         Dim F As FileAttributes
   04.
   05.         F = FileAttributes.Archive Or FileAttributes.System Or _
   06.              FileAttributes.Hidden
   07.
   08.         'Ora F è 00001110 e bisogna eseguire un'operazione di And
   09.         'sui bit, confrontando questo valore con Archive, che è 8.
   10.         'And restituisce Vero solo quando entrambe le condizioni
   11.         'sono vere:
   12.         '00001110 And
   13.         '00001000 =
   14.         '00001000, ossia Archive!
   15.         If F And FileAttributes.Archive = FileAttributes.Archive Then
   16.              Console.WriteLine("Il file è marcato come 'Archive'")
   17.         End If
   18.         Console.ReadKey()
   19.     End Sub
   20. End Module

In definitiva, per immagazzinar e più dati in poco spazio occor r e un enumer ator e contenente solo valor i che sono
potenze di due; con Or si uniscono più campi; con And si ver ifica che un campo sia attivo.
A17. Le Strutture

Nel capitolo pr ecedente ci siamo soffer mati ad analizzar e una par ticolar e categor ia di tipi di dato, gli enumer ator i,
str umenti capaci di r appr esentar e tr amite costanti numer iche possibilità, scelte, opzioni, flags e in gener e valor i che
si possano sceglier e in un insieme finito di elementi. Le str uttur e, invece, appar tengono ad un'altr a categor ia.
Anch'esse r appr esentano un tipo di dato der ivato, o complesso, poiché non r ientr a fr a i tipi base (di cui ho già par lato)
ma è da essi composto. Le str uttur e ci per mettono di cr ear e nuovi tipi di dato che possano adattar si in modo miglior e
alla logica dell'applicazione che si sta scr ivendo: in r ealtà, quello che per mettono di far e è una specie di "collage" di
var iabili. Ad esempio, ammettiamo di voler scr iver e una r ubr ica, in gr ado di memor izzar e nome, cognome e numer o
di telefono dei nostr i pr incipali amici e conoscenti. Ovviamente, dato che si tr atta di tan te per sone, avr emo bisogno di
ar r ay per contener e tutti i dati, ma in che modo li potr emmo immagazzinar e? Per quello che ho illustr ato fino a
questo punto, la soluzione più lampante sar ebbe quella di dichiar ar e tr e ar r ay, uno per i nomi, uno per i cognomi e
uno per i numer i telefonici.

    1. Dim Names() As String
    2. Dim Surnames() As String
    3. Dim PhoneNumbers() As String

Inutile dir e che seguendo questo appr occio il codice r isulter ebbe molto confusionar io e poco aggior nabile: se si volesse
aggiunger e, ad esempio, un altr o dato, "data di nascita", si dovr ebbe dichiar ar e un altr o ar r ay e modificar e pr essoché
tutte le par ti del listato. Usando una str uttur a, invece, potr emmo cr ear e un nuovo tipo di dato che contenga al suo
inter no tutti i campi necessar i:

    1.   Structure Contact
    2.       Dim Name, Surname, PhoneNumber As String
    3.   End Structure
    4.
    5.   '...
    6.
    7.   'Un array di conttati, ognuno rappresentato dalla struttura Contact
    8.   Dim Contacts() As Contact

Come si vede dall'esempio, la sintassi usata per dichiar ar e una str uttur a è la seguente:

    1. Structure [Nome]
    2.     Dim [Campo1] As [Tipo]
    3.     Dim [Campo2] As [Tipo]
    4.     '...
    5. End Structure

Una volta dichiar ata la str uttur a e una var iabile di quel tipo, per ò, come si fa ad acceder e ai campi in essa pr esenti? Si
usa l'oper ator e punto ".", posto dopo il nome della var iabile:

   01. Module Module1
   02.     Structure Contact
   03.         Dim Name, Surname, PhoneNumber As String
   04.     End Structure
   05.
   06.     Sub Main()
   07.         Dim A As Contact
   08.
   09.         A.Name = "Mario"
   10.         A.Surname = "Rossi"
   11.         A.PhoneNumber = "333 33 33 333"
   12.     End Sub
   13. End Module

[Ricor date che le dichiar azioni di nuovi tipi di dato (fino ad or a quelli che abbiamo analizzato sono enumer ator i e
str uttur e, e le classi solo come intr oduzione) possono esser e fatte solo a livello di classe o di namespace, e m ai dentr o
ad un metodo.]
Una str uttur a, volendo ben veder e, non è altr o che un agglomer ato di più var iabili di tipo base e, cosa molto
impor tante, è un tipo value, quindi si compor ta esattamente come Integer , Shor t, Date, ecceter a... e viene
memor izzata dir ettamente sullo stack, senza uso di puntator i.




Ac robazie c on le strutture
Ma or a veniamo al codice ver o e pr opr io. Vogliamo scr iver e quella r ubr ica di cui avevo par lato pr ima, ecco un inizio:

   01. Module Module1
   02.     Structure Contact
   03.         Dim Name, Surname, PhoneNumber As String
   04.     End Structure
   05.
   06.     Sub Main()
   07.         'Contacts(-1) inizializza un array vuoto,
   08.         'ossia con 0 elementi
   09.         Dim Contacts(-1) As Contact
   10.         Dim Command As Char
   11.
   12.         Do
   13.              Console.Clear()
   14.              Console.WriteLine("Rubrica -----")
   15.              Console.WriteLine("Selezionare l'azione desiderata:")
   16.              Console.WriteLine("N - Nuovo contatto;")
   17.              Console.WriteLine("T - Trova contatto;")
   18.              Console.WriteLine("E - Esci.")
   19.              Command = Char.ToUpper(Console.ReadKey().KeyChar)
   20.              Console.Clear()
   21.
   22.              Select Case Command
   23.                  Case "N"
   24.                      'Usa ReDim Preserve per aumentare le dimensioni
   25.                      'dell'array mentenendo i dati già presenti.
   26.                      'L'uso di array e di redim, in questo caso, è
   27.                      'sconsigliato, a favore delle più versatili
   28.                      'Liste, che però non ho ancora introdotto.
   29.                      'Ricordate che il valore specificato tra
   30.                      'parentesi indica l'indice massimo e non
   31.                      'il numero di elementi.
   32.                      'Se, all'inizio, Contacts.Length è 0,
   33.                      'richiamando ReDim Contacts(0), si aumenta
   34.                      'la lunghezza dell'array a uno, poiché
   35.                      'in questo caso l'indice massimo è 0,
   36.                      'ossia quello che indica il primo e
   37.                      'l'unico elemento
   38.                      ReDim Preserve Contacts(Contacts.Length)
   39.
   40.                      Dim N As Contact
   41.                      Console.Write("Nome: ")
   42.                      N.Name = Console.ReadLine
   43.                      Console.Write("Cognome: ")
   44.                      N.Surname = Console.ReadLine
   45.                      Console.Write("Numero di telefono: ")
   46.                      N.PhoneNumber = Console.ReadLine
   47.
   48.                      'Inserisce nell'ultima cella dell'array
   49.                      'l'elemento appena creato
   50.                      Contacts(Contacts.Length - 1) = N
   51.
   52.                  Case "T"
   53.                      Dim Part As String
   54.
   55.                      Console.WriteLine("Inserire nome o cognome del " & _
   56.                           "contatto da trovare:")
   57.                      Part = Console.ReadLine
   58.
   59.
For Each C As Contact In Contacts
   60.                           'Il confronto avviene in modalità
   61.                           'case-insensitive: sia il nome/cognome
   62.                           'che la stringa immessa vengono
   63.                           'ridotti a Lower Case, così da
   64.                           'ignorare la differenza tra
   65.                           'minuscole e maiuscole, qualora presente
   66.                           If (C.Name.ToLower() = Part.ToLower()) Or _
   67.                              (C.Surname.ToLower() = Part.ToLower()) Then
   68.                               Console.WriteLine("Nome: " & C.Name)
   69.                               Console.WriteLine("Cognome: " & C.Surname)
   70.                               Console.WriteLine("Numero di telefono: " & C.PhoneNumber)
   71.                               Console.WriteLine()
   72.                           End If
   73.                      Next
   74.
   75.                  Case "E"
   76.                      Exit Do
   77.
   78.                  Case Else
   79.                      Console.WriteLine("Comando sconosciuto!")
   80.              End Select
   81.              Console.ReadKey()
   82.         Loop
   83.     End Sub
   84. End Module

Or a ammettiamo di voler modificar e il codice per per metter e l'inser imento di più numer i di telefono:

   01. Module Module1
   02. Structure Contact
   03.         Dim Name, Surname As String
   04.         'Importante: NON è possibile specificare le dimensioni
   05.         'di un array dentro la dichiarazione di una struttura.
   06.         'Risulta chiaro il motivo se ci si pensa un attimo.
   07.         'Noi stiamo dichiarando quali sono i campi della struttura
   08.         'e quale è il loro tipo. Quindi specifichiamo che
   09.         'PhoneNumbers è un array di stringhe, punto. Se scrivessimo
   10.         'esplicitamente le sue dimensioni lo staremmo creando
   11.         'fisicamente nella memoria, ma questa è una
   12.         'dichiarazione, come detto prima, e non una
   13.         'inizializzazione. Vedremo in seguito che questa
   14.         'differenza è molto importante per i tipi reference
   15.         '(ricordate, infatti, che gli array sono tipi reference).
   16.         Dim PhoneNumbers() As String
   17.     End Structure
   18.
   19.     Sub Main()
   20.         Dim Contacts(-1) As Contact
   21.         Dim Command As Char
   22.
   23.         Do
   24.              Console.Clear()
   25.              Console.WriteLine("Rubrica -----")
   26.              Console.WriteLine("Selezionare l'azione desiderata:")
   27.              Console.WriteLine("N - Nuovo contatto;")
   28.              Console.WriteLine("T - Trova contatto;")
   29.              Console.WriteLine("E - Esci.")
   30.              Command = Char.ToUpper(Console.ReadKey().KeyChar)
   31.              Console.Clear()
   32.
   33.              Select Case Command
   34.                  Case "N"
   35.                      ReDim Preserve Contacts(Contacts.Length)
   36.
   37.                      Dim N As Contact
   38.                      Console.Write("Nome: ")
   39.                      N.Name = Console.ReadLine
   40.                      Console.Write("Cognome: ")
   41.                      N.Surname = Console.ReadLine
   42.
   43.                      'Ricordate che le dimensioni dell'array non
   44.
'sono ancora state impostate:
   45.                      ReDim N.PhoneNumbers(-1)
   46.
   47.                      'Continua a chiedere numeri di telefono finché
   48.                      'non si introduce più nulla
   49.                      Do
   50.                           ReDim Preserve N.PhoneNumbers(N.PhoneNumbers.Length)
   51.                           Console.Write("Numero di telefono " & N.PhoneNumbers.Length & ": ")
   52.                           N.PhoneNumbers(N.PhoneNumbers.Length - 1) = Console.ReadLine
   53.                      Loop Until N.PhoneNumbers(N.PhoneNumbers.Length - 1) = ""
   54.                      'Ora l'ultimo elemento dell'array è sicuramente
   55.                      'vuoto, lo si dovrebbe togliere.
   56.
   57.                      Contacts(Contacts.Length - 1) = N
   58.
   59.                  Case "T"
   60.                      Dim Part As String
   61.
   62.                      Console.WriteLine("Inserire nome o cognome del " & _
   63.                           "contatto da trovare:")
   64.                      Part = Console.ReadLine
   65.
   66.                      For Each C As Contact In Contacts
   67.                           If (C.Name.ToLower() = Part.ToLower()) Or _
   68.                              (C.Surname.ToLower() = Part.ToLower()) Then
   69.                               Console.WriteLine("Nome: " & C.Name)
   70.                               Console.WriteLine("Cognome: " & C.Surname)
   71.                               Console.WriteLine("Numeri di telefono: ")
   72.                               For Each N As String In C.PhoneNumbers
   73.                                    Console.WriteLine(" - " & N)
   74.                               Next
   75.                               Console.WriteLine()
   76.                           End If
   77.                      Next
   78.
   79.                  Case "E"
   80.                      Exit Do
   81.
   82.                  Case Else
   83.                      Console.WriteLine("Comando sconosciuto!")
   84.              End Select
   85.              Console.ReadKey()
   86.         Loop
   87.     End Sub
   88. End Module

In questi esempi ho cer cato di pr opor r e i casi più comuni di str uttur a, almeno per quanto si è visto fino ad adesso: una
str uttur a for mata da campi di tipo base e una composta dagli stessi campi, con l'aggiunta di un tipo a sua volta
der ivato, l'ar r ay. Fino ad or a, infatti, ho sempr e detto che la str uttur a per mette di r aggr uppar e più membr i di tipo
base, ma sar ebbe r iduttivo r estr inger e il suo ambito di competenza solo a questo. In r ealtà può contener e var iabili di
qualsiasi tipo, compr ese altr e str uttur e. Ad esempio, un contatto avr ebbe potuto anche contener e l'indir izzo di
r esidenza, il quale avr ebbe potuto esser e stato r appr esentato a sua volta da un'ulter ior e str uttur a:

   01.   Structure Address
   02.       Dim State, Town As String
   03.       Dim Street, CivicNumber As String
   04.       Dim Cap As String
   05.   End Structure
   06.
   07.   Structure Contact
   08.       Dim Name, Surname As String
   09.       Dim PhoneNumbers() As String
   10.       Dim Home As Address
   11.   End Structure

Per acceder e ai campi di Home si sar ebbe utilizzato un ulter ior e punto:

   01. Dim A As Contact
   02.
   03.
A.Name = "Mario"
04.   A.Surname = "Rossi"
05.   ReDim A.PhoneNumbers(0)
06.   A.PhoneNumbers(0) = "124 90 87 111"
07.   A.Home.State = "Italy"
08.   A.Home.Town = "Pavia"
09.   A.Home.Street = "Corso Napoleone"
10.   A.Home.CivicNumber = "96/B"
11.   A.Home.Cap = "27010"
A18. Le Classi

Bene bene. Eccoci ar r ivati al sugo della questione. Le classi, entità alla base di tutto l'edificio del .NET. Già nei pr imi
capitoli di questa guida ho accennato alle classi, alla lor o sintassi e al modo di dichiar ar le. Per chi non si r icor dasse (o
non avesse voglia di lasciar e questa magnifica pagina per r itor nar e indietr o nei capitoli), una classe si dichiar a
semplicemente così:

    1. Class [Nome Classe]
    2.     '...
    3. End Class

Con l'atto della dichiar azione, la classe inizia ad esister e all'inter no del codice sor gente, cosicchè il pr ogr ammator e la
può usar e in altr e par ti del listato per gli scopi a causa dei quali è stata cr eata. Or a che ci stiamo avvicinando sempr e
più all'usar e le classi nei pr ossimi pr ogr ammi, tuttavia, è dover oso r icor dar e ancor a una volta la sostanziale differ enza
tr a dichiar azione e inizializzazione, tr a classe e oggetto, giusto per r infr escar e le memor ie più fr agili e, lungi dal
far vi odiar e questo concetto, per far e in modo che il messaggio penetr i:

   01. Module Module1
   02.     'Classe che rappresenta un cubo.
   03.     'Segue la dichiarazione della classe. Da questo momento
   04.     'in poi, potremo usare Cube come tipo per le nostre variabili.
   05.     'Notare che una classe si dichiara e basta, non si
   06.     '"inizializza", perchè non è qualcosa di concreto,
   07.     'è un'astrazione, c'è, esiste in generale.
   08.     Class Cube
   09.         'Variabile che contiene la lunghezza del lato
   10.         Dim SideLength As Single
   11.         'Variabile che contiene la densità del cubo, e quindi
   12.         'ci dice di che materiale è composto
   13.         Dim Density As Single
   14.
   15.         'Questa procedura imposta i valori del lato e
   16.         'della densità
   17.         Sub SetData(ByVal SideLengthValue As Single, ByVal DensityValue As Single)
   18.              SideLength = SideLengthValue
   19.              Density = DensityValue
   20.         End Sub
   21.
   22.         'Questa funzione restituisce l'area di una faccia
   23.         Function GetSurfaceArea() As Single
   24.              Return (SideLength ^ 2)
   25.         End Function
   26.
   27.         'Questa funzione restituisce il volume del cubo
   28.         Function GetVolume() As Single
   29.              Return (SideLength ^ 3)
   30.         End Function
   31.
   32.         'Questa funzione restituisce la massa del cubo
   33.         Function GetMass() As Single
   34.              Return (Density * GetVolume())
   35.         End Function
   36.     End Class
   37.
   38.     Sub Main()
   39.         'Variabile di tipo Cube, che rappresenta uno specifico cubo
   40.         'La riga di codice che segue contiene la dichiarazione
   41.         'della variabile A. La dichiarazione di una variabile
   42.         'fa sapere al compilatore, ad esempio, di che tipo
   43.         'sarà, in quale blocco di codice sarà
   44.         'visibile, ma nulla di più.
   45.         'Non esiste ancora un oggetto Cube collegato ad A, ma
   46.         'potrebbe essere creato in un immediato futuro.
   47.
'N.B.: quando si dichiara una variabile di tipo reference,
   48.         'viene comunque allocata memoria sullo stack; viene
   49.         'infatti creato un puntatore, che punta all'oggetto
   50.         'Nothing, il cui valore simbolico è stato
   51.         'spiegato precedentemente.
   52.         Dim A As Cube
   53.
   54.         'Ed ecco l'immediato futuro: con la prossima linea di
   55.         'codice, creiamo l'oggetto di tipo Cube che verrà
   56.         'posto nella variabile A.
   57.         A = New Cube
   58.         'Quando New è seguito dal nome di una classe, si crea un
   59.         'oggetto di quel tipo. Nella fattispecie, in questo momento
   60.         'il programma si preoccuperà di richiedere della
   61.         'memoria sull'heap managed per allocare i dati relativi
   62.         'all'oggetto e di creare un puntatore sullo stack che
   63.         'punti a tale oggetto. Esso, inoltre, eseguirà
   64.         'il codice contenuto nel costruttore. New, infatti,
   65.         'è uno speciale tipo di procedura, detta
   66.         'Costruttore, di cui parlerò approfonditamente
   67.         'in seguito
   68.
   69.         'Come per le strutture, i membri di classe sono accessibili
   70.         'tramite l'operatore punto ".". Ora imposto le variabili
   71.         'contenute in A per rappresentare un cubo di alluminio
   72.         '(densità 2700 Kg/m<sup>3</sup>) di 1.5m di lato
   73.         A.SetData(1.5, 2700)
   74.
   75.         Console.WriteLine("Superficie faccia: " & A.GetSurfaceArea() & " m2")
   76.         Console.WriteLine("Volume: " & A.GetVolume() & " m3")
   77.         Console.WriteLine("Massa: " & A.GetMass() & " Kg")
   78.         'It's Over 9000!!!!
   79.
   80.         Console.ReadKey()
   81.     End Sub
   82. End Module

In questo esempio ho usato una semplice classe che r appr esenta un cubo di una cer ta dimensione e di un cer to
mater iale. Tale classe espone quattr o funzioni, che ser vono per ottener e infor mazioni o impostar e valor i. C'è un
pr eciso motivo per cui non ho usato dir ettamente le due var iabili accedendovi con l'oper ator e punto, e lo spiegher ò a
br eve nella pr ossima lezione. Quindi, tali funzioni sono membr i di classe e, sopr attutto, funzioni di istanza. Questo
lemma non dovr ebbe suonar vi nuovo: gli oggetti, infatti, sono istanze (copie mater iali, concr ete) di classi (astr azioni).
Anche questo concetto è molto impor tante: il fatto che siano "di istanza" significa che possono esser e r ichiamate ed
usate solo da un oggetto. Per far vi capir e, non si possono invocar e con questa sintassi:

    1. Cube.GetVolume()

ma solo passando attr aver so un'istanza:

    1. Dim B As New Cube
    2. '...
    3. B.GetVolume()

E questo, tr a l'altr o, è abbastanza banale: infatti, come sar ebbe possibile calcolar e ar ea, volume e massa se non si
disponesse della misur a della lunghezza del lato e quella della densità? È ovvio che ogni cubo ha le sue pr opr ie misur e, e
il concetto gener ale di "cubo" non ci dice niente su queste infor mazioni.




Un semplic e c ostruttore
Anche se entr er emo nel dettaglio solo più in là, è necessar io per i pr ossimi esempi che sappiate come funziona un
costr uttor e, anche molto semplice. Esso viene dichiar ato come una nor male pr ocedur a, ma si deve sempr e usar e come
nome "New ":

    1.
Sub New([parametri])
    2.     'codice
    3. End Sub

Qualor a non si specificasse nessun costr uttor e, il compilator e ne cr eer à uno nuovo senza par ametr i, che equivale al
seguente:

    1. Sub New()
    2. End Sub

Il codice pr esente nel cor po del costr uttor e viene eseguito in una delle pr ime fasi della cr eazione dell'oggetto, appena
dopo che questo è statao fisicamente collocato nella memor ia (ma, badate bene, non è la pr ima istr uzione ad esser e
eseguita dopo la cr eazione). Lo scopo di tale codice consiste nell'inizializzar e var iabili di tipo r efer ence pr ima solo
dichiar ate, attr ibuir e valor i alle var iabili value, eseguir e oper azioni di pr epar azione all'uso di r isor se ester ne,
ecceter a... Insomma, ser ve a spianar e la str ada all'uso della classe. In questo caso, l'uso che ne far emo è molto r idotto e,
non vor r ei dir lo, quasi mar ginale, ma è l'unico compito possibile e utile in questo contesto: dar emo al costr uttor e il
compito di inizializzar e SideLength e Density.

   01. Module Module1
   02.     Class Cube
   03.         Dim SideLength As Single
   04.         Dim Density As Single
   05.
   06.         'Quasi uguale a SetData
   07.         Sub New(ByVal SideLengthValue As Single, ByVal DensityValue As Single)
   08.              SideLength = SideLengthValue
   09.              Density = DensityValue
   10.         End Sub
   11.
   12.         Function GetSurfaceArea() As Single
   13.              Return (SideLength ^ 2)
   14.         End Function
   15.
   16.         Function GetVolume() As Single
   17.              Return (SideLength ^ 3)
   18.         End Function
   19.
   20.         Function GetMass() As Single
   21.              Return (Density * GetVolume())
   22.         End Function
   23.     End Class
   24.
   25.     Sub Main()
   26.         'Questa è una sintassi più concisa che equivale a:
   27.         'Dim A As Cube
   28.         'A = New Cube(2700, 1.5)
   29.         'Tra parentesi vanno passati i parametri richiesti dal
   30.         'costruttore
   31.         Dim A As New Cube(2700, 1.5)
   32.
   33.         Console.WriteLine("Superficie faccia: " & A.GetSurfaceArea() & " m<sup>2</sup>")
   34.         Console.WriteLine("Volume: " & A.GetVolume() & " m<sup>3</sup>")
   35.         Console.WriteLine("Massa: " & A.GetMass() & " Kg")
   36.
   37.         Console.ReadKey()
   38.     End Sub
   39. End Module




Una nota sulle Strutture
Anche le str uttur e, come le classi, possono espor r e pr ocedur e e funzioni, e questo non è str ano. Esse, inoltr e, possono
espor r e anche costr uttor i... e questo dovr ebbe appar ir vi str ano. Infatti, ho appena illustr ato l'impor tanza dei
costr uttor i nell'istanziar e oggetti, quindi tipi r efer ence, mentr e le str uttur e sono palesemente tipi value. Il conflitto si
r isolve con una soluzione molto semplice: i costr uttor i dichiar ati nelle str uttur e possono esser e usati esattamente
come per le classi, ma il lor o compito è solo quello di inizializzar e campi e r ichiamar e r isor se, poiché una var iabile di
tipo str uttur ato viene cr eata sullo stack all'atto della sua dichiar azione.

   01. Module Module1
   02.     Structure Cube
   03.         Dim SideLength As Single
   04.         Dim Density As Single
   05.
   06.         Sub New(ByVal SideLengthValue As Single, ByVal DensityValue As Single)
   07.              SideLength = SideLengthValue
   08.              Density = DensityValue
   09.         End Sub
   10.
   11.         Function GetSurfaceArea() As Single
   12.              Return (SideLength ^ 2)
   13.         End Function
   14.
   15.         Function GetVolume() As Single
   16.              Return (SideLength ^ 3)
   17.         End Function
   18.
   19.         Function GetMass() As Single
   20.              Return (Density * GetVolume())
   21.         End Function
   22.     End Structure
   23.
   24.     Sub Main()
   25.         'Questo codice
   26.         Dim A As New Cube(2700, 1.5)
   27.
   28.         'Equivale a questo
   29.         Dim B As Cube
   30.         B.SideLength = 1.5
   31.         B.Density = 2700
   32.
   33.         'A e B sono uguali
   34.
   35.         Console.ReadKey()
   36.     End Sub
   37. End Module
A19. Le Classi - Specificatori di accesso

Le classi possono posseder e molti membr i, di svar iate categor ie, e ognuno di questi è sempr e contr addistinto da un
liv ello di accesso . Esso specifica "chi" può acceder e a quali membr i, e da quale par te del codice. Molto spesso, infatti,
è necessar io pr ecluder e l'accesso a cer te par ti del codice da par te di fr uitor i ester ni: fate bene attenzione, non sto
par lando di pr otezione del codice, di sicur ezza, intendiamoci bene; mi sto r ifer endo, invece, a chi user à il nostr o
codice (fossimo anche noi stessi). I motivi sono dispar ati, ma molto spesso si vuole evitar e che vengano modificate
var iabili che ser vono per calcoli, oper azioni su file, r isor se, ecceter a. Al contr ar io, è anche possibile espander e
l'accesso ad un membr o a chiunque. Con questi due esempi intr oduttivi, apr iamo la str ada agli specificato r i di
accesso , par ole chiave anteposte alla dichiar azione di un membr o che ne deter minano il livello di accesso.
Ecco una lista degli specificator i di accesso esistenti, di cui pr ender ò or a in esame solo i pr imi due:

          Pr ivate: un membr o pr ivato è accessibile solo all'inter no della classe in cui è stato dichiar ato;
          Public: un membr o pubblico è accessibile da qualsiasi par te del codice (dalla stessa classe, dalle sottoclassi, da
          classi ester ne, per fino da pr ogr ammi ester ni);
          Fr iend
          Pr otected
          Pr otected Fr iend (esiste solo in VB.NET)




Un esempio pratic o
Ripr endiamo il codice della classe Cube r ipr oposto nel capitolo pr ecedente. Pr oviamo a scr iver e nella Sub Main questo
codice:

    1. Sub Main()
    2.     Dim A As New Cube(2700, 1.5)
    3.     A.SideLength = 3
    4. End Sub

La r iga "A.SideLength = 3" ver r à sottolineata e appar ir à il seguente er r or e nel log degli er r or i:

    1. ConsoleApplication2.Module1.Cube.SideLength' is not accessible in this
    2. context because it is 'Private'.

Questo è il motivo per cui ho usato una pr ocedur a per impostar e i valor i: l'accesso al membr o (in questo caso "campo",
in quanto si tr atta di una var iabile) SideLength ci è pr ecluso se tentiamo di acceder vi da un codice ester no alla classe,
poiché, di default, nelle classi, Dim equivale a Pr ivate. Dichiar andolo esplicitamente, il codice di Cube sar ebbe stato
così:

   01. Module Module1
   02.     Class Cube
   03.         'Quando gli specificatori di accesso sono anteposti alla
   04.         'dichiarazione di una variabile, si toglie il "Dim"
   05.         Private SideLength As Single
   06.         Private Density As Single
   07.
   08.         Sub New(ByVal SideLengthValue As Single, ByVal DensityValue As Single)
   09.              SideLength = SideLengthValue
   10.              Density = DensityValue
   11.         End Sub
   12.
   13.         Function GetSurfaceArea() As Single
   14.              Return (SideLength ^ 2)
   15.         End Function
   16.
17.          Function GetVolume() As Single
   18.              Return (SideLength ^ 3)
   19.          End Function
   20.
   21.          Function GetMass() As Single
   22.              Return (Density * GetVolume())
   23.          End Function
   24.     End Class
   25.     '...
   26. End Module

In questo specifico caso, sar ebbe stato meglio impostar e tali var iabili come Public, poiché nel lor o scope (= livello di
accesso) attuale non ser vono a molto e, anzi, r ichiedono molto più codice di gestione. Ma immaginate una classe che
compia oper azioni cr ittogr afiche sui dati che gli sono passati in input, usando var iabili d'istanza per i suoi calcoli: se
tali var iabili fosser o accessibili al di fuor i della classe, lo sviluppator e che non sapesse esattamente cosa far ci potr ebbe
compr ometter e ser iamente il r isultato di tali oper azioni, e quindi danneggiar e i pr otocolli di sicur ezza usati
dall'applicazione. Etichettar e un membr o come pr ivate equivar r ebbe scher zosamente a por vi sopr a un gr ande car tello
con scr itto "NON TOCCARE".
Ma veniamo invece a Public, uno degli scope più usati nella scr ittur a di una classe. Di solito, tutti i membr i che devono
esser e r esi disponibili per altr e par ti del pr ogr amma o anche per altr i pr ogr ammator i (ad esempio, se si sta
scr ivendo una libr er ia che sar à usata successivamente da altr e per sone) sono dichiar ati come Public, ossia sempr e
accessibili, senza nessun per messo di sor ta.




E che dir e, allor a, dei membr i senza specificator e di accesso? Non esistono, a dir la tutta. Anche quelli che nel codice non
vengono esplicitamente mar cati dal pr ogr ammator e con una delle keyw or d sopr a elencate hanno uno scope
pr edefinito: si tr atta di Fr iend. Esso ha un compito par ticolar e che potr ete capir e meglio quando affr onter emo la
scr ittur a di una libr er ia di classi: per or a vi baster à saper e che, all'inter no di uno stesso pr ogetto, equivale a Public.
Un'altr a cosa impor tante: anche le classi (e i moduli) sono contr addistinte da un livello di accesso, che segue
esattamente le stesse r egole sopr a esposte. Ecco un esempio:

   01. Public Class Classe1
   02.     Private Class Classe2
   03.         '...
   04.     End Class
   05.
   06.     Class Classe3
   07.         '...
   08.     End Class
   09. End Class
   10.
   11. Class Classe4
   12.     Public Class Classe5
   13.         Private Class Classe6
   14.              '...
   15.
End Class
   16.     End Class
   17. End Class
   18.
   19. Module Module1
   20.     Sub Main()
   21.         '...
   22.     End Sub
   23. End Module

Il codice contenuto in Main può acceder e a:

       Classe1, per chè è Public
       Classe3, per chè è Fr iend, ed è possibile acceder e al suo contenitor e Classe1
       Classe4, per chè è Fr iend
       Classe5, per chè è Public, ed è possibile acceder e al suo contenitor e Classe4

mentr e non può acceder e a:

       Classe2, per chè è Pr ivate
       Classe6, per chè è Pr ivate

d'altr a par te, il codice di Classe2 può acceder e a tutto tr anne a Classe6 e vicever sa.
N.B.: Una classe può esser e dichiar ata Pr ivate solo quando si tr ova all'inter no di un'altr a classe (altr imenti non sar ebbe
mai accessibile, e quindi inutile).




Spec ific atori di ac c esso nelle Strutture
Anche per i membr i di una str uttur a, così come per quelli di una classe, è possibile specificar e tutti gli scope esistenti.
C'è solo una differ enza: quando si omette lo scope e si lascia una var iabile dichiar ata solo con Dim, essa è
automaticamente impostata a Public. Per questo motivo ci er a possibile acceder e ai campi della str uttur a Contact, ad
esempio:

    1. Structure Contact
    2.     Dim Name, Surname, PhoneNumber As String
    3. End Structure

che equivale a:

    1. Structure Contact
    2.     Public Name, Surname, PhoneNumber As String
    3. End Structure

Ovviamente, anche le str uttur e stesse hanno sempr e uno scope, così come qualsiasi altr a entità del .NET.




Un esempio intelligente
Ecco un esempio di classe scr itta utilizzando gli specificator i di accesso per limitar e l'accesso ai membr i da par te del
codice di Main (e quindi da chi usa la classe, poiché l'utente finale può anche esser e un altr o pr ogr ammator e). Oltr e a
questo tr over ete anche un esempio di un diffuso e semplice algor itmo di or dinamento, 2 in 1!

 001. Module Module1
 002.     'Dato che usiamo la classe solo in questo programma, possiamo
 003.     'evitare di dichiararla Public, cosa che sarebbe ideale in
 004.     'una libreria
 005.     Class BubbleSorter
 006.         'Enumeratore pubblico: sarà accessibile da tutti. In questo
 007.         'caso è impossibile dichiararlo come Private, poiché
 008.         'uno dei prossimi metodi richiede come parametro una
 009.
'variabile di tipo SortOrder e se questo fosse private,
010.   'non si potrebbe usare al di fuori della classe, cosa
011.   'che invece viene richiesta.
012.   Public Enum SortOrder
013.       Ascending 'Crescente
014.       Descending 'Decrescente
015.       None 'Nessun ordinamento
016.   End Enum
017.
018.   'Mantiene in memoria il senso di ordinamento della lista,
019.   'per evitare di riordinarla nel caso fosse richiesto due
020.   'volte lo stesso
021.   Private CurrentOrder As SortOrder = SortOrder.None
022.   'Mantiene in memoria una copia dell'array, che è
023.   'accessibile ai soli membri della classe. In
024.   'questo modo, è possibile eseguire tutte
025.   'le operazioni di ordinamento usando un solo metodo
026.   'per l'inserimento dell'array
027.   Private Buffer() As Double
028.
029.   'Memorizza in Buffer l'array passato come parametro
030.   Public Sub PushArray(ByVal Array() As Double)
031.       'Se Buffer è diverso da Nothing, lo imposta
032.       'esplicitamente a Nothing (equivale a distruggere
033.       'l'oggetto)
034.       If Buffer IsNot Nothing Then
035.           Buffer = Nothing
036.       End If
037.       'Copia l'array: ricordate come si comportano i tipi
038.       'reference e pensate a quali ripercussioni tale
039.       'comportamento potrà avere sul codice
040.       Buffer = Array
041.       'Annulla CurrentOrder
042.       CurrentOrder = SortOrder.None
043.   End Sub
044.
045.   'Procedura che ordina l'array secondo il senso specificato
046.   Public Sub Sort(ByVal Order As SortOrder)
047.       'Se il senso è None, oppure è uguale a quello corrente,
048.       'è inutile proseguire, quindi si ferma ed esce
049.       If (Order = SortOrder.None) Or (Order = CurrentOrder) Then
050.           Exit Sub
051.       End If
052.
053.       'Questa variabile tiene conto di tutti gli scambi
054.       'effettuati
055.       Dim Occurrences As Int32 = 0
056.
057.       'Il ciclo seguente ordina l'array in senso crescente:
058.       'se l'elemento i è maggiore dell'elemento i+1,
059.       'ne inverte il posto, e aumenta il contatore di 1.
060.       'Quando il contatore rimane 0 anche dopo il For,
061.       'significa che non c'è stato nessuno scambio
062.       'e quindi l'array è ordinato.
063.       Do
064.           Occurrences = 0
065.           For I As Int32 = 0 To Buffer.Length - 2
066.                If Buffer(I) > Buffer(I + 1) Then
067.                    Dim Temp As Double = Buffer(I)
068.                    Buffer(I) = Buffer(I + 1)
069.                    Buffer(I + 1) = Temp
070.                    Occurrences += 1
071.                End If
072.           Next
073.       Loop Until Occurrences = 0
074.
075.       'Se l'ordine era discendente, inverte l'array
076.       If Order = SortOrder.Descending Then
077.           Array.Reverse(Buffer)
078.       End If
079.
080.       'Memorizza l'ordine
081.
CurrentOrder = Order
 082.              End Sub
 083.
 084.             'Restituisce l'array ordinato
 085.             Public Function PopArray() As Double()
 086.                 Return Buffer
 087.             End Function
 088.         End Class
 089.
 090.         Sub Main()
 091.             'Crea un    array temporaneo
 092.             Dim a As    Double() = {1, 6, 2, 9, 3, 4, 8}
 093.             'Crea un    nuovo oggetto BubbleSorter
 094.             Dim b As    New BubbleSorter()
 095.
 096.              'Vi inserisce l'array
 097.              b.PushArray(a)
 098.              'Invoca la procedura di ordinamento
 099.              b.Sort(BubbleSorter.SortOrder.Descending)
 100.
 101.              'E per ogni elemento presente nell'array finale
 102.              '(quello restituito dalla funzione PopArray), ne stampa
 103.              'il valore a schermo
 104.              For Each n As Double In (b.PopArray())
 105.                   Console.Write(n & " ")
 106.              Next
 107.
 108.             Console.ReadKey()
 109.         End Sub
 110. End     Module




Ric apitolando...
Ricapitolando, quindi, davanti a ogni membr o si può specificar e una keyw or d tr a Pr ivate, Public e Fr iend (per quello
che abbiamo visto in questo capitolo), che ne limita l'accesso. Nel caso non si specifichi nulla, lo specificator e pr edefinito
var ia a seconda dell'entità a cui è stato applicato, secondo questa tabella:

       Pr ivate per var iabili contenute in una classe
       Public per var iabili contenute in una str uttur a
       Fr iend per tutte le altr e entità
A20. Le Proprietà - Parte I

Le pr opr ietà sono una categor ia di membr i di classe molto impor tante, che user emo molto spesso da qui in avanti. Non
è possibile definir ne con pr ecisione la natur a: esse sono una via di mezzo tr a metodi (pr ocedur e o funzioni) e campi
(var iabili dichiar ate in una classe). In gener e, si dice che le pr opr ietà siano "campi intelligenti", poiché il lor o r uolo
consiste nel mediar e l'inter azione tr a codice ester no alla classe e campo di una classe. Esse si "avvolgono" intor no a un
campo (per questo motivo vengono anche chiamate w r apper , dall'inglese w r ap = impacchettar e) e decidono, tr amite
codice scr itto dal pr ogr ammator e, quali valor i siano leciti per quel campo e quali no - stile buttafuor i, per intender ci.
La sintassi con cui si dichiar a una pr opr ietà è la seguente:

   01. Property [Nome]() As [Tipo]
   02.     Get
   03.         '...
   04.         Return [Valore restituito]
   05.     End Get
   06.     Set(ByVal value As [Tipo])
   07.         '...
   08.     End Set
   09. End Property

Or a, questa sintassi, nel suo insieme, è molto diver sa da tutto ciò che abbiamo visto fino ad or a. Tuttavia, guar dando
bene, possiamo r iconoscer e alcuni blocchi di codice e r icondur li ad una categor ia pr ecedentemente spiegata:

       La pr ima r iga di codice r icor da la dichiar azione di una var iabile;
       Il blocco Get r icor da una funzione; il codice ivi contenuto viene eseguito quando viene r ichiesto il valor e della
       pr opr ietà;
       Il blocco Set r icor da una pr ocedur a a un par ametr o; il codice ivi contenuto viene eseguito quando un codice
       imposta il valor e della pr opr ietà.

Da quello che ho appena scr itto sembr a pr opr io che una pr opr ietà sia una var iabile pr ogr ammabile, ma allor a da dove
si pr ende il valor e che essa assume? Come ho già r ipetuto, una pr opr ietà media l'inter azione tr a codice ester no e
campo di una classe: quindi dobbiamo stabilir e un modo per collegar e la pr opr ietà al campo che ci inter essa. Ecco un
esempio:

   01. Module Module1
   02.     Class Example
   03.         'Campo pubblico di tipo Single.
   04.         Public _Number As Single
   05.
   06.         'La proprietà Number media, in questo caso, l'uso
   07.         'del campo _Number.
   08.         Public Property Number() As Single
   09.              Get
   10.                  'Quando viene chiesto il valore di Number, viene
   11.                  'restituito il valore della variabile _Number. Si
   12.                  'vede che la proprietà non fa altro che manipolare
   13.                  'una variabile esistente e non contiene alcun
   14.                  'dato di per sé
   15.                  Return _Number
   16.              End Get
   17.              Set(ByVal value As Single)
   18.                  'Quando alla proprietà viene assegnato un valore,
   19.                  'essa modifica il contenuto di _Number impostandolo
   20.                  'esattamente su quel valore
   21.                  _Number = value
   22.              End Set
   23.         End Property
   24.     End Class
   25.
26.     Sub Main()
   27.         Dim A As New Example()
   28.
   29.         'Il codice di Main sta impostando il valore di A.Number.
   30.         'Notare che una proprietà si usa esattamente come una
   31.         'comunissima variabile di istanza.
   32.         'La proprietà, quindi, richiama il suo blocco Set come
   33.         'una procedura e assegna il valore 20 al campo A._Number
   34.         A.Number = 20
   35.
   36.         'Nella prossima riga, invece, viene richiesto il valore
   37.         'di Number per poterlo scrivere a schermo. La proprietà
   38.         'esegue il blocco Get come una funzione e restituisce al
   39.         'chiamante (ossia il metodo/oggetto che ha invocato Get,
   40.         'in questo caso Console.WriteLine) il valore di A._Number
   41.         Console.WriteLine(A.Number)
   42.
   43.         'Per gli scettici, facciamo un controllo per vedere se
   44.         'effettivamente il contenuto di A._Number è cambiato.
   45.         'Potrete constatare che è uguale a 20.
   46.         Console.WriteLine(A._Number)
   47.
   48.         Console.ReadLine()
   49.     End Sub
   50. End Module

Per pr ima cosa bisogna subito far e due impor tanti osser vazioni:

       Il nome della pr opr ietà e quello del campo a cui essa sovr intende sono molto simili. Questa similar ità viene
       mentenuta per l'appunto a causa dello str etto legame che lega pr opr ietà e campo. È una convenzione che il nome
       di un campo mediato da una pr opr ietà inizi con il car atter e under scor e ("_"), oppur e con una di queste
       combinazioni alfanumer iche: "p_", "m_". Il nome usato per la pr opr ietà sar à, invece, identico, ma senza
       l'under scor e iniziale, come in questo esempio.
       Il tipo definito per la pr opr ietà è identico a quello usato per il campo. Abbastanza ovvio, d'altr onde: se essa deve
       mediar e l'uso di una var iabile, allor a anche tutti i valor i r icevuti e r estituiti dovr anno esser e compatibili.




La potenza nasc osta delle proprietà
Ar r ivati a questo punto, uno potr ebbe pensar e che, dopotutto, non vale la pena di spr ecar e spazio per scr iver e una
pr opr ietà quando può acceder e dir ettamente al campo. Bene, se c'è ver amente qualcuno che leggendo quello che ho
scr itto ha pensato ver amente a questo, può anche andar e a compianger si in un angolino buio. XD Scher zi a par te,
l'utilità c'è, ma spesso non si vede. Pr ima di tutto, iniziamo col dir e che se un campo è mediato da una pr opr ietà, per
convenzione (ma anche per buon senso), deve esser e Pr ivate, altr imenti lo si potr ebbe usar e indiscr iminatamente
senza limitazioni, il che è pr opr io quello che noi vogliamo impedir e. A questo possiamo anche aggiunger e una
consider azione: visto che abbiamo la possibilità di far lo, aggiungendo del codice a Get e Set, per chè non far e qualche
contr ollo sui valor i inser iti, giusto per evitar e er r or i peggior i in un immediato futur o? Ammettiamo di aver e la
nostr a bella classe:

   01. Module Module1
   02.     'Questa classe rappresenta un semplice sistema inerziale,
   03.     'formato da un piano orizzontale scabro (con attrito) e
   04.     'una massa libera di muoversi su di esso
   05.     Class InertialFrame
   06.         Private _DynamicFrictionCoefficient As Single
   07.         Private _Mass As Single
   08.         Private _GravityAcceleration As Single
   09.
   10.         'Coefficiente di attrito radente (dinamico), μ
   11.         Public Property DynamicFrictionCoefficient() As Single
   12.              Get
   13.
Return _DynamicFrictionCoefficient
   14.                  End Get
   15.                  Set(ByVal value As Single)
   16.                      _DynamicFrictionCoefficient = value
   17.                  End Set
   18.              End Property
   19.
   20.              'Massa, m
   21.              Public Property Mass() As Single
   22.                  Get
   23.                      Return _Mass
   24.                  End Get
   25.                  Set(ByVal value As Single)
   26.                      _Mass = value
   27.                  End Set
   28.              End Property
   29.
   30.              'Accelerazione di gravità che vale nel sistema, g
   31.              Public Property GravityAcceleration() As Single
   32.                  Get
   33.                      Return _GravityAcceleration
   34.                  End Get
   35.                  Set(ByVal value As Single)
   36.                      _GravityAcceleration = value
   37.                  End Set
   38.              End Property
   39.
   40.              'Calcola e restituisce la forza di attrito che agisce
   41.              'quando la massa è in moto
   42.              Public Function CalculateFrictionForce() As Single
   43.                  Return (Mass * GravityAcceleration) * DynamicFrictionCoefficient
   44.              End Function
   45.
   46.        End Class
   47.
   48.        Sub Main()
   49.            Dim F As New InertialFrame()
   50.
   51.              Console.WriteLine("Sistema inerziale formato da:")
   52.              Console.WriteLine(" - Un piano orizzontale e scabro;")
   53.              Console.WriteLine(" - Una massa variabile.")
   54.              Console.WriteLine()
   55.
   56.              Console.WriteLine("Inserire i dati:")
   57.              Console.Write("Coefficiente di attrito dinamico = ")
   58.              F.DynamicFrictionCoefficient = Console.ReadLine
   59.              Console.Write("Massa (Kg) = ")
   60.              F.Mass = Console.ReadLine
   61.              Console.Write("Accelerazione di gravità (m/s<sup>2</sup>) = ")
   62.              F.GravityAcceleration = Console.ReadLine
   63.
   64.              Console.WriteLine()
   65.              Console.Write("Attrito dinamico = ")
   66.              Console.WriteLine(F.CalculateFrictionForce() & " N")
   67.
   68.            Console.ReadLine()
   69.        End Sub
   70. End    Module

I calcoli funzionano, le pr opr ietà sono scr itte in modo cor r etto, tutto gir a alla per fezione, se non che... qualcuno tr ova
il modo di metter e μ = 2 e m = -7, valor i assur di poiché 0 < μ <= 1 ed m > 0. Modificando il codice delle pr opr ietà
possiamo impor r e questi vincoli ai valor i inser ibili:

   01. Module Module1
   02.     Class InertialFrame
   03.         Private _DynamicFrictionCoefficient As Single
   04.         Private _Mass As Single
   05.         Private _GravityAcceleration As Single
   06.
   07.         Public Property DynamicFrictionCoefficient() As Single
   08.              Get
   09.
Return _DynamicFrictionCoefficient
   10.              End Get
   11.              Set(ByVal value As Single)
   12.                  If (value > 0) And (value <= 1) Then
   13.                       _DynamicFrictionCoefficient = value
   14.                  Else
   15.                       Console.WriteLine(value & " non è un valore consentito!")
   16.                       Console.WriteLine("Coefficiente attrito dinamico = 0.1")
   17.                       _DynamicFrictionCoefficient = 0.1
   18.                  End If
   19.              End Set
   20.          End Property
   21.
   22.          Public Property Mass() As Single
   23.              Get
   24.                  Return _Mass
   25.              End Get
   26.              Set(ByVal value As Single)
   27.                  If value > 0 Then
   28.                       _Mass = value
   29.                  Else
   30.                       Console.WriteLine(value & " non è un valore consentito!")
   31.                       Console.WriteLine("Massa = 1")
   32.                       _Mass = 1
   33.                  End If
   34.              End Set
   35.          End Property
   36.
   37.          Public Property GravityAcceleration() As Single
   38.              Get
   39.                  Return _GravityAcceleration
   40.              End Get
   41.              Set(ByVal value As Single)
   42.                  _GravityAcceleration = Math.Abs(value)
   43.              End Set
   44.          End Property
   45.
   46.          Public Function CalculateFrictionForce() As Single
   47.              Return (Mass * GravityAcceleration) * DynamicFrictionCoefficient
   48.          End Function
   49.
   50.     End Class
   51.
   52.     '...
   53. End Module

In gener e, ci sono due modi di agir e quando i valor i che la pr opr ietà r iceve in input sono er r ati:

       Modificar e il campo r eimpostandolo su un valor e di default, ossia la str ategia che abbiamo adottato per questo
       esempio;
       Lanciar e un'eccezione.

La soluzione for malmente più cor r etta sar ebbe la seconda: il codice chiamante dovr ebbe poi cattur ar e e gestir e tale
eccezione, lasciando all'utente la possibilità di decider e cosa far e. Tuttavia, per far vi fr onte, bisogner ebbe intr odur r e
ancor a un po' di teor ia e di sintassi, r agion per cui il suo uso è stato posto in secondo piano r ispetto alla pr ima. Inoltr e,
bisogner ebbe anche evitar e di por r e il codice che comunica all'utente l'er r or e nel cor po della pr opr ietà e, più in
gener ale, nella classe stessa, poiché questo codice potr ebbe esser e r iutilizzato in un'altr a applicazione che magar i non
usa la console (altr a r agione per sceglier e la seconda possibilità). Mettendo da par te tali osser vazioni di cir costanza,
comunque, si nota come l'uso delle pr opr ietà offr a molta più gestibilità e flessibilità di un semplice campo. E non è
ancor a finita...




Curiosità: dietro le quinte di una proprietà
N.B.: Potete anche pr oceder e a legger e il pr ossimo capitolo, poiché questo par agr afo è pur amente illustr ativo.
Come esempio user ò questa pr opr ietà:

   01. Property Number() As Single
   02.     Get
   03.         Return _Number
   04.     End Get
   05.     Set(ByVal value As Single)
   06.         If (value > 30) And (value < 100) Then
   07.              _Number = value
   08.         Else
   09.              _Number = 31
   10.         End If
   11.     End Set
   12. End Property

Quando una pr opr ietà viene dichiar ata, ci sembr a che essa esista come un'entità unica nel codice, ed è più o meno
ver o. Tuttavia, una volta che il sor gente passa nelle fauci del compilator e, succede una cosa abbastanza singolar e. La
pr opr ietà cessa di esister e e viene invece spezzata in due elementi distinti:

          Una funzione senza par ametr i, di nome "get_[Nome Pr opr ietà]", il cui cor po viene cr eato copiando il codice
          contenuto nel blocco Get. Nel nostr o caso, get_Number :

              1. Function get_Number() As Single
              2.     Return _Number
              3. End Function

          Una pr ocedur a con un par ametr o, di nome "set_[Nome Pr opr ietà]", il cui cor po viene cr eato copiando il codice
          contenuto nel blocco Set. Nel nostr o caso, set_Number :

              1. Sub set_Number(ByVal value As Single)
              2.     If (value > 30) And (value < 100) Then
              3.          _Number = value
              4.     Else
              5.          _Number = 31
              6.     End If
              7. End Sub

Entr ambi i metodi hanno come specificator e di accesso lo stesso della pr opr ietà. Inoltr e, ogni r iga di codice del tipo

    1. [Proprietà] = [Valore]

oppur e

    1. [Valore] = [Proprietà]

viene sostituita con la cor r ispondente r iga:

    1. set_[Nome Proprietà]([Valore])

oppur e:

    1. [Valore] = get_[Nome Proprietà]

Ad esempio, il seguente codice:

    1. Dim A As New Example
    2. A.Number = 20
    3. Console.WriteLine(A.Number)

viene tr asfor mato, dur ante la compilazione, in:

    1. Dim A As New Example
    2. A.set_Number(20)
    3. Console.WriteLine(A.get_Number())
Questo per dir e che una pr opr ietà è un costr utto di alto livello, uno str umento usato nella pr ogr ammazione astr atta:
esso viene scomposto nelle sue par ti fondamentali quando il pr ogr amma passa al livello medio, ossia quando è tr adotto
in IL, lo pseudo-linguaggio macchina del Fr amew or k .NET.
A21. Le Proprietà - Parte II

Proprietà ReadOnly e W riteOnly
Fin'or a abbiamo visto che le pr opr ietà sono in gr ado di mediar e l'inter azione tr a codice ester no alla classe e suoi
campi, e tale mediazione compr endeva la possibilità di r ifiutar e cer ti valor i e consentir ne altr i. Ma non è finita qui:
usando delle apposite keyw or ds è possibile r ender e una pr opr ietà a sola lettur a (ossia è possibile legger ne il valor e ma
non modificar lo) o a sola scr ittur a (ossia è possibile modificar ne il valor e ma non ottener lo). Per quanto r iguar da la
pr ima, viene abbastanza natur ale pensar e che ci possano esser e valor i solo esposti ver so cui è pr oibita la
manipolazione dir etta, magar i per ché par ticolar mente impor tanti o, più spesso, per chè logicamente immutabili (vedi
oltr e per un esempio); spostando l'attenzione per un attimo sulla seconda, per ò, sar à par imenti del tutto lecito
domandar si quale sia la lor o utilità. Le var iabili, i campi, e quindi, per estensione, anche le pr opr ietà, sono per lor o
natur a atti a contener e dati, che ver r anno poi utilizzati in altr e par ti del pr ogr amma: tali dati vengono
continuamente letti e/o modificati e, per quanto sia possibile cr eder e che ve ne siano di immodificabili, come costanti
e valor i a sola lettur a, appar e invece assur da l'esistenza di campi solo modificabili. Per modificar e qualcosa, infatti, se
ne deve conoscer e almeno qualche infor mazione. La r ealtà è che le pr opr ietà Wr iteOnly sono innatur ali per la
str agr ande maggior andza dei pr ogr ammator i; piuttosto di usar le è meglio definir e pr ocedur e. Mi occuper ò quindi di
tr attar e solo la keyw or d ReadOnly.
In br eve, la sintassi di una pr opr ietà a sola lettur a è questa:

    1. ReadOnly Property [Nome]() As [Tipo]
    2.     Get
    3.         '...
    4.         Return [Valore]
    5.     End Get
    6. End Property

Notate che il blocco Set è assente: ovviamente, si tr atta di codice inutile dato che la pr opr ietà non può esser e
modificata. Per continuar e il discor so iniziato pr ima, ci sono pr incipalmente tr e motivi per dichiar ar e un'entità del
gener e:

       I dati a cui essa for nisce accesso sono impor tanti per la vita della classe, ed è quindi necessar io lasciar e che la
       modifica avvenga tr amite altr i metodi della classe stessa. Tuttavia, non c'è motivo di nasconder ne il valor e al
       codice ester no, cosa che può anche r ivelar si molto utile, sia come dato da elabor ar e, sia come infor mazione di
       dettaglio;
       La pr opr ietà espr ime un valor e che non si può modificar e per chè per pr opr ia natur a immutabile. Un classico
       esempio può esser e la data di nascita di una per sona: tipicamente la si inser isce come par ametr o del
       costr uttor e, o la si pr eleva da un database, e viene memor izzata in un campo esposto tr amite pr opr ietà
       ReadOnly. Questo è logico, poiché non si può cambiar e la data di nascita; è quella e basta. Un caso par ticolar e
       sar ebbe quello di un er r or e commesso dur ante l'inser imento della data, che costr inger ebbe a cambiar la. In
       questi casi, la modifica avviene per altr e vie (metodi con autenticazione o modifica del database);
       La pr opr ietà espr ime un valor e che viene calcolato al momento. Questo caso è molto speciale, poiché va al di là
       della nor male funzione di w r apper che le pr opr ietà svolgono nor malmente. Infatti, si può anche scr iver e una
       pr opr ietà che non sovr intende ad alcun campo, ma che, anzi, cr ea un campo fittizio: ossia, da fuor i sembr a che
       ci sia un'infor mazione in più nella classe, ma questa viene solo desunta o inter polata da altr i dati noti. Esempio:

           01. Class Cube
           02.     Private _SideLength As Single
           03.     Private _Density As Single
           04.
           05.     Public Property SideLength() As Single
           06.
Get
          07.                Return _SideLength
          08.            End Get
          09.            Set(ByVal value As Single)
          10.                If value > 0 Then
          11.                     _SideLength = value
          12.                Else
          13.                     _SideLength = 1
          14.                End If
          15.            End Set
          16.        End Property
          17.
          18.        Public Property Density() As Single
          19.            Get
          20.                Return _Density
          21.            End Get
          22.            Set(ByVal value As Single)
          23.                If value > 0 Then
          24.                     _Density = value
          25.                Else
          26.                     _Density = 1
          27.                End If
          28.            End Set
          29.        End Property
          30.
          31.        Public ReadOnly Property SurfaceArea() As Single
          32.            Get
          33.                Return (SideLength ^ 2)
          34.            End Get
          35.        End Property
          36.
          37.        Public ReadOnly Property Volume() As Single
          38.            Get
          39.                Return (SideLength ^ 3)
          40.            End Get
          41.        End Property
          42.
          43.        Public ReadOnly Property Mass() As Single
          44.            Get
          45.                Return (Volume * Density)
          46.            End Get
          47.        End Property
          48. End    Class

       Vedendola dall'ester no, si può pensar e che la classe Cube contenga come dati concr eti (var iabili) SideLength,
       Density, Sur faceAr ea, Volume e Mass, e che questi siano esposti tr amite una pr opr ietà. In r ealtà essa ne
       contiene solo i pr imi due e in base a questi calcola gli altr i.

In questo esempio teor ico, le due pr opr ietà esposte sono r eadonly per il pr imo e il secondo motivo:

   01. Module Esempio3
   02.     Class LogFile
   03.         Private _FileName As String
   04.         Private _CreationTime As Date
   05.
   06.         'Niente deve modificare il nome del file, altrimenti
   07.         'potrebbero verificarsi errori nella lettura o scrittura
   08.         'dello stesso, oppure si potrebbe chiudere un file
   09.         'che non esiste ancora
   10.         Public ReadOnly Property FileName() As String
   11.              Get
   12.                  Return _FileName
   13.              End Get
   14.         End Property
   15.
   16.         'Allo stesso modo non si pu� modificare la data di
   17.         'creazione di un file: una volta creato, viene
   18.         'prelevata l'ora e il giorno e impostata la
   19.         'variabile. Se potesse essere modificata
   20.         'non avrebbe più alcun significato
   21.
Public ReadOnly Property CreationTime() As Date
   22.             Get
   23.                 Return _CreationTime
   24.             End Get
   25.         End Property
   26.
   27.         Public Sub New(ByVal Path As String)
   28.             _FileName = Path
   29.             _CreationTime = Date.Now
   30.         End Sub
   31.     End Class
   32. End Module




Una nota sui tipi referenc e
C'è ancor a un'ultima, ma impor tante, clausola da far notar e per le pr opr ietà ReadOnly. Si è gi� vista la differ enza tr a
i tipi value e i tipi r efer ence: i pr imi contengono un valor e, mentr e i secondi un puntator e all'ar ea di memor ia in cui
r isiede l'oggetto voluto. A causa di questa par ticolar e str uttur a, legger e il valor e di un tipo r efer ence da una pr opr ietà
ReadOnly significa saper ne l'indir izzo, il che equivale ad ottener e il valor e dell'oggetto puntato. Non è quindi
assolutamente sbagliato scr iver e:

   01.   Class ASystem
   02.       Private _Box As Cube
   03.
   04.          Public ReadOnly Property Box() As Cube
   05.              Get
   06.                  Return _Box
   07.              End Get
   08.          End Property
   09.
   10.       '...
   11.   End Class
   12.
   13.   '...
   14.
   15.   Dim S As New ASystem()
   16.   S.Box.SideLength = 4

In questo modo, noi staimo effettivamente modificando l'oggetto S.Box , ma indir ettamente: non stiamo, invece,
cambiando il valor e del campo S._Box , che effettivamente è ciò che ci viene impedito di far e. In sostanza, non stiamo
as s egn an do un nuovo oggetto alla var iabile S._Box , ma stiamo solo manipolando i dati di un oggetto esistente, e
questo è consentito. Anzi, è molto meglio dichiar ar e pr opr ietà di tipo r efer ence come ReadOnly quando non è
necessar io assegnar e o impostar e nuovi oggetti.
A22. Le Proprietà - Parte III

Proprietà c on parametri
Nei due capitoli pr ecedenti, ho sempr e scr itto pr opr ietà che semplicemente r estituivano il valor e di un campo, ossia il
codice del blocco Get non er a nulla di più di un semplice Retur n. Intr oduciamo or a, invece, la possibilità di ottener e
infor mazioni diver se dalla stessa pr opr ietà specificando un par ametr o, pr opr io come fosse un metodo. Avr ete notato,
infatti, che fin dal pr incipio c'er a una coppia di par entesi tonde vicino al nome della pr opr ietà, ossia pr opr io la sintassi
che si usa per dichiar ar e metodi senza par ametr i. Ecco un esempio:

   01. Module Module1
   02.     'Classe che rappresenta un estrattore di numeri
   03.     'casuali
   04.     Class NumberExtractor
   05.         Private _ExtractedNumbers() As Byte
   06.         'Generatore di numeri casuali. Random è una classe
   07.         'del namespace System
   08.         Private Rnd As Random
   09.
   10.         'Questa proprietà ha un parametro, Index, che
   11.         'specifica a quale posizione dell'array si intende recarsi
   12.         'per prelevarne il valore. Nonostante l'array abbia solo 6
   13.         'elementi di tipo Byte, l'indice viene comunemente sempre
   14.         'indicato come intero a 32 bit. È una specie di
   15.         'convenzione, forse derivante dalla maggior facilità di
   16.         'elaborazione su macchine a 32 bit
   17.         Public ReadOnly Property ExtractedNumbers(ByVal Index As Int32) As Byte
   18.              Get
   19.                   If (Index >= 0) And (Index < _ExtractedNumbers.Length) Then
   20.                        Return _ExtractedNumbers(Index)
   21.                   Else
   22.                        Return 0
   23.                   End If
   24.              End Get
   25.         End Property
   26.
   27.         Public Sub New()
   28.              'Essendo di tipo reference, si deve creare un nuovo
   29.              'oggetto Random e assegnarlo a Rnd. La ragione per cui
   30.              'Rnd è un membro di classe consiste nel fatto
   31.              'che se fosse stata variabile temporanea del corpo
   32.              'della procedura ExtractNumbers, sarebbero usciti
   33.              'gli stessi numeri. Questo perchè la sequenza
   34.              'pseudocasuale creata dalla classe non cambia se
   35.              'non glielo si comunica espressamente usando un altro
   36.              'costruttore. Non tratterò questo argomento ora
   37.              Rnd = New Random()
   38.              ReDim _ExtractedNumbers(5)
   39.         End Sub
   40.
   41.         Public Sub ExtractNumbers()
   42.              'Estrae 6 numeri casuali tra 1 e 90 e li pone nell'array
   43.              For I As Int32 = 0 To 5
   44.                   _ExtractedNumbers(I) = Rnd.Next(1, 91)
   45.              Next
   46.         End Sub
   47.     End Class
   48.
   49.     Sub Main()
   50.         Dim E As New NumberExtractor()
   51.
   52.         E.ExtractNumbers()
   53.         Console.WriteLine("Numeri estratti: ")
   54.         For I As Int32 = 0 To 5
   55.              Console.Write(E.ExtractedNumbers(I) & " ")
   56.
Next
   57.
   58.         Console.ReadKey()
   59.     End Sub
   60. End Module

Notar e che sar ebbe stato logicamente equivalente cr ear e una pr opr ietà che r estituisse tutto l'ar r ay, in questo modo:

    1. Public ReadOnly Property ExtractedNumbers() As Byte()
    2.     Get
    3.         Return _ExtractedNumbers
    4.     End Get
    5. End Property

Ma non si sar ebbe avuto alcun contr ollo sull'indice che l'utente avr ebbe potuto usar e: nel pr imo modo, invece, è possibile
contr ollar e l'indice usato e r estituir e 0 qualor a esso non sia coer ente con i limiti dell'ar r ay. La r estituzione di elementi
di una lista, tuttavia, è solo una delle possibilità che le pr opr ietà par ametr iche offr ono, e non c'è limite all'uso che se ne
può far e. Nonostante ciò, è bene sottolinear e che è meglio utilizzar e una funzione o una pr ocedur a (poiché le pr opr ietà
di questo tipo possono anche non esser e r eadonly, questo er a solo un caso) qualor a si debbano eseguir e calcoli o
elabor azioni non immediati, diciamo oltr e le 20/30 r ighe di codice, ma anche di meno, a seconda della pesantezza delle
oper azioni. Fate conto che le pr opr ietà debbano sempr e esser e il più legger e possibile, computazionalmente par lando:
qualche costr utto di contr ollo come If o Select, qualche calcolo sul Retur n, ma nulla di più.




Proprietà di default
Con questo ter mine si indica la pr opr ietà pr edefinita di una classe. Per esister e, essa deve soddisfar e questi due
r equisiti:

        Deve posseder e almeno un par ametr o;
        Deve esser e unica.

Anche se solitamente si usa in altr e cir costanze, ecco una pr opr ietà di default applicata al pr ecedente esempio:

   01. Module Module1
   02. Class NumberExtractor
   03.         Private _ExtractedNumbers() As Byte
   04.         Private Rnd As Random
   05.
   06.         'Una proprietà di default si dichiara come una
   07.         'normalissima proprietà, ma anteponendo allo specificatore
   08.         'di accesso la keyword Default
   09.         Default Public ReadOnly Property ExtractedNumbers(ByVal Index As Int32) As Byte
   10.              Get
   11.                   If (Index >= 0) And (Index < _ExtractedNumbers.Length) Then
   12.                        Return _ExtractedNumbers(Index)
   13.                   Else
   14.                        Return 0
   15.                   End If
   16.              End Get
   17.         End Property
   18.
   19.         Public Sub New()
   20.              Rnd = New Random()
   21.              ReDim _ExtractedNumbers(5)
   22.         End Sub
   23.
   24.         Public Sub ExtractNumbers()
   25.              For I As Int32 = 0 To 5
   26.                   _ExtractedNumbers(I) = Rnd.Next(1, 91)
   27.              Next
   28.         End Sub
   29.     End Class
   30.
   31.     Sub Main()
   32.
Dim E As New NumberExtractor()
   33.
   34.         E.ExtractNumbers()
   35.         Console.WriteLine("Numeri estratti: ")
   36.         For I As Int32 = 0 To 5
   37.              'Ecco l'utilità delle proprietà di default: si possono
   38.              'richiamare anche omettendone il nome. In questo caso
   39.              'E(I) è equivalente a scrivere E.ExtractedNumbers(I),
   40.              'ma poiché ExtractedNumbers è di default,
   41.              'viene desunta automaticamente
   42.              Console.Write(E(I) & " ")
   43.         Next
   44.
   45.         Console.ReadKey()
   46.     End Sub
   47. End Module

Dal codice salta subito all'occhio la motivazione dei due pr er equisiti specificati inizialmente:

         Se la pr opr ietà non avesse almeno un par ametr o, sar ebbe impossibile per il compilator e saper e quando il
         pr ogr ammator e si sta r ifer endo all'oggetto e quando alla sua pr opr ietà di default;
         Se non fosse unica, sar ebbe impossibile per il compilator e decider e quale pr ender e.

Le pr opr ietà di default sono molto diffuse, specialmente nell'ambito degli oggetti w indow s for m, ma spesso non le si sa
r iconoscer e. Anche per quello che abbiamo impar ato fin'or a, per ò, possiamo scovar e un esempio di pr opr ietà di
default. Il tipo Str ing espone una pr opr ietà par ametr izzata Char s(I), che per mette di saper e quale car atter e si tr ova
alla posizione I nella str inga, ad esempio:

    1.    Dim S As String = "Ciao"
    2.    Dim C As Char = S.Chars(1)
    3.    ' > C = "i", poiché "i" è il carattere alla posizione 1
    4.    ' nella stringa S

Ebbene, Char s è una pr opr ietà di default, ossia è possibile scr iver e:

    1. Dim S As String = "Ciao"
    2. Dim C As Char = S(1)
    3. ' > C = "i"




Get e Set c on spec ific atori di ac c esso
Anche se a pr ima vista potr ebbe sembr ar e str ano, sì, è possibile assegnar e uno specificator e di accesso anche ai singoli
blocchi Get e Set all'inter no di una pr opr ietà. Questa peculiar e car atter istica viene sfr uttata ver amente poco, ma offr e
una gr ande flessibilità e un'altr ettanto gr ande potenzialità di gestione. Limitando l'accesso ai singoli blocchi, è possibile
r ender e una pr opr ietà ReadOnly solo per cer te par ti di codice e/o Wr iteOnly solo per altr e par ti, pur senza usar e
dir ettamente tali keyw or ds. Ovviamente, per esser e logicamente applicabili, gli specificator i di accesso dei blocchi
inter ni devono esser e più r estr ittivi di quello usato per contr assegnar e la pr opr ietà stessa. Infatti, se una pr opr ietà è
pr ivata, ovviamente non potr à aver e un blocco get pubblico. In gener e, la ger ar chia di r estr ittività segue questa lista,
dove Public è il meno r estr ittivo e Pr ivate il più r estr ittivo:

         Public
         Pr otected Fr iend
         Fr iend
         Pr otected
         Pr ivate

Altr a condizione necessar ia è che uno solo tr a Get e Set può esser e mar cato con uno scope diver so da quello della
pr opr ietà. Non avr ebbe senso, infatti, ad esempio, definir e una pr opr ietà pubblica con un Get Fr iend e un Set Pr ivate,
poiché non sar ebbe più pubblica (in quanto sia get che set non sono pubblici)! Ecco un esempio:

    1. Public Property A() As Byte
    2.     Get
    3.         '...
    4.     End Get
    5.     Private Set(ByVal value As Byte)
    6.         '...
    7.     End Set
    8. End Property

La pr opr ietà A è sempr e leggibile, ma modificabile solo all'inter no della classe che la espone. In pr atica, è come una
nor male pr opr ietà per il codice inter no alla classe, ma come una ReadOnly per quello ester no. È pur ver o che in questo
caso, si sar ebbe potuto r ender la dir ettamente ReadOnly e modificar e dir ettamente il campo da essa avvolto invece che
espor r e un Set pr ivato, ma sono punti di vista. Ad ogni modo, l'uso di scope diver sificati per mette di far e di tutto e di
più ed è solo un caso che non mi sia venuto in mente un esempio più significativo.




Mettiamo un po' d'ordine sulle key w ord
In questi ultimi capitoli ho spiegato un bel po' di keyw or d diver se, e specialmente nelle pr opr ietà può accader e di dover
specificar e molte keyw or d insieme. Ecco l'or dine cor r etto (anche se l'editor del nostr o ambiente di sviluppo le r ior dina
per noi nel caso dovessimo sbagliar e):

    1. [Default] [ReadOnly/WriteOnly] [Public/Friend/Private/...] Property ...

E or a quelle che conoscete sono ancor a poche... pr ovate voi a scr iver e una pr opr ietà del gener e:

    1. Default Protected Friend Overridable Overloads ReadOnly Property A(ByVal Index As Int32) As
          Byte
    2.      Get
    3.          '...
    4.      End Get
    5. End Property




N.B.: ovviamente, tutto quello che si è detto fin'or a sulle pr opr ietà nelle classi vale anche per le str uttur e!
A23. Membri Shared

Tutte le categor ie di membr i che abbiamo analizzato nei pr ecedenti capitoli - campi, metodi, pr opr ietà, costr uttor i -
sono sempr e state viste come appar tenenti ad un oggetto, ad un'istanza di classe. Infatti, ci si r ifer isce ad una
pr opr ietà o a un metodo di un o s pecifico oggetto, dicendo ad esempio "La pr opr ietà SideLength dell'oggetto A di tipo
Cube vale 3, mentr e quella dell'oggetto B anch'esso di tipo Cube vale 4.". La classe, ossia il tipo di una var iabile
r efer ence, ci diceva solo quali membr i un cer to oggetto potesse espor r e: ci for niva, quindi, il "pr ogetto di costr uzione"
di un oggetto nella memor ia, in cui si potevano collocar e tali campi, tali metodi, tal'altr e pr opr ietà e via dicendo.
Or a, un membr o shar ed, o co ndiv iso , o statico (ter mine da usar si più in C# che non in VB.NET), non appar tiene più
ad un'istanza di classe, ma alla classe stessa. Mi r endo conto che il concetto possa esser e all'inizio difficile da capir e e da
inter ior izzar e cor r ettamente. Per questo motivo far ò un esempio il più semplice, ma più significativo possibile.
Ripr endiamo la classe Cube, modificata come segue:

   01. Module Module1
   02.
   03.     Class Cube
   04.         Private _SideLength As Single
   05.         Private _Density As Single
   06.
   07.         'Questo campo è Shared, condiviso. Come vedete,
   08.         'per dichiarare un membro come tale, si pone la
   09.         'keyword Shared dopo lo specificatore di accesso. Questa
   10.         'variabile conterrà il numero di cubi creati
   11.         'dal nostro programma.
   12.         'N.B.: I campi Shared sono di default Private...
   13.         Private Shared _CubesCount As Int32 = 0
   14.
   15.         Public Property SideLength() As Single
   16.              Get
   17.                  Return _SideLength
   18.              End Get
   19.              Set(ByVal value As Single)
   20.                  If value > 0 Then
   21.                       _SideLength = value
   22.                  Else
   23.                       _SideLength = 1
   24.                  End If
   25.              End Set
   26.         End Property
   27.
   28.         Public Property Density() As Single
   29.              Get
   30.                  Return _Density
   31.              End Get
   32.              Set(ByVal value As Single)
   33.                  If value > 0 Then
   34.                       _Density = value
   35.                  Else
   36.                       _Density = 1
   37.                  End If
   38.              End Set
   39.         End Property
   40.
   41.         Public ReadOnly Property SurfaceArea() As Single
   42.              Get
   43.                  Return (SideLength ^ 2)
   44.              End Get
   45.         End Property
   46.
   47.         Public ReadOnly Property Volume() As Single
   48.              Get
   49.                  Return (SideLength ^ 3)
   50.
End Get
   51.             End Property
   52.
   53.             Public ReadOnly Property Mass() As Single
   54.                 Get
   55.                     Return (Volume * Density)
   56.                 End Get
   57.             End Property
   58.
   59.             'Allo stesso modo, la proprietà che espone il membro
   60.             'shared deve essere anch'essa shared
   61.             Public Shared ReadOnly Property CubesCount() As Int32
   62.                 Get
   63.                     Return _CubesCount
   64.                 End Get
   65.             End Property
   66.
   67.            'Ogni volta che un nuovo cubo viene creato, _CubesCount
   68.            'viene aumentato di uno, per rispecchiare il nuovo numero
   69.            'di istanze della classe Cube esistenti in memoria
   70.            Sub New()
   71.                _CubesCount += 1
   72.            End Sub
   73.        End Class
   74.
   75.        Sub Main()
   76.            Dim Cube1 As New Cube()
   77.            Cube1.SideLength = 1
   78.            Cube1.Density = 2700
   79.
   80.             Dim Cube2 As New Cube()
   81.             Cube2.SideLength = 0.9
   82.             Cube2.Density = 3500
   83.
   84.             Console.Write("Cubi creati: ")
   85.             'Notate come si accede a un membro condiviso: poiché
   86.             'appartiene alla classe e non alla singola istanza, vi si
   87.             'accede specificando prima il nome della classe, poi
   88.             'il comune operatore punto, e successivamente il nome
   89.             'del membro. Tutti i membri shared funzionano in questo
   90.             'modo
   91.             Console.WriteLine(Cube.CubesCount)
   92.
   93.            Console.ReadKey()
   94.        End Sub
   95. End    Module

Facendo cor r er e l'applicazione, si vedr à appar ir e a scher mo il numer o 2, poiché abbiamo cr eato due oggetti di tipo
Cube. Come si vede, il campo CubesCount non r iguar da un solo specifico oggetto, ma la totalità di tutti gli oggetti di
tipo Cube, poiché è un dato globale. In gener ale, esso è di dominio della classe Cube, ossia della r appr esentazione più
astr atta dell'essenza di ogni oggetto: per saper e quanti cubi sono stati cr eati, non si può inter pellar e una singola
istanza, per chè essa non "ha per cezione" di tutte le altr e istanze esistenti. Per questo motivo CubesCount è un
membr o condiviso.
Anche in questo caso c'è una r istr etta gamma di casi in cui è oppor tuno sceglier e di definir e un membr o come
condiviso:

       Quando contiene infor mazioni r iguar danti la totalità delle istanze di una classe, come in questo caso;
       Quando contiene infor mazioni accessibili e necessar ie a tutte le istanze della classe, come illustr er ò fr a qualche
       capitolo;
       Quando si tr atta di un metodo "di libr er ia". I cosiddetti metodi di libr er ia sono metodi sempr e shar ed che
       svolgono funzioni gener ali e sono utilizzabili da qualsiasi par te del codice. Un esempio potr ebbe esser e la
       funzione Math.Abs(x ), che r estituisce il valor e assoluto di x . Come si vede, è shar ed poiché vi si accede usando il
       nome della classe. Inoltr e, essa è sempr e usabile, poiché si tr atta di una semplice funzione matematica, che,
       quindi, for nisce ser vizi di or dine gener ale;
Quando si tr ova in un modulo, come spiegher ò nel pr ossimo par agr afo.




Classi Shared
Come!?!? Esistono classi shar ed? Ebbene sì. Può sembr ar e assur do, ma ci sono, ed è lecito domandar si quale sia la lor o
funzione. Se un membr o shar ed appar tiene a una classe... cosa possiamo dir e di una classe shar ed?
A dir e il ver o, abbiamo sempr e usato classi shar ed senza saper lo: i moduli, infatti, non sono altr o che classi condivise (o
statiche). Tuttavia, il significato della par ola shar ed, se applicato alle classi, cambia r adicalmente. Un modulo, quindi
una classe shar ed, r ende implicitamente shar ed tutti i suoi membr i. Quindi, tutte le pr opr ietà, i campi e i metodi
appar tenenti ad un modulo - ivi compr esa la Sub Main - sono membr i shar ed. Che senso ha questo? I moduli sono
consuetamente usati, al di fuor i delle applicazioni console, per r ender e disponibili a tutto il pr ogetto membr i di
par ticolar e r ilevanza o utilità, ad esempio funzioni per il salvataggio dei dati, infor mazioni sulle opzioni salvate,
ecceter a... Infatti è impossibile definir e un membr o shar ed in un modulo, poiché ogni membr o del modulo lo è già di
per sé:

    1. Module Module1
    2.     Shared Sub Hello()
    3.
    4.     End Sub
    5.
    6.     '...
    7. End Sub

Il codice sopr a r ipor tato pr ovocher à il seguente er r or e:

    1. Methods in a Module cannot be declared 'Shared'.

Inoltr e, è anche possibile acceder e a membr i di un modulo senza specificar e il nome del modulo, ad esempio:

   01.    Module Module2
   02.        Sub Hello()
   03.            Console.WriteLine("Hello!")
   04.        End Sub
   05.    End Module
   06.
   07.    Module Module1
   08.        Sub Main()
   09.            Hello() ' = Module2.Hello()
   10.            Console.ReadKey()
   11.        End Sub
   12.    End Module

Questo fenomeno è anche noto col nome di Im po r ts statico .
A dir la ver ità esiste una piccola differ enza tr a classi statiche e moduli. Una classe può esser e statica anche solo se
tutti i suoi membr i lo sono, ma non gode dell'Impor ts Statico. Un modulo, al contr ar io, oltr e ad aver e tutti i membr i
shar ed, gode sempr e dell'Impor ts Statico. Per far la br eve:

   01.    Module Module2
   02.        Sub Hello()
   03.            Console.WriteLine("Hello Module2!")
   04.        End Sub
   05.    End Module
   06.
   07.    Class Class2
   08.        Shared Sub Hello()
   09.            Console.WriteLine("Hello Class2!")
   10.        End Sub
   11.    End Class
   12.
   13.    Module Module1
   14.        Sub Main()
   15.
'Per richiamare l'Hello di Class2, è sempre
16.         'necessaria questa sintassi:
17.         Class2.Hello()
18.         'Per invocare l'Hello di Module2, invece, basta
19.         'questa, a causa dell'Imports Statico
20.         Hello()
21.         Console.ReadKey()
22.     End Sub
23. End Module
A24. ArrayList, HashTable e SortedList

Abbiamo già ampiamente visto e illustr ato il funzionamento degli ar r ay. Ho anche già detto più volte come essi non
siano sempr e la soluzione miglior e ai nostr i pr oblemi di immagazzinamento dati. Infatti, è difficile decider ne la
dimensione quando non si sa a pr ior i quanti dati ver r anno immessi: inoltr e, è oner oso in ter mini di tempo e r isor se
modificar ne la lunghezza mentr e il pr ogr amma gir a; e nel caso contr ar io, è molto limitativo conceder e all'utente un
numer o pr efissato massimo di valor i. A questo pr oposito, ci vengono in aiuto delle classi già pr esenti nelle libr er ie
standar d del Fr amew or k .NET che aiutano pr opr io a gestir e insiemi di elementi di lunghezza var iabile. Di seguito ne
pr opongo una br eve panor amica.




Array List
Si tr atta di una classe per la gestione di liste di elementi. Essendo un tipo r efer ence, quindi, segue che ogni oggetto
dichiar ato come di tipo Ar r ayList debba esser e inizializzato pr ima dell'uso con un adeguato costr uttor e. Una volta
cr eata un'istanza, la si può utilizzar e nor malmente. La differ enza con l'Ar r ay r isiede nel fatto che l'Ar r ayList, all'inizio
della sua "vita", non contiene nessun elemento, e, di conseguenza occupa r elativamente meno memor ia. Infatti, quando
noi inizializziamo un ar r ay, ad esempio così:

    1. Dim A(100) As Int32

nel momento in cui questo codice viene eseguito, il pr ogr amma r ichiede 101 celle di memor ia della gr andezza di 4
bytes ciascuna da r iser var e per i pr opr i dati: che esse siano impostate o meno (all'inizio sono tutti 0), non ha
impor tanza, per chè A occuper à sempr e la stessa quantità di memor ia. Al contr ar io l'Ar r ayList non "sa" nulla su quanti
dati vor r emmo intr odur r e, quindi, ogni volta che un nuovo elemento viene intr odotto, esso si es pan de allocando
dinamicamente nuova memor ia solo se ce n'è bisogno. In questo r isiede la potenza delle liste.
Per aggiunger e un nuovo elemento all'ar r aylist bisogna usar e il metodo d'istanza Add, passandogli come par ametr o il
valor e da aggiunger e. Ecco un esempio:

   01. Module Module1
   02.
   03.     Class Cube
   04.         '...
   05.     End Class
   06.
   07.     Sub Main()
   08.         'Crea un nuovo arraylist
   09.         Dim Cubes As New ArrayList
   10.
   11.         Console.WriteLine("Inserismento cubi:")
   12.         Console.WriteLine()
   13.         Dim Cmd As Char
   14.         Do
   15.              Console.WriteLine()
   16.              Dim C As New Cube
   17.              'Scrive il numero del cubo
   18.              Console.Write((Cubes.Count + 1) & " - ")
   19.              Console.Write("Lato (m): ")
   20.              C.SideLength = Console.ReadLine
   21.
   22.              Console.Write("    Densità (kg/m<sup>3</sup>): ")
   23.              C.Density = Console.ReadLine
   24.
   25.              'Aggiunge un nuovo cubo alla collezione
   26.              Cubes.Add(C)
   27.
   28.              Console.WriteLine("Termina inserimento? y/n")
   29.
Cmd = Console.ReadKey().KeyChar
   30.         Loop Until Char.ToLower(Cmd) = "y"
   31.
   32.         'Calcola la massa totale di tutti i cubi nella lista
   33.         Dim TotalMass As Single = 0
   34.         'Notate che l'ArrayList si può usare come un
   35.         'normale array. L'unica differenza sta nel fatto che
   36.         'esso espone la proprietà Count al posto di Length.
   37.         'In genere, tutte le liste espongono Count, che comunque
   38.         'ha sempre lo stesso significato: restituisce il numero
   39.         'di elementi nella lista
   40.         For I As Int32 = 0 To Cubes.Count - 1
   41.              TotalMass += Cubes(I).Mass
   42.         Next
   43.
   44.         Console.WriteLine("Massa totale: " & TotalMass)
   45.         Console.ReadKey()
   46.     End Sub
   47. End Module

Allo stesso modo, è possibile r imuover e o inser ir e elementi con altr i metodi:

         Remove(x ) : r imuove l'elemento x dall'ar r aylist
         RemoveAt(x ) : r imuove l'elemento che si tr ova nella posizione x dell'Ar r ayList
         Index Of(x ) : r estituisce l'indice dell'elemento x
         Contains(x ) : r estituisce Tr ue se x è contenuto nell'Ar r ayList, altr imenti False
         Clear : pulisce l'ar r aylist eliminando ogni elemento
         Clone : r estituisce una copia esatta dell'Ar r ayList. Questo ar gomento ver r à discusso più in là nella guida.




Hashtable
L'Hashtable possiede un meccanismo di allocazione della memor ia simile a quello di un Ar r ayList, ma è concettualmente
differ ente in ter mini di utilizzo. L'Ar r ayList, infatti, non si discosta molto, par lando di pr atica, da un Ar r ay - e infatti
vediamo questa somiglianza nel nome: ogni elemento è pur sempr e contr addistinto da un indice, e mediante questo è
possibile ottener ne o modificar ne il valor e; inoltr e, gli indici sono sempr e su base 0 e sono sempr e numer i inter i,
gener almente a 32 bit. Quest'ultima peculiar ità ci per mette di dir e che in un Ar r ayList gli elementi sono logicamente
or dinati. In un Hashtable, al contr ar io, tutto ciò che ho esposto fin'or a non vale. Questa nuova classe si basa
sull'associazione di una chiav e (key) con un v alo r e (value). Quando si aggiunge un nuovo elemento all'Hashtable, se ne
deve specificar e la chiave, che può esser e qualsiasi cosa: una str inga, un numer o, una data, un oggetto, ecceter a...
Quando si vuole r ipescar e quello stesso elemento bisogna usar e la chiave che gli er a stata associata. Usando numer i
inter i come chiavi si può s imulare il compor tamento di un Ar r ayList, ma il meccanismo intr inseco di questo tipo di
collezione r imane pur sempr e molto diver so. Ecco un esempio:

   01.    'Hashtabel contenente alcuni materiali e le
   02.    'relative densità
   03.    Dim H As New Hashtable
   04.    'Aggiunge un elemento, contraddistinto da una chiave stringa
   05.    H.Add("Acqua", 1000)
   06.    H.Add("Alluminio", 2700)
   07.    H.Add("Argento", 10490)
   08.    H.Add("Nichel", 8800)
   09.
   10.    '...
   11.    'Possiamo usare l'hashtable per associare
   12.    'facilmente densità ai nostri cubi:
   13.    Dim C As New Cube(1, H("Argento"))

Notar e che è anche possibile far e il contr ar io, ossia:

    1. Dim H As New Hashtable
    2.
H.Add(1000, "Acqua")
    3. H.Add(2700, "Alluminio")
    4. H.Add(10490, "Argento")
    5. H.Add(8800, "Nichel")

In quest'ultimo esempio, l'Hashtable contiene quattr o chiavi costituite da valor i numer ici: non è comunque possibile
ciclar le usando un For . Infatti, negli Ar r ayList e negli Ar r ay, abbiamo la gar anzia che se la collezione contiene 8
elementi, ad esempio, ci sar anno sempr e degli indici inter i validi tr a 0 e 7; con gli Hashtable, al contr ar io, non
possiamo desumer e n ulla sulle chiavi osser vando il semplice numer o di elementi. In gener e, per iter ar e attr aver so gli
elementi di un Hashtable, si usano dei costr utti For Each:

    1. For Each V As String In H.Values
    2.      'Enumera tutti gli elementi di H
    3.      ' V = "Acqua", "Alluminio", "Argento", ...
    4. Next



    1. For Each K As Int32 In H.Keys
    2.      'Enumera tutte le chiavi
    3.      'K = 1000, 2700, 10490, ...
    4. Next

Per l'iter azione ci vengono in aiuto le pr opr ietà Values e Keys, che contengono r ispettivamente tutti i valor i e tutte le
chiavi dell'Hashtable: queste collezioni sono a sola lettur a, ossia non è possibile modificar le in alcun modo. D'altr onde, è
abbastanza ovvio: se aggiungessimo una chiave l'Hashtable non sapr ebbe a quale elemento associar la. L'unico modo per
modificar le è indir etto e consiste nell'usar e metodi come Add, Remove, ecceter a... che sono poi gli stessi di Ar r ayList.




SortedList
Si compor ta esattamente come un Hashtable, solo che gli elementi vengono mantenuti sempr e in or dine secondo la
chiave.
A25. Metodi factory

Si definisce Factor y un metodo che ha come unico scopo quello di cr ear e una nuova istanza di una classe e r estituir e
tale istanza al chiamante (dato che si par la di "r estituir e", i metodi Factor y sar anno sempr e funzioni). Or a, ci si
potr ebbe chieder e per chè usar e metodi factor y al posto di nor mali costr uttor i. La differ enza tr a questi non è da
sottovalutar e: i costr uttor i ser vono ad istanziar e un oggetto, ma, una volta avviati, non possono "fer mar si". Con
questo voglio dir e che, qualor a venisser o r iscontr ati degli er r or i nei par ametr i di cr eazione dell'istanza (nel caso ce ne
siano), il costr uttor e cr eer ebbe comunque un nuovo oggetto, ma molto pr obabilmente quest'ultimo conter r ebbe dati
er r onei. Un metodo Factor y, invece, contr olla che tutto sia a posto pr im a di cr ear e il nuovo oggetto: in questo modo,
se c'è qualcosa che non va, lo può comunicar e al pr ogr ammator e (o all'utente), ad esempio lanciando un'eccezione o
visualizzando un messaggio di er r or e. E' convenzione - ma è anche logica - che un metodo Factor y sia definito sempr e
all'inter no della stessa classe che cor r isponde al suo tipo di output e che sia Shar ed (altr imenti non si potr ebbe
r ichiamar e pr ima della cr eazione dell'oggetto, ovviamente). Un esempio di quanto detto:

   01. Module Module1
   02.     Class Document
   03.         'Campo statico che contiene tutti i documenti
   04.         'aperi fin'ora
   05.         Private Shared Documents As New Hashtable
   06.         'Identificatore del documento: un paragrafo nel prossimo
   07.         'capitolo spiegherà in dettaglio i significato e
   08.         'l'utilità delle variabili ReadOnly
   09.         Private ReadOnly _ID As Int16
   10.         'Nome del file e testo contenuto in esso
   11.         Private ReadOnly _FileName, _Text As String
   12.
   13.         Public ReadOnly Property ID() As Int16
   14.              Get
   15.                  Return _ID
   16.              End Get
   17.         End Property
   18.
   19.         Public ReadOnly Property FileName() As String
   20.              Get
   21.                  Return _FileName
   22.              End Get
   23.         End Property
   24.
   25.         Public ReadOnly Property Text() As String
   26.              Get
   27.                  Return _Text
   28.              End Get
   29.         End Property
   30.
   31.         'Da notare il costruttore Private: nessun client al di
   32.         'fuori della classe può inizializzare il nuovo
   33.         'oggetto. Solo il metodo factory lo può fare
   34.         Private Sub New(ByVal ID As Int16, ByVal Path As String)
   35.              Me._ID = ID
   36.              Me._FileName = Path
   37.              Me._Text = IO.File.ReadAllText(Path)
   38.              'Me fa riferimento alla classe stessa
   39.              Documents.Add(ID, Me)
   40.         End Sub
   41.
   42.         'Il metodo factory crea un documento se non esiste l'ID
   43.         'e se il percorso su disco è diverso, altrimenti
   44.         'restituisce il documento che esiste già
   45.         Public Shared Function Create(ByVal ID As Int16, _
   46.              ByVal Path As String) As Document
   47.              If Documents.ContainsKey(ID) Then
   48.                  'Ottiene il documento già esistente con questo ID
   49.
Dim D As Document = Documents(ID)
   50.                    'Se coincidono sia l'ID che il nome del file,
   51.                    'allora restituisce l'oggetto già esistente
   52.                    If D.FileName = Path Then
   53.                         Return D
   54.                    Else
   55.                         'Altrimenti restituisce Nothing, dato che non
   56.                         'possono esistere due documenti con uguale ID,
   57.                         'o si farebbe confusione
   58.                         Return Nothing
   59.                    End If
   60.                End If
   61.                'Se non esiste un documento con questo ID, lo crea
   62.                Return New Document(ID, Path)
   63.            End Function
   64.        End Class
   65.
   66.        Sub Main()
   67.            Dim D As     Document   =   Document.Create(0,     "C:testo.txt")
   68.            Dim E As     Document   =   Document.Create(0,     "C:testo.txt")
   69.            Dim F As     Document   =   Document.Create(0,     "C:file.txt")
   70.            Dim G As     Document   =   Document.Create(1,     "C:file.txt")
   71.
   72.            'Dimostra che se ID e Path coincidono, i due oggetti
   73.            'sono la stessa istanza
   74.            Console.WriteLine(E Is D)
   75.            'Dimostra che se l'ID esiste già, ma il Path differisce,
   76.            'l'oggetto restituito è Nothing
   77.            Console.WriteLine(F Is Nothing)
   78.            Console.ReadKey()
   79.        End Sub
   80. End    Module

Il codice sopr a r ipor tato cr ea volutamente tutte le situazioni contemplate all'inter no del metodo factor y statico: E ha
gli stessi par ametr i di D, quindi nel metodo factor y usato per cr ear e E viene r estituita l'istanza D già esistente; F ha
lo stesso ID, quindi è Nothing. A pr ova di ciò, sullo scher mo appar ir à il seguente output:

    1. True
    2. True




Classi fac tory e oggetti immutabili
Una classe contenente solo metodi factor y è detta classe factor y. Il più delle volte, l'uso di una tattica simile a quella
sopr a r ipor tata potr ebbe por tar e alcuni dubbi: dato che esistono due var iabili che puntano alla stessa istanza, il
modificar ne l'una potr ebbe causar e l'automatica modifica dell'altr a. Tuttavia, spesse volte, gli oggetti che possono
esser e cr eati con metodi factor y non espongono alcun altr o metodo per la modifica o l'eliminazione dello stesso
oggetto, che quindi non può esser e cambiato in alcun modo. Oggetti di questo tipo sono detti im m utabili: un esempio
di oggetti immutabili sono la str inghe. Al contr ar io di come si potr ebe pensar e, una volta cr eate il lor o valor e non può
esser e cambiato: l'unica cosa che si può far e è assegnar e alla var iabile str inga un nuovo valor e:

    1.   'Questa stringa è      immutabile
    2.   Dim S As String =      "Ciao"
    3.   'Viene creata una      nuova stringa temporanea con valore "Buongiorno"
    4.   'e assegnata a S.      "Ciao" verrà distrutta dal Garbage Colletcion
    5.   S = "Buongiorno"
A26. Costruttori

Come si è accennato nelle pr ecedenti lezioni, i costr uttor i ser vono a cr ear e un oggetto, un'istanza mater iale della
classe. Ogni costr uttor e, poichè ce ne può esser e anche più di uno, è sempr e dichiar ato usando la keyw or d New e non
può esser e altr imenti. Si possono passar e par ametr i al costr uttor e allo stesso modo di come si passano alle nor mali
pr ocedur e o funzioni, specificandoli tr a par entesi. Il codice scr itto nel costr uttor e viene eseguito pr ima di ogni altr o
metodo nella classe, per ciò può anche modificar e le var iabili r ead-only (in sola lettur a), come vedr emo in seguito. Anche
i moduli possono aver e un costr uttor e e questo viene eseguito pr ima della pr ocedur a Main. Una cosa da tener e bene a
mente è che, nonostante New sia eseguito pr ima di ogni altr a istr uzione, sia le costanti sia i campi con inizializzator e
(ad esempio Dim I As Int32 = 50) sono già stati inizializzati e contengono già il lor o valor e. Esempio:

   01. Module Module1
   02.     'Classe
   03.     Class Esempio
   04.         'Costante pubblica
   05.         Public Const Costante As Byte = 56
   06.         'Variabile pubblica che non pu� essere modificata
   07.         Public ReadOnly Nome As String
   08.         'Variabile privata
   09.         Private Variabile As Char
   10.
   11.         'Costruttore della classe: accetta un parametro
   12.         Sub New(ByVal Nome As String)
   13.              Console.WriteLine("Sto inizializzando un oggetto Esempio...")
   14.              'Le variabili ReadOnly sono assegnabli solo nel
   15.              'costruttore della classe
   16.              Me.Nome = Nome
   17.              Me.Variabile = "c"
   18.         End Sub
   19.     End Class
   20.
   21.     'Costruttore del Modulo
   22.     Sub New()
   23.         Console.WriteLine("Sto inizializzando il Modulo...")
   24.     End Sub
   25.
   26.     Sub Main()
   27.         Dim E As New Esempio("Ciao")
   28.         E.Nome = "Io" ' Sbagliato: Nome è ReadOnly
   29.         Console.ReadKey()
   30.     End Sub
   31. End Module

Quando si fa cor r er e il pr ogr amma si ha questo output:

    1. Sto inizializzando il Modulo...
    2. Sto inizializzando un oggetto Esempio...

L'esempio mostr a l'or dine in cui vengono eseguiti i costr uttor i: pr ima viene inizializzato il modulo, in seguito viene
inizializzato l'oggetto E, che occupa la pr ima linea di codice della pr ocedur a Main. È evidente che Main viene eseguita
dopo New .




V ariabili ReadOnly
Ho par lato pr ima delle var iabili ReadOnly e ho detto che possono solamente esser e lette ma non modificate. La
domanda che viene spontaneo por si è: non sar ebbe meglio usar e una costante? La differ enza è più mar cata di quanto
sembr i: le costanti devono esser e inizializzate con un valor e immutabile, ossia che definisce il pr ogr ammator e mentr e
scr ive il codice (ad esempio, 1, 2, "Ciao" ecceter a); la var iabili ReadOnly possono esser e impostate nel costr uttor e, ma,
cosa più impor tante, possono assumer e il valor e der ivante da un'espr essione o da una funzione. Ad esempio:

    1. Public Const Data_Creazione_C As Date = Date.Now 'Sbagliato!
    2. Public ReadOnly Data_Creazione_V As Date = Date.Now ' Giusto

La pr ima istr uzione gener a un er r or e "Costant ex pr ession is r equir ed!" ("È r ichiesta un'espr essione costante!"),
der ivante dal fatto che Date.Now è una funzione e, come tale, il suo valor e, pur pr eso una sola volta, non è costante,
ma può var iar e. Non si pone nessun pr oblema, invece, per le var iabili ReadOnly, poichè sono sempr e var iabili.




Costruttori Shared
I costr uttor i Shar ed sono detti co str utto r i statici e vengono eseguiti solamente quando è cr eata la pr im a istanza di
una data classe: per questo sono detti anche co str utto r i di classe o di tipo poichè non appar tengono ad ogni singolo
oggetto che da quella classe pr ende la str uttur a, ma piuttosto alla classe stessa (vedi differ enza tr a classe e oggetto).
Un esempio di una possibile applicazione può esser e questo: si sta scr ivendo un pr ogr amma che tiene tr accia di ogni
er r or e r ipor tandolo su un file di log, e gli er r or i vengono gestiti da una classe Er r or s. Data la str uttur a
dell'applicazione, possono esister e più oggetti di tipo Er r or s, ma tutti devono condivider e un file comune... Come si fa?
Costr uttor e statico! Questo fa in modo che si apr a il file di log solamente una volta, ossia quando viene istanziato il
pr imo oggetto Er r or s. Esempio:

   01. Module Esempio
   02.     Class Errors
   03.         'Variabile statica che rappresenta un oggetto in grado
   04.         'di scrivere su un file
   05.         Public Shared File As IO.StreamWriter
   06.
   07.         'Costruttore statico che inizializza l'oggetto StreamWriter
   08.         'Da notare è che un costruttore statico NON può avere
   09.         'parametri: il motivo è semplice. Se li potesse avere
   10.         'e ci fossero più costruttori normali il compilatore
   11.         'non saprebbe cosa fare, poichè Shared Sub New
   12.         'potrebbe avere parametri diversi dagli altri
   13.         Shared Sub New()
   14.              Console.WriteLine("Costruttore statico: sto creando il log...")
   15.              File = New IO.StreamWriter("Errors.log")
   16.         End Sub
   17.
   18.         'Questo è il costruttore normale
   19.         Sub New()
   20.              Console.WriteLine("Costruttore normale: sto creando un oggetto...")
   21.         End Sub
   22.
   23.         Public Sub WriteLine(ByVal Text As String)
   24.              File.WriteLine(Text)
   25.         End Sub
   26.     End Class
   27.
   28.     Sub Main()
   29.         'Qui viene eseguito il costruttore statico e quello normale
   30.         Dim E1 As New Errors
   31.         'Qui solo quello normale
   32.         Dim E2 As New Errors
   33.
   34.         E1.WriteLine("Nessun errore")
   35.
   36.         Console.ReadKey()
   37.     End Sub
   38. End Module

L'ouput è:

    1. Costruttore statico: sto creando il log...
    2. Costruttore normale: sto creando un oggetto...
    3. Costruttore normale: sto creando un oggetto...
Questo esempio evidenzia bene come vengano eseguiti i costr uttor i: mentr e si cr ea il pr imo oggetto Er r or s in
assoluto viene eseguito quello statico e in più anche quello nor male, per i successivi, invece, solo quello nor male.
Ovviamente non tr over e il file Er r or s.log con la scr itta "Nessun er r or e" poichè nell'esempio il file non è stato chiuso.
Ripr ender emo lo stesso discor so con i distr uttor i.




Costruttori Friend e Private
I costr uttor i possono esser e specificati come Fr iend e Pr ivate pr opr io come ogni altr o membr o di classe. Tuttavia l'uso
degli specificator i di accesso sui costr uttor i ha par ticolar i effetti collater ali. Dichiar ar e un costr uttor e Pr ivate, ad
esempio, equivale e impor r e che niente possa inizializzar e l'oggetto al di fuor i della classe stessa: questo caso
par ticolar e è stato analizzato nella lezione pr ecedente con i metodi factor y statici e ser ve a r ender e obbligator io l'uso
di questi ultimi. Un costr uttor e Fr iend invece r ende la classe inizializzabile da ogni par te del pr ogetto cor r ente: se un
client ester no utilizzasse la classe impor tandola da una libr er ia (vedi oltr e) non potr ebbe usar ne il costr uttor e.
A27. Gli Operatori

Gli oper ator i sono speciali metodi che per mettono di eseguir e, appunto, oper azioni tr a due valor i mediante l'uso di un
simbolo (ad esempio, + per la somma, - per la differ enza, ecceter a...). Quando facciamo i calcoli, comunemente usando i
tipi base numer ici del Fr amew or k, come Int16 o Double, usiamo pr aticamente sempr e degli oper ator i. Essi non sono
nulla di "str aor dinar io", nel senso che anche se non sembr a, data la lor o par ticolar e sintassi, sono pur sempr e definiti
all'inter no delle var ie classi come nor mali membr i (statici). Gli oper ator i, come i tipi base, del r esto, non si
sottr aggono alla globale astr azione degli linguaggi or ientati agli oggetti: tutto è sempr e incasellato al posto giusto in
una qualche classe. Ma questo lo vedr emo più avanti quando par ler ò della Reflection.
Sor volando su questa br eve par entesi idilliaca, tor niamo all'aspetto più concr eto di questo capitolo. Anche il
pr ogr ammator e ha la possibilità di defin ire nuovi oper ator i per i tipi che ha cr eato: ad esempio, può scr iver e
oper ator i che oper ino tr a str uttur e e tr a classi. In gener e, si pr efer isce adottar e gli oper ator i nel caso delle str uttur e
poiché, essendo tipi value, si pr estano meglio - come idea, più che altr o - al fatto di subir e oper azioni tr amite simboli.
Venendo alla pr atica, la sintassi gener ale di un oper ator e è la seguente:

    1. Shared Operator [Simbolo]([Parametri]) As [Tipo Restituito]
    2.     '...
    3.     Return [Risultato]
    4. End Operator

Come si vede, la sintassi è molto simile a quella usata per dichiar ar e una funzione, ad eccezione della keyw or d e
dell'identificator e. Inoltr e, per far sì che l'oper ator e sia non solo sintatticamente, ma anche semanticamente valido,
devono esser e soddisfatte queste condizioni:

        L'oper ator e deve SEM PRE esser e dichiar ato come Shar ed, ossia statico. Infatti, l'oper ator e r ientr a nel dominio
        della classe in sé e per sé, appar tiene al tipo, e non ad un'istanza in par ticolar e. Infatti, l'oper ator e può esser e
        usato per eseguir e oper azioni tr a tutte le istanze possibili della classe. Anche se viene definito in una str uttur a,
        deve comunque esser e Shar ed. Infatti, sebbene il concetto di str uttur a si pr esti di meno a questa "visione" un po'
        assiomatica del concetto di istanza, è pur sempr e ver o che possono esister e tante var iabili diver se contenenti
        dati diver si, ma dello stesso tipo str uttur ato.
        L'oper ator e può specificar e al m assim o due par ametr i (si dice unar io se ne specifica uno, e binar io se due), e di
        questi almeno uno DEVE esser e dello stesso tipo in cui l'oper ator e è definito - tipicamente il pr imo dei due deve
        soddisfar e questa seconda condizione. Questo r isulta abbastanza ovvio: se avessimo una str uttur a Fr azione,
        come fr a poco mostr er ò, a cosa ser vir ebbe dichiar ar vi all'inter no un oper ator e + definito tr a due numer i
        inter i? A par te il fatto che esiste già, è logico aspettar si che, dentr o un nuovo tipo, si descr ivano le istr uzioni
        necessar ie ad oper ar e con quel nuovo tipo, o al massimo ad attuar e calcoli tr a questo e i tipi già esistenti.
        Il simbolo che contr addistingue l'oper ator e dev e esser e scelto tr a quelli disponibili, di cui qui r ipor to un elenco
        con annessa descr izione della funzione che usualmente l'oper ator e r icopr e:
                + (somma)
                - (differ enza)
                * (pr odotto)
                / (divisione)
                 (divisione inter a)
                ^ (potenza)
                & (concatenazione)
                = (uguaglianza)
                > (maggior e)
< (minor e)
             >= (maggior e o uguale)
             <= (minor e o uguale)
             >> (shift destr o dei bit)
             << (shift sinistr o dei bit)
             And (inter sezione logica)
             Or (unione logica)
             Not (negazione logica)
             Xor (aut logico)
             Mod (r esto della divisione inter a)
             Like (r icer ca di un patter n: di solito il pr imo ar gomento indica dove cer car e e il secondo cosa cer car e)
             IsTr ue (è ver o)
             IsFalse (è falso)
             CType (conver sione da un tipo ad un altr o)
      Sintatticamente par lando, nulla vieta di usar e il simbolo And per far e una somma, ma sar ebbe meglio attener si
      alle nor mali nor me di utilizzo r ipor tate.

Ed ecco un esempio:

 001. Module Module1
 002.
 003.     Public Structure Fraction
 004.         'Numeratore e denominatore
 005.         Private _Numerator, _Denumerator As Int32
 006.
 007.         Public Property Numerator() As Int32
 008.              Get
 009.                  Return _Numerator
 010.              End Get
 011.              Set(ByVal value As Int32)
 012.                  _Numerator = value
 013.              End Set
 014.         End Property
 015.
 016.         Public Property Denumerator() As Int32
 017.              Get
 018.                  Return _Denumerator
 019.              End Get
 020.              Set(ByVal value As Int32)
 021.                  If value <> 0 Then
 022.                       _Denumerator = value
 023.                  Else
 024.                       'Il denominatore non può mai essere 0
 025.                       'Dovremmo lanciare un'eccezione, ma vedremo più
 026.                       'avanti come si fa. Per ora lo impostiamo a uno
 027.                       _Denumerator = 1
 028.                  End If
 029.              End Set
 030.         End Property
 031.
 032.         'Costruttore con due parametri, che inizializza numeratore
 033.         'e denominatore
 034.         Sub New(ByVal N As Int32, ByVal D As Int32)
 035.              Me.Numerator = N
 036.              Me.Denumerator = D
 037.         End Sub
 038.
 039.         'Restituisce la Fraction sottoforma di stringa
 040.         Function Show() As String
 041.              Return Me.Numerator & " / " & Me.Denumerator
 042.         End Function
 043.
 044.         'Semplifica la Fraction
 045.         Sub Semplify()
 046.
Dim X As Int32
047.
048.       'Prende X come il valore meno alto in modulo
049.       'e lo inserisce in X. X servirà per un
050.       'calcolo spicciolo del massimo comune divisore
051.       X = Math.Min(Math.Abs(Me.Numerator), Math.Abs(Me.Denumerator))
052.
053.       'Prima di iniziare, per evitare errori, controlla
054.       'se numeratore e denominatore sono entrambi negativi:
055.       'in questo caso li divide per -1
056.       If (Me.Numerator < 0) And (Me.Denumerator < 0) Then
057.           Me.Numerator /= -1
058.           Me.Denumerator /= -1
059.       End If
060.
061.       'E con un ciclo scova il valore più alto di X
062.       'per cui sono divisibili sia numeratore che denominatore
063.       '(massimo comune divisore) e li divide per quel numero.
064.
065.       'Continua a decrementare X finché non trova un
066.       'valore per cui siano divisibili sia numeratore che
067.       'denominatore: dato che era partito dall'alto, questo
068.       'sarà indubbiamente il MCD
069.       Do Until ((Me.Numerator Mod X = 0) And (Me.Denumerator Mod X = 0))
070.            X -= 1
071.       Loop
072.
073.       'Divide numeratore e denominatore per l'MCD
074.       Me.Numerator /= X
075.       Me.Denumerator /= X
076.   End Sub
077.
078.   'Somma due frazioni e restituisce la somma
079.   Shared Operator +(ByVal F1 As Fraction, ByVal F2 As Fraction) _
080.       As Fraction
081.       Dim F3 As Fraction
082.
083.       'Se i denumeratori sono uguali, si limita a sommare
084.       'i numeratori
085.       If F1.Denumerator = F2.Denumerator Then
086.            F3.Denumerator = F1.Denumerator
087.            F3.Numerator = F1.Numerator + F2.Numerator
088.       Else
089.            'Altrimenti esegue tutta l'operazione
090.            'x   a   x*b + a*y
091.            '- + - = ---------
092.            'y   b      y*b
093.            F3.Denumerator = F1.Denumerator * F2.Denumerator
094.            F3.Numerator = F1.Numerator * F2.Denumerator + F2.Numerator * F1.Denumerator
095.       End If
096.
097.       'Semplifica la Fraction
098.       F3.Semplify()
099.       Return F3
100.   End Operator
101.
102.   'Sottrae due Fraction e restituisce la differenza
103.   Shared Operator -(ByVal F1 As Fraction, ByVal F2 As Fraction) _
104.       As Fraction
105.       'Somma l'opposto del secondo membro
106.       F2.Numerator = -F2.Numerator
107.       Return F1 + F2
108.   End Operator
109.
110.   'Moltiplica due frazioni e restituisce il prodotto
111.   Shared Operator *(ByVal F1 As Fraction, ByVal F2 As Fraction) _
112.       As Fraction
113.       'Inizializza F3 con il numeratore pari al prodotto
114.       'dei numeratori e il denominatore pari al prodotto dei
115.       'denominatori
116.       Dim F3 As Fraction = New Fraction(F1.Numerator * F2.Numerator, _
117.           F1.Denumerator * F2.Denumerator)
118.
F3.Semplify()
 119.                  Return F3
 120.              End Operator
 121.
 122.             'Divide due frazioni e restituisce il quoziente
 123.             Shared Operator /(ByVal F1 As Fraction, ByVal F2 As Fraction) _
 124.                 As Fraction
 125.                 'Inizializza F3 eseguendo l'operazione:
 126.                 'a    x   a   y
 127.                 '- / - = - * -
 128.                 'b    y   b   x
 129.                 Dim F3 As Fraction = New Fraction(F1.Numerator * F2.Denumerator, _
 130.                      F1.Denumerator * F2.Numerator)
 131.                 F3.Semplify()
 132.                 Return F3
 133.             End Operator
 134.         End Structure
 135.
 136.         Sub Main()
 137.             Dim A As New Fraction(8, 112)
 138.             Dim B As New Fraction(3, 15)
 139.
 140.              A.Semplify()
 141.              B.Semplify()
 142.              Console.WriteLine(A.Show())
 143.              Console.WriteLine(B.Show())
 144.
 145.              Dim C As Fraction = A + B
 146.              Console.WriteLine("A + B = " & C.Show())
 147.
 148.             Console.ReadKey()
 149.         End Sub
 150. End     Module




CTy pe
CType è un par ticolar e oper ator e che ser ve per conver tir e da un tipo di dato ad un altr o. Non è ancor a stato
intr odotto nei pr ecedenti capitoli, ma ne par ler ò più ampiamente in uno dei successivi. Scr ivo comunque un par agr afo
a questo r iguar do per amor di completezza e utilità di consultazione.
Come è noto, CType può eseguir e conver sioni da e ver so tipi conosciuti: la sua sintassi, tuttavia, potr ebbe sviar e dalla
cor r etta dichiar azione. Infatti, nonostante CType accetti due par ametr i, la sua dichiar azione ne implica uno solo, ossia
il tipo che si desider a conver tir e, in questo caso Fr action. Il secondo par ametr o è implicitamente indicato dal tipo di
r itor no: se scr ivessimo "CType(ByVal F As Fr action) As Double", questa istr uzione gener er ebbe un CType in gr ado di
conver tir e dal tipo Fr action al tipo Double nella manier a consueta in cui siamo abituati:

    1. Dim F As Fraction
    2. '...
    3. Dim D As Double = CType(F, Double)

La dichiar azione di una conver sione ver so Double gener a automaticamente anche l'oper ator e CDbl, che si può usar e
tr anquillamente al posto della ver sione completa di CType. Or a conviene por r e l'accento sul come CType viene
dichiar ato: la sua sintassi non è speciale solo per chè può esser e confuso da unar io a binar io, ma anche per chè deve
dichiar ar e sem pr e se una conver sione è W idening (di espansione, ossia senza per dita di dati) o Nar r o w ing (di
r iduzione, con possibile per dita di dati). Per questo motivo si deve specificar e una delle suddette keyw or d tr a Shar ed
e Oper ator . Ad esempio: Fr action r appr esenta un numer o r azionale e, sebbene Double non r appr esenti tutte le cifr e di
un possibile numer o per iodico, possiamo consider ar e che nel passaggio ver so i Double non ci sia per dita di dati nè di
pr ecisione in modo r ilevante. Possiamo quindi definir e la conver sione Widening:

    1. Shared Widening Operator CType(ByVal F As Fraction) As Double
    2.     Return F.Numerator / F.Denumerator
    3. End Operator
Invece, la conver sione ver so un numer o inter o implica non solo una per dita di pr ecisione r ilevante ma anche di dati,
quindi la definir emo Nar r ow ing:

    1. Shared Narrowing Operator CType(ByVal F As Fraction) As Int32
    2.     'Notare l'operatore  di divisione intera (per maggiori
    3.     'informazioni sulla divisione intera, vedere capitolo A6)
    4.     Return F.Numerator  F.Denumerator
    5. End Operator




Operatori di c onfronto
Gli oper ator i di confr onto godono anch'essi di una car atter istica par ticolar e: devono sempr e esser e definiti in coppia,
< con >, = con <>, <= con >=. Non può infatti esister e un modo per ver ificar e se una var iabile è minor e di un altr a e
non se è maggior e. Se manca uno degli oper ator i complementar i, il compilator e visualizzer à un messaggio di er r or e.
Ovviamente, il tipo r estituito dagli oper ator i di confr onto sar à sempr e Boolean, poiché una condizione può esser e solo
o ver a o falsa.

   01.   Shared Operator <(ByVal F1 As Fraction, ByVal F2 As Fraction) As Boolean
   02.       'Converte le frazioni in double e confronta questi valori
   03.       Return (CType(F1, Double) < CType(F2, Double))
   04.   End Operator
   05.
   06.   Shared Operator >(ByVal F1 As Fraction, ByVal F2 As Fraction) As Boolean
   07.       Return (CDbl(F1) > CDbl(F2))
   08.   End Operator
   09.
   10.   Shared Operator =(ByVal F1 As Fraction, ByVal F2 As Fraction) As Boolean
   11.       Return (CDbl(F1) = CDbl(F2))
   12.   End Operator
   13.
   14.   Shared Operator <>(ByVal F1 As Fraction, ByVal F2 As Fraction) As Boolean
   15.       'L'operatore "diverso" restituisce sempre un valore opposto
   16.       'all'operatore "uguale"
   17.       Return Not (F1 = F2)
   18.   End Operator

È da notar e che le espr essioni come (a=b) o (a-c>b) r estituiscano un valor e booleano. Possono anche esser e usate nelle
espr essioni, ma è sconsigliabile, in quanto il valor e di Tr ue è spesse volte confuso: in VB.NET è -1, ma a r untime è 1,
mentr e negli altr i linguaggi è sempr e 1. Queste espr essioni possono tuttavia esser e assegnate con sicur ezza ad altr i
valor i booleani:

    1.   '...
    2.   a = 10
    3.   b = 20
    4.   Console.WriteLine("a è maggiore di b: " & (a > b))
    5.   'A schermo compare: "a è maggiore di b: False"
A28. Differenze tra classi e strutture

Nel cor so dei pr ecedenti capitoli ho più volte detto che le classi ser vono per cr ear e nuovi tipi e aggiunger e a questi
nuove funzionalità, così da estender e le nor mali capacità del Fr amew or k, ma ho detto la stessa cosa delle str uttur e,
magar i enfatizzandone di meno l'impor tanza. Le classi possono espor r e campi, e le str uttur e anche; le classi possono
espor r e pr opr ietà, e le stuttur e anche; le classi possono espor r e metodi, e le str uttur e anche; le classi possono espor r e
costr uttor i, e le str uttur e anche; le classi e i membr i di classe possono aver e specificator i di accesso, e le str uttur e e i
lor o membr i anche. Insomma... a dir la tutta sembr er ebbe che classi e str uttur e siano concetti un po' r idondanti, cr eati
solo per aver e un tipo r efer ence e un tipo value, ma in definitiva molto simili.
Ovviamente non avr ei scr itto questo capitolo se le cose fosser o state r ealmente così. Le classi sono infinitamente più
potenti delle str uttur e e fr a pochissimo capir ete il per chè.




Memorizzazione
Iniziamo col chiar ir e un aspetto già noto. Le str uttur e sono tipi value, mentr e le classi sono tipi r efer ence. Ripetendo
concetti già spiegati pr ecedentemente, le pr ime vengono collocate dir ettamente sullo stack, ossia sulla memor ia
pr incipale, nello spazio r iser vato alle var iabili del pr ogr amma, mentr e le seconde vengono collocate in un'altr a par te
della memor ia (heap managed) e pongono sullo stack solo un puntator e alla lor o ver a locazione. Questo significa
pr incipalmente due cose:

       L'accesso a una str uttur a e ai suoi membr i è più r apido di un accesso ad una classe;
       La classe occupa più memor ia, a par ità di membr i (almeno 6 bytes in più).

Inoltr e, una str uttur a si pr esta meglio alla memor izzazione "linear e", ed è infatti gr andemente pr efer ita quando si
esegue il mar shalling dei dati (ossia la lor o tr asfor mazione da entità alla pur a r appr esentazione in memor ia, costituita
da una semplice ser ie di bits). In questo modo, per pr ima cosa è molto più facile legger e e scr iver e str uttur e in
memor ia se si devono attuar e oper azioni di basso livello, ed è anche possibile r ispar miar e spazio usando un'oppor tuna
disposizione delle var iabili. Le classi, al contr ar io, non sono così or dinate, ed è meno facile manipolar le. Non mi
addentr er ò oltr e in questo ambito, ma, per chi volesse, ci sono delle mie dispense che spiegano come funziona la
memor izzazione delle str uttur e.




Identità
Un'altr a conseguenza del fatto che le classi siano tipi r efer ence consiste in questo: due oggetti, a par ità di campi, sono
sem pr e diver si, poiché si tr atta di due istanze distinte, seppur contenti gli stessi dati. Due var iabili di tipo
str uttur ato che contengono gli stessi dati, invece, sono uguali, per chè non esiste il concetto di istanza per i tipi value. I
tipi value sono, per l'appunto, v alo r i, ossia semplici dati, infor mazione pur a, ammasso di bits, né più né meno. Per
questo motivo, ad esempio, è impossibile modificar e una pr opr ietà di una str uttur a tr amite l'oper ator e punto, poiché
sar ebbe come tentar e di modificar e la par te decimale di 1.23: 1.23 è sempr e 1.23, si tr atta di un valor e e non lo si può
modificar e, ma al massimo si può assegnar e un altr o valor e alla var iabile che lo contiene.
Al contr ar io, gli oggetti sono entità più complesse: non si tr atta di "infor mazione pur a" come i tipi str uttur ati. Un
oggetto contiene molteplici campi e pr opr ietà sempr e modificabili, per chè indicano solo un aspetto dell'oggetto: ad
esempio, il color e di una par ete è sempr e modificabile: basta tinteggiar e la par ete con un nuovo color e. Come dir e che
"la par ete" non è come un numer o, che è sempr e quello e basta: essa è un qualcosa di concr eto con diver se pr opr ietà.
Sono concetti molto astr atti e per cer ti ver si molto ar dui da capir e di pr imo acchito... io ho tentato di far e esempi
convinceti, ma sper o che con il tempo impar er ete da soli a inter ior izzar e queste differ enze - differ enze che, pur
essendo impor tanti, non sono le più impor tanti.




Paradigma di programmazione ad oggetti
Ed eccoci ar r ivati al punto caldo della discussione. La sostanziale differ enza che separ a nettamente str uttur e da classi
è l'ader enza ai dettami del par adigma di pr ogr ammazione ad oggetti, in par ticolar e ad er editar ietà e polimor fismo.
Le classi possono er editar e cer ti membr i da altr e classi e modificar ne il funzionamento. Le str uttur e non possono far e
questo. Inoltr e, le classi possono implementar e inter facce, ossia sistemar e i pr opr i membr i per ader ir e a scheletr i di
base: le str uttur e non per mettono di far e neppur e questo.
Queste tr e car ater istiche (ma le pr ime due in par ticolar e) sono potenti str umenti a disposizione del pr ogr ammator e,
e nei pr ossimi capitoli le analizzer emo nel dettaglio.
A29. L'Ereditarietà

Eccoci ar r ivati a par lar e degli aspetti peculiar i di un linguaggio ad oggetti! Iniziamo con l'Eder editar ietà.




L'ereditarietà è la possibilità di un linguaggio ad oggetti di far der ivar e una classe da un'altr a: in questo caso, la pr ima
assume il nome di classe der iv ata, mentr e la seconda quello di classe base. La classe der ivata acquisisce tutti i
membr i della classe base, ma può r idefinir li o aggiunger ne di nuovi. Questa car atter istica di ogni linguaggio Object
Or iented è par ticolar mente efficace nello schematizzar e una r elazione "is-a" (ossia "è un"). Per esempio, potr emmo
definir e una classe Vegetale, quindi una nuova classe Fior e, che er edita Vegetale. Fior e è un Vegetale, come mostr a la
str uttur a ger ar chica dell'er editar ietà. Se definissimo un'altr a classe Pr imula, der ivata da Fior e, dir emmo che Pr imula
è un Fior e, che a sua volta è un Vegetale. Quest'ultimo tipo di r elazione, che cr ea classi der ivate che sar anno basi per
er editar e altr e classi, si chiama er editar ietà indir e tta.
Passiamo or a a veder e come si dichiar a una classe der ivata:

    1. Class [Nome]
    2.   Inherits [Classe base]
    3.   'Membri della classe
    4. End Class

La keyw or d Inher its specifica quale classe base er editar e: si può aver e solo UNA dir ettiva Inher its per classe, ossia non
è possibile er editar e più classi base. In questo fr angente, si può scopr ir e come le pr opr ietà siano utili e flessibili: se
una classe base definisce una var iabile pubblica, questa diver r à par te anche della classe der ivata e su tale var iabile
ver r anno basate tutte le oper azioni che la coinvolgono. Siccome è possibile che la classe der ivata voglia r idefinir e tali
oper azioni e molto pr obabilmente anche l'utilizzo della var iabile, è sempr e consigliabile dichiar ar e campi Pr ivate
avvolti da una pr opr ietà, poichè non c'è mai alcun per icolo nel modificar e una pr opr ietà in classi der ivate, ma non è
possibile modificar e i campi nella stessa classe. Un semplice esempio di er editar ietà:

   01.   Class Person
   02.       'Per velocizzare la scrittura del codice, assumiamo che
   03.       'questi campi pubblici siano proprietà
   04.       Public FirstName, LastName As String
   05.
   06.       Public ReadOnly Property CompleteName() As String
   07.           Get
   08.               Return FirstName & " " & LastName
   09.           End Get
   10.       End Property
   11.   End Class
   12.
   13.   'Lo studente, ovviamente, è una persona
   14.   Class Student
   15.       'Student eredita da Person
   16.       Inherits Person
   17.
   18.       'In più, definisce anche questi campi pubblici
   19.       'La scuola frequentata
   20.       Public School As String
   21.       'E l'anno di corso
   22.       Public Grade As Byte
   23.   End Class

In seguito, si può utilizzar e la classe der ivata come si è sempr e fatto con ogni altr a classe. Nel far ne uso, tuttavia, è
necessar io consider ar e che una classe der ivata possiede non solo i membr i che il pr ogr ammator e ha esplicitamente
definito nel suo cor po, ma anche tutti quei membr i pr esenti nella classe base che si sono implicitamente acquisiti
nell'atto stesso di scr iver e "Inher its". Se vogliamo, possiamo assimilar e una classe ad un insieme, i cui elementi sono i
suoi membr i: una classe base è sottoinsieme della cor r ispondente classe der ivata. Di solito, l'ambiente di sviluppo aiuta
molto in questo, poiché, nei sugger imenti pr oposti dur ante la scr ittur a del codice, vengono automaticamente inser ite
anche le voci er editate da altr e classi. Ciò che abbiamo appena visto vale anche per er editar ietà indir etta: se A
er edita da B e B er edita da C, A dispor r à dei membr i di B, alcuni dei quali sono anche membr i di C (semplice pr opr ietà
tr ansitiva).
Or a, per ò, bisogna por r e un bel Nota Bene alla questione. Infatti, non tutto è semplice come sembr a. For se nessuno si è
chiesto che fine fanno gli specificator i di accesso quando un membr o viene er editato da una classe der ivata. Ebbene,
esistono delle pr ecise r egole che indicano come gli scope vengono tr attati quando si er edita:

       Un membr o Public o Fr iend della classe base diventa un membr o Public o Fr iend della classe der ivata (in
       pr atica, non cambia nulla; viene er editato esattamente com'è);
       Un membr o Pr iv ate della classe base non è accessibile dalla classe der ivata, poichè il suo ambito di visibilità
       impedisce a ogni chiamante ester no alla classe base di far vi r ifer imento, come già visto nelle lezioni pr ecedenti;
       Un membr o Pr o tected della classe base diventa un membr o Pr otected della classe der ivata, ma si compor ta
       come un membr o Pr ivate.

Ed ecco che abbiamo intr odotto uno degli specificator i che ci er avamo lasciati indietr o. I membr i Pr otected sono
par ticolar mente utili e costituiscono una sor ta di "scappatoia" al fatto che quelli pr ivati non subiscono l'er editar ietà.
Infatti, un memebr o Pr otected si compor ta esattamente come uno Pr ivate, con un'unica eccezione: è er editabile, ed in
questo caso diventa un membr o Pr otected della classe der ivata. Lo stesso discor so vale anche per Pr otected Fr iend.
Ecco uno schema che esemplifica il compor tamento dei pr incipali Scope:




Esempio:

  001. Module Esempio
  002.     Class Person
  003.         'Due campi protected
  004.         Protected _FirstName, _LastName As String
  005.         'Un campo private readonly: non c'è ragione di rendere
  006.         'questo campo Protected poichè la data di nascita non
  007.         'cambia ed è sempre accessibile tramite la proprietà
  008.         'pubblica BirthDay
  009.         Private ReadOnly _BirthDay As Date
  010.
  011.         Public Property FirstName() As String
  012.              Get
  013.                  Return _FirstName
  014.              End Get
  015.              Set(ByVal Value As String)
  016.                  If Value <> "" Then
  017.                      _FirstName = Value
  018.                  End If
  019.              End Set
  020.         End Property
  021.
  022.         Public Property LastName() As String
  023.              Get
  024.                  Return _LastName
  025.              End Get
  026.              Set(ByVal Value As String)
  027.                  If Value <> "" Then
  028.
_LastName = Value
029.               End If
030.           End Set
031.       End Property
032.
033.       Public ReadOnly Property BirthDay() As Date
034.           Get
035.               Return _BirthDay
036.           End Get
037.       End Property
038.
039.       Public ReadOnly Property CompleteName() As String
040.           Get
041.               Return _FirstName & " " & _LastName
042.           End Get
043.       End Property
044.
045.       'Costruttore che accetta tra parametri obbligatori
046.       Sub New(ByVal FirstName As String, ByVal LastName As String, _
047.           ByVal BirthDay As Date)
048.           Me.FirstName = FirstName
049.           Me.LastName = LastName
050.           Me._BirthDay = BirthDay
051.       End Sub
052.   End Class
053.
054.   'Lo studente, ovviamente, è una persona
055.   Class Student
056.       'Student eredita da Person
057.       Inherits Person
058.
059.       'La scuola frequentata
060.       Private _School As String
061.       'E l'anno di corso
062.       Private _Grade As Byte
063.
064.       Public Property School() As String
065.           Get
066.               Return _School
067.           End Get
068.           Set(ByVal Value As String)
069.               If Value <> "" Then
070.                    _School = Value
071.               End If
072.           End Set
073.       End Property
074.
075.       Public Property Grade() As Byte
076.           Get
077.               Return _Grade
078.           End Get
079.           Set(ByVal Value As Byte)
080.               If Value > 0 Then
081.                    _Grade = Value
082.               End If
083.           End Set
084.       End Property
085.
086.       'Questa nuova proprietà si serve anche dei campi FirstName
087.       'e LastName nel modo corretto, poichè sono Protected anche
088.       'nella classe derivata e fornisce un profilo completo
089.       'dello studente
090.       Public ReadOnly Property Profile() As String
091.           Get
092.               'Da notare l'accesso a BirthDay tramite la proprietà
093.               'Public: non è possibile accedere al campo _BirthDay
094.               'perchè è privato nella classe base
095.               Return _FirstName & " " & _LastName & ", nato il " & _
096.               BirthDay.ToShortDateString & " frequenta l'anno " & _
097.               _Grade & " alla scuola " & _School
098.           End Get
099.       End Property
100.
101.             'Altra clausola importante: il costruttore della classe
 102.             'derivata deve sempre richiamare il costruttore della
 103.             'classe base
 104.             Sub New(ByVal FirstName As String, ByVal LastName As String, _
 105.                 ByVal BirthDay As Date, ByVal School As String, _
 106.                 ByVal Grade As Byte)
 107.                 MyBase.New(FirstName, LastName, BirthDay)
 108.                 Me.School = School
 109.                 Me.Grade = Grade
 110.             End Sub
 111.         End Class
 112.
 113.         Sub Main()
 114.             Dim P As New Person("Pinco", "Pallino", Date.Parse("06/07/90"))
 115.             Dim S As New Student("Tizio", "Caio", Date.Parse("23/05/92"), _
 116.             "Liceo Classico Ugo Foscolo", 2)
 117.
 118.              Console.WriteLine(P.CompleteName)
 119.              'Come si vede, la classe derivata gode degli stessi membri
 120.              'di quella base, acquisiti secondo le regole
 121.              'dell'ereditarietà appena spiegate
 122.              Console.WriteLine(S.CompleteName)
 123.              'E in più ha anche i suoi nuovi membri
 124.              Console.WriteLine(S.Profile)
 125.
 126.              'Altra cosa interessante: dato che Student è derivata da
 127.              'Person ed espone tutti i membri di Person, più altri,
 128.              'non è sbagliato assegnare un oggetto Student a una
 129.              'variabile Person
 130.              P = S
 131.              Console.WriteLine(P.CompleteName)
 132.
 133.             Console.ReadKey()
 134.         End Sub
 135. End     Module

L'output:

    1.   Pinco Pallino
    2.   Tizio Caio
    3.   Tizio Caio, nato il 23/5/1992 frequenta l'anno 2 alla scuola Liceo Classico Ugo
    4.   Foscolo
    5.   Tizio Caio

(Per maggior i infor mazioni sulle oper azioni con le date, veder e il capitolo B13)
Anche se il sor gente è ampiamente commentato mi soffer mer ei su alcuni punti caldi. Il costr uttor e della classe der ivata
deve sem pr e r ichiamar e il costr uttor e della classe base, e questo avviene tr amite la keyw or d MyBase che, usata in
una classe der ivata, fa r ifer imento alla classe base cor r ente: attr aver so questa par ola r iser vata è possibile anche
r aggiunger e i membr i pr ivati della classe base, ma si fa r ar amente, poichè il suo impiego più fr equente è quello di
r ipr ender e le vecchie ver sioni di metodi modificati. Il secondo punto r iguar da la conver sione di classi: passar e da
Student a Per son non è, come potr ebbe sembr ar e, una conver sione di r iduzione, poichè dur ante il pr ocesso, nulla va
per duto nel ver o senso della par ola. Cer to, si per dono le infor mazioni supplementar i, ma alla classe base queste non
ser vono: la sicur ezza di eseguir e la conver sione r isiede nel fatto che la classe der ivata gode degli stessi membr i di
quella base e quindi non si cor r e il r ischio che ci sia r ifer imento a un membr o inesistente. Questo invece si ver ifica nel
caso opposto: se una var iabile di tipo Student assumesse il valor e di un oggetto Per son, School e Gr ade sar ebber o pr ivi
di valor e e ciò gener ebbe un er r or e. Per eseguir e questo tipo di passaggi è necessar io l'oper ator e Dir ectCast.
A30. Polimorfismo

Il polimor fismo è la capacità di un linguaggio ad oggetti di r idefinir e i membr i della classe base in modo tale che si
compor tino in manier a differ ente all'inter no delle classi der ivate. Questa possibilità è quindi str ettamente legata
all'er editar ietà. Le keyw or ds che per mettono di attuar ne il funzionamento sono due: Over r idable e Over r ides. La
pr ima deve mar car e il membr o della classe base che si dovr à r idefinir e, mentr e la seconda contr assegna il membr o
della classe der ivata che ne costituisce la nuova ver sione. È da notar e che solo membr i della stessa categor ia con no m e
ug uale e sig natur e identica (ossia con lo stesso numer o e lo stesso tipo di par ametr i) possono subir e questo
pr ocesso: ad esempio non si può r idefinir e la pr ocedur a Show Tex t() con la pr opr ietà Tex t, per chè hanno nome
differ ente e sono di diver sa categor ia (una è una pr ocedur a e l'altr a una pr opr ietà). La sintassi è semplice:

    1.   Class [Classe base]
    2.       Overridable [Membro]
    3.   End Class
    4.
    5.   Class [Classe derivata]
    6.       Inherits [Classe base]
    7.       Overrides [Membro]
    8.   End Class

Questo esempio pr ende come base la classe Per son definita nel capitolo pr ecedente e sviluppa da questa la classe
Teacher (insegnante), modificandone le pr opr ietà LastName e CompleteName:

 001. Module Module1
 002.     Class Person
 003.         Protected _FirstName, _LastName As String
 004.         Private ReadOnly _BirthDay As Date
 005.
 006.         Public Property FirstName() As String
 007.              Get
 008.                  Return _FirstName
 009.              End Get
 010.              Set(ByVal Value As String)
 011.                  If Value <> "" Then
 012.                      _FirstName = Value
 013.                  End If
 014.              End Set
 015.         End Property
 016.
 017.         'Questa proprietà sarà ridefinita nella classe Teacher
 018.         Public Overridable Property LastName() As String
 019.              Get
 020.                  Return _LastName
 021.              End Get
 022.              Set(ByVal Value As String)
 023.                  If Value <> "" Then
 024.                      _LastName = Value
 025.                  End If
 026.              End Set
 027.         End Property
 028.
 029.         Public ReadOnly Property BirthDay() As Date
 030.              Get
 031.                  Return _BirthDay
 032.              End Get
 033.         End Property
 034.
 035.         'Questa proprietà sarà ridefinita nella classe Teacher
 036.         Public Overridable ReadOnly Property CompleteName() As String
 037.              Get
 038.                  Return _FirstName & " " & _LastName
 039.              End Get
 040.
End Property
041.
042.       'Costruttore che accetta tra parametri obbligatori
043.       Sub New(ByVal FirstName As String, ByVal LastName As String, _
044.           ByVal BirthDay As Date)
045.           Me.FirstName = FirstName
046.           Me.LastName = LastName
047.           Me._BirthDay = BirthDay
048.       End Sub
049.   End Class
050.
051.   Class Teacher
052.       Inherits Person
053.       Private _Subject As String
054.
055.       Public Property Subject() As String
056.           Get
057.               Return _Subject
058.           End Get
059.           Set(ByVal Value As String)
060.               If Value <> "" Then
061.                    _Subject = Value
062.               End If
063.           End Set
064.       End Property
065.
066.       'Ridefinisce la proprietà LastName in modo da aggiungere
067.       'anche il titolo di Professore al cognome
068.       Public Overrides Property LastName() As String
069.           Get
070.               Return "Prof. " & _LastName
071.           End Get
072.           Set(ByVal Value As String)
073.               'Da notare l'uso di MyBase e LastName: in questo
074.               'modo si richiama la vecchia versione della
075.               'proprietà LastName e se ne imposta il
076.               'valore. Viene quindi richiamato il blocco Set
077.               'vecchio: si risparmiano due righe di codice
078.               'poichè non si deve eseguire il controllo
079.               'If su Value
080.               MyBase.LastName = Value
081.           End Set
082.       End Property
083.
084.       'Ridefinisce la proprietà CompleteName in modo da
085.       'aggiungere anche la materia insegnata e il titolo di
086.       'Professore
087.       Public Overrides ReadOnly Property CompleteName() As String
088.           Get
089.               'Anche qui viene richiamata la vecchia versione di
090.               'CompleteName, che restituisce semplicemente il
091.               'nome completo
092.               Return "Prof. " & MyBase.CompleteName & _
093.               ", dottore in " & Subject
094.           End Get
095.       End Property
096.
097.       Sub New(ByVal FirstName As String, ByVal LastName As String, _
098.           ByVal BirthDay As Date, ByVal Subject As String)
099.           MyBase.New(FirstName, LastName, BirthDay)
100.           Me.Subject = Subject
101.       End Sub
102.   End Class
103.
104.   Sub Main()
105.       Dim T As New Teacher("Mario", "Rossi", Date.Parse("01/01/1950"), _
106.       "Letteratura italiana")
107.
108.       'Usiamo le nuove proprietà, ridefinite nella classe
109.       'derivata
110.       Console.WriteLine(T.LastName)
111.       '> "Prof. Rossi"
112.
Console.WriteLine(T.CompleteName)
  113.         '> "Prof. Mario Rossi, dottore in Letteratura italiana"
  114.
  115.         Console.ReadKey()
  116.     End Sub
  117. End Module

In questo modo si è visto come r idefinir e le pr opr ietà. Ma pr ima di pr oseguir e vor r ei far notar e un compor tamento
par ticolar e:

    1. Dim P As Person = T
    2. Console.WriteLine(P.LastName)
    3. Console.WriteLine(P.CompleteName)

In questo caso ci si aspetter ebbe che le pr opr ietà r ichiamate da P agiscano come specificato nella classe base (ossia
senza includer e altr e infor mazioni se non il nome ed il cognome), poiché P è di quel tipo. Questo, invece, non accade.
Infatti, P e T, dato che abbiamo usato l'oper ator e =, puntano or a allo stesso oggetto in memor ia, solo che P lo vede
come di tipo Per son e T come di tipo Teacher . Tuttavia, l'oggetto r eale è di tipo Teacher e per ciò i suoi metodi sono a
tutti gli effetti quelli r idefiniti nella classe der ivata. Quando P tenta di r ichiamar e le pr opr ietà in questione, ar r iva
all'indir izzo di memor ia dove sono conser vate le istr uzioni da eseguir e, solo che queste si tr ovano all'inter no di un
oggetto Teacher e il lor o codice è, di conseguenza, diver so da quello della classe base. Questo compor tamento, al
contr ar io di quanto potr ebbe sembr ar e, è utilissimo: ci per mette, ad esempio, di memor izzar e in un ar r ay di per sone
sia studenti che insegnanti, e ci per mette di scr iver e a scher mo i lor o nomi differ entemente senza eseguir e una
conver sione. Ecco un esempio:

   01.    Dim Ps(2) As Person
   02.
   03.    Ps(0) = New Person("Luigi", "Ciferri", Date.Parse("7/7/1982"))
   04.    Ps(1) = New Student("Mario", "Bianchi", Date.Parse("19/10/1991"), _
   05.        "Liceo Scientifico Tecnologico Cardano", 5)
   06.    Ps(2) = New Teacher("Ubaldo", "Nicola", Date.Parse("11/2/1980"), "Filosofia")
   07.
   08.    For Each P As Person In Ps
   09.         Console.WriteLine(P.CompleteName)
   10.    Next

È lecito assegnar e oggetti Student e Teacher a una cella di un ar r ay di Per son in quanto classi der ivate da Per son. I
metodi r idefiniti, tuttavia, r imangono e modificano il compor tamento di ogni oggetto anche se r ichiamato da una
"mascher a" di classe base. Pr oviamo or a con un piccolo esempio sul polimor fismo dei metodi:

   01.    Class A
   02.        Public Overridable Sub ShowText()
   03.            Console.WriteLine("A: Testo di prova")
   04.        End Sub
   05.    End Class
   06.
   07.    Class B
   08.        Inherits A
   09.
   10.        'Come si vede il metodo ha:
   11.        '- lo stesso nome: ShowText
   12.        '- lo stesso tipo: è una procedura
   13.        '- gli stessi parametri: senza parametri
   14.        'Qualunque tentativo di cambiare una di queste caratteristiche
   15.        'produrrà un errore del compilatore, che comunica di non poter
   16.        'ridefinire il metodo perchè non ne esistono di uguali nella
   17.        'classe base
   18.        Public Overrides Sub ShowText()
   19.            Console.WriteLine("B: Testo di prova")
   20.        End Sub
   21.    End Class

Ultime due pr ecisazioni: le var iabili non possono subir e polimor fismo, così come i membr i statici.
Shadow ing
Se il polimor fismo per mette di r idefinir e accur atamente membr i che pr esentano le stesse car atter istiche, ed è quindi
più pr eciso, lo shadow ing per mette letter almente di oscur ar e qualsiasi membr o che abbia lo stesso nome,
indipendentemente dalla categor ia, dalla signatur e e dalla qauntità di ver sioni alter native pr esenti. La keyw or d da
usar e è Shadow s, e si applica solo sul membr o della classe der ivata che intendiamo r idefinir e, oscur ando l'omonimo
nella classe base. Ad esempio:

   01. Module Esempio
   02.     Class Base
   03.         Friend Control As Byte
   04.     End Class
   05.
   06.     Class Deriv
   07.         Inherits Base
   08.         Public Shadows Sub Control(ByVal Msg As String)
   09.              Console.WriteLine("Control, seconda versione: " & Msg)
   10.         End Sub
   11.     End Class
   12.
   13.     Sub Main()
   14.         Dim B As New Base
   15.         Dim D As New Deriv
   16.
   17.         'Entrambe le classe hanno lo stesso membro di nome
   18.         '"Control", ma nella prima è un campo friend,
   19.         'mentre nella seconda è una procedura pubblica
   20.         Console.WriteLine(B.Control)
   21.         D.Control("Ciao")
   22.
   23.         Console.ReadKey()
   24.     End Sub
   25. End Module

Come si vede, la sintassi è come quella di Over r ides: Shadow s viene specificato tr a lo specificator e di accesso (se c'e') e
la tipologia del membr o (in questo caso Sub, pr ocedur a). Entr ambe le classi pr esentano Contr ol, ma la seconda ne fa un
uso totalmente diver so. Ad ogni modo l'uso dello shadow ing in casi come questo è for tememente sconsigliabile: più che
altr o lo si usa per assicur ar si che, se mai dovesse uscir e una nuova ver sione della classe base con dei nuovi metodi che
pr esentano lo stesso nome di quelli della classe der ivata da noi definita, non ci siano pr oblemi di compatibilità.
Se una var iabile è dichiar ata Shadow s, viene omessa la keyw or d Dim.
A31. Conversioni di dati

Il Fr amew or k .NET è in gr ado di eseguir e conver sioni automatiche a r untime ver so tipi di ampiezza maggior e, per
esempio è in gr ado di conver tir e Int16 in Int32, Char in Str ing, Single in Double e via dicendo. Queste oper azioni di
conver sione vengono dette w idening (dall'inglese w ide = lar go), ossia che avvengono senza la per dita di dati, poiché
tr aspor tano un valor e che contiene una data infor mazione in un tipo che può contener e più infor mazioni. Gli oper ator i
di conver sione ser vono per eseguir e conver sioni che vanno nella dir ezione opposta, e che sono quindi, nar r o w ing
(dall'inglese nar r ow = str etto). Queste ultime possono compor tar e la per dita di dati e per ciò gener ano un er r or e se
implicite.




CTy pe
CType è l'oper ator e di conver sione univer sale e per mette la conver sione di qualsiasi tipo in qualsiasi altr o tipo, almeno
quando questa è possibile. La sintassi è molto semplice:

 [Variabile] = CType([Valore da convertire], [Tipo in cui convertire])


Ad esempio:

    1. Dim I As Int32 = 50
    2. 'Converte I in un valore Byte
    3. Dim B As Byte = CType(I, Byte)

Questa lista r ipor ta alcuni casi in cui è bene usar e esplicitamente l'oper ator e di conver sione CType:

       Per conver tir e un valor e inter o o decimale in un valor e booleano;
       Per conver tir e un valor e Single o Double in Decimal;
       Per conver tir e un valor e inter o con segno in uno senza segno;
       Per conver tir e un valor e inter o senza segno in uno con segno della stessa ampiezza (ad esempio da UInt32 a
       Int32).

Oltr e a CType, esistono moltissime ver sioni più cor te di quest'ultimo che conver tono in un solo tipo: CInt conver te
sempr e in Int32, CBool sempr e in booleano, CByte in byte, CShor t Int16, CLong, CUShor t, CULong, CUInt, CSng, CDbl,
CDec, CStr , CDate, CObj. È inoppor tuno utilizzar e CStr poichè ci si può sevir e della funzione ToStr ing er editata da ogni
classe da System.Object; allo stesso modo, è meglio evitar e CDate, a favor e di Date.Par se, come si vedr à nella lezione
"DateTimePicker : Lavor ar e con le date".
CType può comunque esser e usato per qualsiasi altr a conver sione contemplabile, anche e sopr attutto con i tipi
Refer ence.




Direc tCast
Dir ectCast lavor a in un modo legger mente di diver so: CType tenta sempr e di conver tir e l'ar gomento di or gine nel tipo
specificato, mentr e Dir ectCast lo fa solo se tale valor e può esser e sottoposto al casting (al "passaggio" da un tipo
all'altr o, piuttosto che alla conver sione) ver so il tipo indicato. Per ciò non è, ad esempio, in gr ado di conver tir e una
str inga in inter o, e neanche un valor e shor t in un integer , sebbene questa sia una conver sione di espansione. Questi
ultimi esempi non sono validi anche per chè questo par ticolar e oper ator e può accettar e come ar gomenti solo oggetti, e
quindi tipi Refer ence. In gener ale, quindi, dato il legger o r ispar mio di tempo di Dir ectCast in confr onto a CType, è
conveniente usar e Dir ectCast:
Per eseguir e l'unbox ing di tipi value;
         Per eseguir e il casting di una classe base in una classe der ivata (vedi "Er editar ieta'");
         Per eseguir e il casting di un oggetto in qualsiasi altr o tipo r efer ence;
         Per eseguir e il casting di un oggetto in un'inter faccia.

N.B.: notar e che tutti i casi sopr a menzionati hanno come tipo di par tenza un oggetto, pr opr io come detto
pr ecedentemente.




Try Cast
Tr yCast ha la stessa sintassi di Dir ectCast, e quindi anche di CType, ma nasconde un piccolo pr egio. Spesso, quando si
esegue una conver sione si deve pr ima contr ollar e che la var iabile in questione sia di un deter minato tipo base o
implementi una deter minata inter faccia e solo successivamente si esegue la conver sione ver a e pr opr ia. Con ciò si
contr olla due volte la stessa var iabile, pr ima con l'If e poi con Dir ectCast. Tr yCast, invece, per mette di eseguir e il tutto
in un unico passaggio e r estituisce semplicemente Nothing se il cast fallisce. Questo appr occio r ende tale oper ator e
cir ca 0,2 volte più veloce di Dir ectCast.




Convert
Esiste, poi, una classe statica definita del namespace System - il namespace più impor tante di tutto il Fr amew or k.
Questa classe, essendo statica (e qui facciamo un po' di r ipasso), espone solo metodi statici e non può esser e istanziata
(non espone costr uttor i e comunque sar ebbe inutile far lo). Essa contiene molte funzioni per eseguir e la conver sione
ver so i tipi di base ed espone anche un impor tante valor e che vedr emo molto più in là par lando dei database.
Essenzialmente, tutti i suoi metodi hanno un nome del tipo "ToXXXX", dove XXXX è uno qualsiasi tr a i tipi base: ad
esempio, c'è, ToInt32, ToDouble, ToByte, ToStr ing, ecceter a... Un esempio:

   01.    Dim   I   As Int32    = 34
   02.    Dim   D   As Double   = Convert.ToDouble(I)
   03.    ' D   =   34.0
   04.    Dim   S   As String   = Convert.ToString(D)
   05.    ' S   =   "34"
   06.    Dim   N   As Single   = Convert.ToSingle(S)
   07.    ' N   =   34.0
   08.    Dim   K   As String   = "31/12/2008"
   09.    Dim   A   As Date =   Convert.ToDate(K)

All'inter no di Conver t sono definiti anche alcuni metodi per conver tir e una str inga da e ver so il for mato Base64, una
par ticolar e codifica che utilizza solo 64 car atter i, al contr ar io dell'ASCII standar d che ne utilizza 128 o di quello esteso
che ne utilizza 256. Tale codifica viene usata ad esempio nell'invio delle e-mail e pr oduce output un ter zo più voluminosi
degli input, ma in compenso tutti i car atter i contemplati sono sempr e leggibili (non ci sono, quindi, car atter i
"speciali"). Per appr ofondir e l'ar gomento, cliccate su w ik ipedia.
Per r ipr ender e il discor so conver sioni, sar ebbe lecito pensar e che la definizione di una classe del gener e, quando
esistono già altr i oper ator i come CType e Dir ectCast - altr ettanto qualificati e per for manti - sia abbastanza
r idondante. Più o meno è così. Utilizzar e la classe Conver t al posto degli altr i oper ator i di casting non gar antisce alcun
vantaggio di sor ta, e può anche esser e r icondotta ad una questione di gusti (io per sonalmente pr efer isco CType). Ad
ogni modo, c'è da dir e un'altr a cosa al r iguar do: i metodi di Conver t sono piuttosto r igor osi e for niscono dei ser vizi
molto mir ati. Per questo motivo, in casi molto vantaggiosi, ossia quando il cast può esser e ottimizzato, essi eseguono
pur sempr e le stesse istr uzioni: al contr ar io, CType può "ingegnar si" e for nir e una conver sione più efficiente.
Quest'ultimo, quindi, è legger mente più elastico ed adattabile alle situazioni.
Parse
Un'oper azione di par sing legge una str inga, la elabor a, e la conver te in un valor e di altr o tipo. Abbiamo già visto un
utilizzo di Par se nell'uso delle date, poiché il tipo Date espone il metodo Par se, che ci per mette di conver tir e la
r appr esentazione testuale di una data in un valor e date appr opr iato. Quasi tutti i tipi base del Fr amew or k espongono
un metodo Par se, che per mette di passar e da una str inga a quel tipo: possiamo dir e che Par se è l'inver sa di ToStr ing.
Ad esempio:

   01.   Dim I As Int32
   02.
   03.   I = Int32.Parse("27")
   04.   ' I = 27
   05.
   06.   I = Int32.Parse("78.000")
   07.   ' Errore di conversione!
   08.
   09.   I = Int32.Parse("123,67")
   10.   ' Errore di conversione!

Come vedete, Par se ha pur sempr e dei limiti: ad esempio non contempla i punti e le vir gole, sebbene la conver sione,
vista da noi "umani", sia del tutto lecita (78.000 è settantottomila con il separ ator e delle migliaia e 123,67 è un numer o
decimale, quindi conver tibile in inter o con un ar r otondamento). Inoltr e, Par se viene anche automaticamente chiamato
dai metodi di Conver t quando il valor e passato è una str inga. Ad esempio, Conver t.ToInt32("27") r ichiama a sua volta
Int32.Par se("27"). Per far vi veder e in che modo CType è più flessibile, r ipetiamo l'esper imento di pr ima usando appunto
CType:

   01.   Dim I As Int32
   02.
   03.   I = CType("27", Int32)
   04.   ' I = 27
   05.
   06.   I = CType("78.000", Int32)
   07.   ' I = 78000
   08.
   09.   I = CType("123,67", Int32)
   10.   ' I = 124

Per fetto: niente er r or i di conver sione e tutto come ci si aspettava!




Try Parse
Una var iante di Par se è Tr yPar se, anch'essa definita da molti tipi base. La sostanziale differ enza r isiede nel fatto che,
mentr e la pr ima può gener ar e er r or i nel caso la str inga non possa esser e conver tita, la seconda non lo fa, ma non
r estituisce neppur e il r isultato. Infatti, Tr yPar se accetta due ar gomenti, come nella seguente signatur e:

    1. TryParse(ByVal s As String, ByRef result As [Tipo]) As Boolean

Dove [Tipo] dipende da quale tipo base la stiamo r ichiamando: Int32.Tr yPar se avr à il secondo ar gomento di tipo Int32,
Date.Tr yPar se ce l'avr à di tipo Date, e così via. In sostanza Tr yPar se tenta di eseguir e la funzione Par se sulla str inga s:
se ci r iesce, r estituisce Tr ue e pone il r isultato in r esult (notar e che il par ametr o è passato per indir izzo); se non ci
r iesce, r estituisce False. Ecco un esempio:

   01.   Dim S As String = "56/0/1000"
   02.   'S contiene una data non valida
   03.   Dim D As Date
   04.
   05.   If Date.TryParse(S, D) Then
   06.        Console.WriteLine(D.ToLongDateString())
   07.   Else
   08.        Console.WriteLine("Data non valida!")
   09.   End If
Ty peOf
TypeOf ser ve per contr ollar e se una var iabile è di un cer to tipo, der iva da un cer to tipo o implementa una cer ta
inter faccia, ad esempio:

    1. Dim I As Int32
    2. If TypeOf I Is Int32 Then
    3.   'Questo blocco viene eseguito poichè I è di tipo Int32
    4. End If

Oppur e:

    1. Dim T As Student
    2. If TypeOf T Is Person Then
    3.     'Questo blocco viene eseguito perchè T, essendo Student, è
    4.     'anche di tipo Person, in quanto Student è una sua classe
    5.     'derivata
    6. End If

Ed infine un esempio sulle inter facce, che potr ete tor nar e a guar dar e da qui a qualche capitolo:

    1. Dim K(9) As Int32
    2. If TypeOf Is IEnumerable Then
    3.     'Questo blocco viene eseguito poiché gli array implementano
    4.     'sempre l'interfaccia IEnumerable
    5. End If
A31. L'Overloading

L'Over loading è la capacità di un linguaggio ad oggetti di poter definir e, nella stessa classe, più var ianti dello stesso
metodo. Per poter eseguir e cor r ettamente l'over loading, è che ogni var iante del metodo abbia queste car atter istiche:

         Sia della stessa categor ia (pr ocedur a O funzione, anzi, per dir la in modo più esplicito: pr ocedur a Xor funzione);
         Abbia lo stesso nome;
         Abbia signatur e diver sa da tutte le altr e var ianti. Per color o che non se lo r icor dasser o, la signatur e di un
         metodo indica il tipo e la quantità dei suoi par ametr i. Questo è il tr atto essenziale che per mette di
         differ enziar e concr etamente una var iante dall'altr a.

Per far e un esempio, il metodo Console.Wr iteLine espone ben 18 ver sioni diver se, che ci consentono di stampar e
pr essoché ogni dato sullo scher mo. Fr a quelle che non abbiamo mai usato, ce n'è una in par ticolar e che vale la pena di
intr odur r e or a, poiché molto utile e flessibile. Essa pr evede un pr imo par ametr o di tipo str inga e un secondo
Par amAr r ay di oggetti:

    1. Console.WriteLine("stringa", arg0, arg1, arg2, arg3, ...)

Il pr imo par ametr o pr ende il nome di str ing a di fo r m ato , poiché specifica il for mato in cui i dati costituiti dagli
ar gomenti addizionali dovr anno esser e visualizzati. All'inter no di questa str inga, si possono specificar e, oltr e ai
nor mali car atter i, dei codici speciali, nella for ma "{I}", dove I è un numer o compr eso tr a 0 e il numer o di par amtr i
meno uno: "{I}" viene detto segnaposto e ver r à sostituito dal par ametr o I nella str inga. Ad esempio:

    1.    A = 1
    2.    B = 3
    3.    Console.WriteLine("La somma di {0} e {1} è {2}.", A, B, A + B)
    4.    '> "La somma di 1 e 3 è 4."

Ulter ior i infor mazioni sulle str inghe di for mato sono disponibili nel capitolo "Magie con le str inghe".
Ma or a passiamo alla dichiar azione dei metodi in over load. La par ola chiave da usar e, ovviamente, è Over loads,
specificata poco dopo lo scope, e dopo gli eventuali Over r idable od Over r ides. Le entità che possono esser e sottoposte
ad over load, oltr e ai metodi, sono:

         Metodi statici
         Oper ator i
         Pr opr ietà
         Costr uttor i
         Distr uttor i

Anche se gli ultimi due sono sempr e metodi - per or a tr alasciamo i distr uttor i, che non abbiamo ancor a analizzato - è
bene specificar e con pr ecisione, per chè a compiti speciali spesso cor r ispondono compor tamenti altr ettanto speciali.
Ecco un semplicissimo esempio di over load:

   01. Module Module1
   02.
   03.     'Restituisce il numero di secondi passati dalla data D a oggi
   04.     Private Function GetElapsed(ByVal D As Date) As Single
   05.         Return (Date.Now - D).TotalSeconds
   06.     End Function
   07.
   08.     'Come sopra, ma il parametro è di tipo intero e indica
   09.     'un anno qualsiasi
   10.     Private Function GetElapsed(ByVal Year As Int32) As Single
   11.         'Utilizza Year per costruire un nuovo valore Date
   12.
'e usa la precedente variante del metodo per
   13.            'ottenere il risultato
   14.            Return GetElapsed(New Date(Year, 1, 1))
   15.        End Function
   16.
   17.        'Come le due sopra, ma il parametro è di tipo stringa
   18.        'e indica la data
   19.        Private Function GetElapsed(ByVal D As String) As Single
   20.            Return GetElapsed(Date.Parse(D))
   21.        End Function
   22.
   23.        Sub Main()
   24.            'GetElapsed viene       chiamata con tre tipi di parametri
   25.            'diversi, ma sono       tutti leciti
   26.            Dim El1 As Single       = GetElapsed(New Date(1987, 12, 4))
   27.            Dim El2 As Single       = GetElapsed(1879)
   28.            Dim El3 As Single       = GetElapsed("12/12/1991")
   29.
   30.            Console.ReadKey()
   31.        End Sub
   32.
   33. End   Module

Come avr ete notato, nell'esempio pr ecedente non ho usato la keyw or d Over loads: anche se le r egole dicono che i
membr i in over load vanno segnati, non è sempr e necessar io far lo. Anzi, molte volte si evita di dichiar ar e
esplicitamente i membr i di cui esistono var ianti come Over loads. Ci sono var ie r agioni per questa pr atica: l'over load è
scontato se i metodi pr esentano lo stesso nome, e il compilator e r iesce comunque a distinguer e tutto nitidamente;
inoltr e, capita spesso di definir e var ianti e per r ender e il codice più leggibile e meno pesante (anche se i sor genti in
VB tendono ad esser e un poco pr olissi), si omette Over loads. Tuttavia, esistono casi in cui è assolutamente necessar io
usar e la keyw or d; eccone un esempio:

   01. Module Module1
   02.     Class Person
   03.         Protected _FirstName, _LastName As String
   04.         Private ReadOnly _BirthDay As Date
   05.
   06.         Public Property FirstName() As String
   07.              Get
   08.                  Return _FirstName
   09.              End Get
   10.              Set(ByVal Value As String)
   11.                  If Value <> "" Then
   12.                      _FirstName = Value
   13.                  End If
   14.              End Set
   15.         End Property
   16.
   17.         Public Overridable Property LastName() As String
   18.              Get
   19.                  Return _LastName
   20.              End Get
   21.              Set(ByVal Value As String)
   22.                  If Value <> "" Then
   23.                      _LastName = Value
   24.                  End If
   25.              End Set
   26.         End Property
   27.
   28.         Public ReadOnly Property BirthDay() As Date
   29.              Get
   30.                  Return _BirthDay
   31.              End Get
   32.         End Property
   33.
   34.         Public Overridable ReadOnly Property CompleteName() As String
   35.              Get
   36.                  Return _FirstName & " " & _LastName
   37.              End Get
   38.         End Property
   39.
40.             'ToString è una funzione definita nella classe
   41.             'System.Object e poiché ogni cosa in .NET
   42.             'deriva da questa classe, &egrae; sempre possibile
   43.             'ridefinire tramite polimorfismo il metodo ToString.
   44.             'In questo caso ne scriveremo non una, ma due versioni,
   45.             'quindi deve essere dichiarato sia Overrides, perchè
   46.             'sovrascrive System.Object.ToString, sia Overloads,
   47.             'perchè è una versione alternativa di
   48.             'quella che andremo a scrivere tra poco
   49.             Public Overloads Overrides Function ToString() As String
   50.                 Return CompleteName
   51.             End Function
   52.
   53.             'Questa versione accetta un parametro stringa che assume
   54.             'la funzione di stringa di formato: il metodo restituirà
   55.             'la frase immessa, sostituendo {F} con FirstName e {L} con
   56.             'LastName. In questa versione è sufficiente
   57.             'Overloads, dato che non esiste un metodo ToString che
   58.             'accetti un parametro stringa in System.Object e perciò
   59.             'non lo potremmo modificare
   60.             Public Overloads Function ToString(ByVal FormatString As String) _
   61.                 As String
   62.                 Dim Temp As String = FormatString
   63.                 'Sostituisce {F} con FirstName
   64.                 Temp = Temp.Replace("{F}", _FirstName)
   65.                 'Sostituisce {L} con LastName
   66.                 Temp = Temp.Replace("{L}", _LastName)
   67.
   68.                 Return Temp
   69.             End Function
   70.
   71.            Sub New(ByVal FirstName As String, ByVal LastName As String, _
   72.                ByVal BirthDay As Date)
   73.                Me.FirstName = FirstName
   74.                Me.LastName = LastName
   75.                Me._BirthDay = BirthDay
   76.            End Sub
   77.        End Class
   78.
   79.        Sub Main()
   80.            Dim P As New Person("Mario", "Rossi", Date.Parse("17/07/67"))
   81.
   82.             Console.WriteLine(P.ToString)
   83.             '> Mario Rossi
   84.
   85.             'vbCrLf è una costante che rappresenta il carattere
   86.             '"a capo"
   87.             Console.WriteLine(P.ToString("Nome: {F}" & vbCrLf & "Cognome: {L}"))
   88.             '> Nome: Mario
   89.             '> Cognome: Rossi
   90.
   91.           Console.ReadKey()
   92.       End Sub
   93. End   Module

Come mostr ato dall'esempio, quando il membr o di cui si vogliono definir e var ianti è sottoposto anche a polimor fismo, è
necessar io specificar e la keyw or d Over loads, poiché, in caso contr ar io, il compilator e r intr accer ebbe quello stesso
membr o come diver so e, non potendo esister e membr i con lo stesso nome, pr odur r ebbe un er r or e.
A32. Gestione degli errori

Fino ad or a, nello scr iver e il codice degli esempi, ho sempr e (o quasi sempr e) supposto che l'utente inser isse dati
coer enti e cor r etti. Al massimo, ho inser ito qualche costr utto di contr ollo per ver ificar e che tutto andasse bene.
Infatti, per quello che abbiamo visto fino ad or a, c'er ano solo due modi per evitar e che il pr ogr amma andasse in cr ash
o pr oducesse output pr ivi di senso: scr iver e del codice a pr ova di bomba (e questo, gar antisco, non è sempr e possibile)
o contr ollar e, pr ima di eseguir e le oper azioni, che tutti i dati fosser o per fettamente coer enti con il pr oblema da
affr ontar e.
Ahim�, non è sempr e possibile agir e in questo modo: ci sono cer ti casi in cui né l'uno né l'altr o metodo sono efficaci. E
di questo posso for nir e subito un esempio lampante: ammettiamo di aver scr itto un pr ogr amma che esegua la
divisione tr a due numer i. Molto banale come codice. Chiediamo all'utente i suddetti dati con Console.ReadLine,
contr olliamo che il secondo sia diver so da 0 (pr opr io per evitar e un er r or e a r untime) e in questo caso stampiamo il
r isultato. Ma... se l'utente inser isse, ad esempio, una letter a anziché un numer o, o per sbaglio o per pur o sadismo?
Beh, qualcuno potr à pensar e "Usiamo Tr yCast", tuttavia Tr yCast, essendo una r iedizione di Dir ectCast, agisce solo
ver so tipi r efer ence e Int32 è un tipo base. Qualcun altr o, invece, potr ebbe pr opor r e di usar e Tr yPar se, ma abbiamo
già r ilevato come la funzione Par se sia di vedute r istr ette. In definitiva, non abbiamo alcun modo di contr ollar e pr ima
se il dato immesso o no sia r ealmente coer ente con ciò che stiamo chiedendo all'utente. Possiamo saper e se il dato non
è coer ente solo quando si ver ifica l'er r or e, ma in questo caso non possiamo per metter ci che il pr ogr amma vada in
cr ash per una semplice distr azione. Dovr emo, quindi, ges tire l'er r or e.
In .NET quelli che finor a ho chiamato "er r or i" si dicono, più pr opr iamente, Eccezio ni e sono anch'esse r appr esentate da
una classe: la classe base di tutte le eccezioni è System.Ex ception, da cui der ivano tutte le var ianti per le specifiche
eccezioni (ad esempio divisione per zer o, file inesistente, for mato non valido, ecceter a...). Accanto a queste, esiste
anche uno specifico costr utto che ser ve per gestir le, e pr ende il nome di Tr y. Ecco la sua sintassi:

    1. Try
    2.     'Codice che potrebbe generare l'eccezione
    3. Catch [Variabile] As [Tipo Eccezione]
    4.     'Gestisce l'eccezione [Tipo Eccezione]
    5. End Try

Tr a Tr y e Catch viene scr itto il codice incr iminato, che potr ebbe eventualmente gener ar e l'er r or e che noi stiamo
tentando di r intr acciar e e gestir e. Nello specifico, quando accade un avvenimento del gener e, si dice che il codice
"lancia" un'eccezione, poiché la par ola chiave usata per gener ar la, come vedr emo, è pr opr io Thr ow (= lanciar e). Or a,
passatemi il par agone che sto per far e, for se un po' fantasioso: il metodo in questione è come una fionda che scaglia un
sassolino - un pacchetto di infor mazioni che ci dice tutto sul per chè e sul per come è stato gener ato quello specifico
er r or e. Or a, se questo sassolino viene inter cettato da qualcosa (dal blocco catch), possiamo evitar e danni collater ali,
ma se niente blocca la sua cor sa, ahimé, dovr emmo r ipagar e i vetr i r otti a qualcuno. Ecco un esempio:

   01. Module Module1
   02.     Sub Main()
   03.         Dim a, b As Single
   04.         'ok controlla se a e b sono coerenti
   05.         Dim ok As Boolean = False
   06.
   07.         Do
   08.              'Tenta di leggere i numeri da tastiera
   09.              Try
   10.                  Console.WriteLine("Inserire due numeri non nulli: ")
   11.                  a = Console.ReadLine
   12.                  b = Console.ReadLine
   13.                  'Se il codice arriva fino a questo punto, significa
   14.                  'che non si sono verificate eccezioni
   15.                  ok = True
   16.
Catch Ex As InvalidCastException
   17.                  'Se, invece, il programma arriva in questo blocco,
   18.                  'vuol dire che abbiamo "preso" (catch) un'eccezione
   19.                  'di tipo InvalidCastException, che è stata
   20.                  '"lanciata" dal codice precedente. Tutti i dati
   21.                  'relativi a quella eccezione sono ora conservati
   22.                  'nella variabile Ex.
   23.                  'Possiamo accedervi oppure no, come in questo caso,
   24.                  'ma sono in ogni caso informazioni utili, come
   25.                  'vedremo fra poco
   26.                  Console.WriteLine("I dati inseriti non sono numeri!")
   27.                  'I dati non sono coerenti, quindi ok = False
   28.                  ok = False
   29.              End Try
   30.              'Richiede gli stessi dati fino a che non si tratta
   31.              'di due numeri
   32.         Loop Until ok
   33.
   34.         'Esegue il controllo su b e poi effettua la divisione
   35.         If b <> 0 Then
   36.              Console.WriteLine("{0} / {1} = {2}", a, b, a / b)
   37.         Else
   38.              Console.WriteLine("Divisione impossibile!")
   39.         End If
   40.
   41.         Console.ReadKey()
   42.     End Sub
   43. End Module

Or a potr este anche chieder vi "Come faccio a saper e quale classe r appr esenta quale eccezione?". Beh, in gener e, si
mette un blocco Tr y dopo aver notato il ver ificar si dell'er r or e e quindi dopo aver letto il messaggio di er r or e che
contiene anche il nome dell'eccezione. In alter nativa si può specificar e come tipo semplicemente Ex ception, ed in quel
caso ver r anno cattur ate tutte le eccezioni gener ate, di qualsiasi tipo. Ecco una var iante dell'esempio pr ecedente:

   01. Module Module1
   02.     Sub Main()
   03.         'a e b sono interi short, ossia possono assumere
   04.         'valori da -32768 a +32767
   05.         Dim a, b As Int16
   06.         Dim ok As Boolean = False
   07.
   08.         Do
   09.              Try
   10.                  Console.WriteLine("Inserire due numeri non nulli: ")
   11.                  a = Console.ReadLine
   12.                  b = Console.ReadLine
   13.                  ok = True
   14.              Catch Ex As Exception
   15.                  'Catturiamo una qualsiasi eccezione e stampiamo il
   16.                  'messaggio
   17.                  'ad essa relativo. Il messaggio è contenuto nella
   18.                  'proprietà Message dell'oggetto Ex.
   19.                  Console.WriteLine(Ex.Message)
   20.                  ok = False
   21.              End Try
   22.         Loop Until ok
   23.
   24.         If b <> 0 Then
   25.              Console.WriteLine("{0} / {1} = {2}", a, b, a / b)
   26.         Else
   27.              Console.WriteLine("Divisione impossibile!")
   28.         End If
   29.
   30.         Console.ReadKey()
   31.     End Sub
   32. End Module

Pr ovando ad inser ir e un numer o tr oppo gr ande o tr oppo piccolo si otter r à "Over flow di un'oper azione ar itmetica.";
inser endo una str inga non conver tibile in numer o si otter r à "Cast non valido dalla str inga [str inga] al tipo 'Shor t'".
Questa ver sione, quindi, cattur a e gestisce ogni possibile eccezione.
Ricor date che è possibile usar e anche più clausole Catch in un unico blocco Tr y, ad esempio una per ogni eccezione
diver sa.




Clausola Finally
Il costr utto Tr y è costituito da un blocco Tr y e da una o più clausole Catch. Tuttavia, opzionalmente, è possibile
specificar e anche un'ulter ior e clausola, che deve esser e posta dopo tutti i Catch: Finally. Finally dà inizio ad un altr o
blocco di codice che viene s empre eseguito, sia che si gener i un'eccezione, sia che non se ne gener i alcuna. Il codice ivi
contenuto viene eseguito comunque dopo il tr y e il catch. Ad esempio, assumiamo di aver e questo blocco di codice, con
alcune istr uzioni di cui non ci inter essa la natur a: mar chiamo le istr uzioni con delle letter e e ipotizziamo che la D
gener i un'eccezione:

   01.      Try
   02.          A
   03.          B
   04.          C
   05.          D
   06.          E
   07.          F
   08.      Catch Ex As Exception
   09.          G
   10.          H
   11.      Finally
   12.          I
   13.          L
   14.      End Try

Le istr uzioni eseguite sar anno:

   01.      A
   02.      B
   03.      C
   04.      'Eccezione: salta nel blocco Catch
   05.      G
   06.      H
   07.      'Alla fine esegue comunque il Finally
   08.      I
   09.      L




Lanc iare un'ec c ezione e c reare ec c ezioni personalizzate
Ammettiamo or a di aver bisogno di un'eccezione che r appr esenti una par ticolar e cir costanza che si ver ifica solo nle
nostr o pr ogr amma, e di cui non esiste un cor r ispettivo tr a le eccezioni pr edefinite del Fr amew or k. Dovr emo scr iver e
una nuova eccezione. Per far ciò, bisogna semplicemente dichiar ar e una nuova classe che er editi dalla classe Ex eption:

   01. Module Module1
   02.     'Questa classe rappresenta l'errore lanciato quando una
   03.     'password imessa è sbagliata. Per convenzione, tutte le
   04.     'classi che rappresentano un'eccezione devono terminare
   05.     'con la parola "Exception"
   06.     Class IncorrectPasswordException
   07.         Inherits System.Exception 'Eredita da Exception
   08.
   09.         'Queste proprietà ridefiniscono quelle della classe
   10.         'Exception tramite polimorfismo, perciò sono
   11.         'dichiarate Overrides
   12.
   13.         'Sovrascrive il messaggio di errore
   14.         Public Overrides ReadOnly Property Message() As String
   15.              Get
   16.
Return "La password inserita � sbagliata!"
   17.                    End Get
   18.                End Property
   19.
   20.                'Modifica il link di aiuto
   21.                Public Overrides Property HelpLink() As String
   22.                    Get
   23.                        Return "http://totem.altervista.org"
   24.                    End Get
   25.                    Set(ByVal Value As String)
   26.                        MyBase.HelpLink = value
   27.                    End Set
   28.                End Property
   29.
   30.            'Il resto dei membri di Exception sono molto importanti
   31.            'e vengono inizializzati con dati prelevati tramite
   32.            'Reflection (ultimo argomento di questa sezione), perciò
   33.            'è conveniente non modificare altro. Potete
   34.            'semmai aggiungere qualche membro
   35.        End Class
   36.
   37.        Sub Main()
   38.            Dim Pass As String = "b7dha90"
   39.            Dim NewPass As String
   40.
   41.                Try
   42.                    Console.WriteLine("Inserire la password:")
   43.                    NewPass = Console.ReadLine
   44.                    If NewPass <> Pass Then
   45.                        'Lancia l'eccezione usando la keyword Throw
   46.                        Throw New IncorrectPasswordException
   47.                    End If
   48.                Catch IPE As IncorrectPasswordException
   49.                    'Visualizza il messaggio
   50.                    Console.WriteLine(IPE.Message)
   51.                    'E il link d'aiuto
   52.                    Console.WriteLine("Help: " & IPE.HelpLink)
   53.                End Try
   54.
   55.            Console.ReadKey()
   56.        End Sub
   57. End    Module

Come si è visto nell'esempio, lanciar e un'eccezione è molto semplice: basta scr iver e Thr ow , seguito da un oggetto
Ex ception valido. In questo caso abbiamo cr eato l'oggetto Incor r ectPassw or dEx ception nello stessa linea di codice in cui
l'abbiamo lanciato.
A33. Distruttori

Avver tenza: questo è un capitolo molto tecnico. For se vi sar à più utile in futur o.




Gli oggetti COM (Component Object Model) utilizzati dal vecchio VB6 possedevano una car atter istica peculiar e che
per metteva di deter minar e quando non vi fosse più bisogno di lor o e la memor ia associata potesse esser e r ilasciata:
er ano dotati di un r efer ence counter , ossia di un "contator e di r ifer imenti". Ogni volta che una var iabile veniva
impostata su un oggetto COM, il contator e veniva aumentato di 1, mentr e quando quella var iabile veniva distr utta o se
ne cambiava il valor e, il contator e scendeva di un'unità. Quando tale valor e r aggiungeva lo zer o, gli oggetti venivano
distr utti. Er ano pr esenti alcuni pr oblemi di cor r uzione della memor ia, per ò: ad esempio se due oggetti si puntavano
vicendevolmente ma non er ano utilizzati dall'applicazione, essi non venivano distr utti (r ifer imento cir colar e).
Il meccanismo di gestione della memor ia con il .NET Fr amew or k è molto diver so, e or a vediamo come oper a.




Garbage Collec tion
Questo è il nome del pr ocesso sul quale si basa la gestione della memor ia del Fr amew or k. Quando l'applicazione tenta di
cr ear e un nuovo oggetto e lo spazio disponibile nell'heap managed scar seggia, viene messo in moto questo meccanismo,
attr aver so l'attivazione del Gar bage Collector . Per pr ima cosa vengono visitati tutti gli oggetti pr esenti nello heap: se
ce n'è uno che non è r aggiungibile dall'applicazione, questo viene distr utto. Il pr ocesso è molto sofisticato, in quanto è in
gr ado di r ilevar e anche dipendenze indir ette, come classi non r aggiungibili dir ettamente, r efer enziate da altr e classi
che sono r aggiungibili dir ettamente; r iesce anche a r isolver e il pr oblema opposto, quello del r ifer imento cir colar e. Se
uno o più oggetti non vengono distr utti per chè sono necessar i al pr ogr amma per funzionar e, si dice che essi sono
sopr avvissuti a una Gar bage Collection e appar tengono alla gener azione 1, mentr e quelli inizializzati che non hanno
subito ancor a nessun pr ocesso di r accolta della memor ia sono di gener azione 0. L'indice gener azionale viene
incr ementato di uno fino ad un massimo di 2. Questi ultimi oggetti sono sopr avvissuti a molti contr olli, il che significa
che continuano a esser e utilizzati nello stesso modo: per ciò il Gar bage Collector li sposta in una posizione iniziale
dell'heap managed, in modo che si dovr anno eseguir e meno oper azioni di spostamento della memor ia in seguito. La
stessa cosa vale per le gener azioni successive. Questo sistema assicur a che ci sia sempr e spazio liber o, ma non
gar antisce che ogni oggetto logicamente distr utto lo sia anche fisicamente: se per quegli oggetti che allocano solo
memor ia il pr oblema è r elativo, per altr i che utilizzano file e r isor se ester ne, invece, diventa più complicato. Il
compito di r ilasciar e le r isor se spetta quindi al pr ogr ammator e, che dovr ebbe, in una classe ideale, pr eoccupar si che
quando l'oggetto venga distr utto lo siano cor r ettamente anche le r isor se ad esso associate. Bisogna quindi far e
eseguir e del codice appena pr ima della distr uzione: come? lo vediamo or a.




Finalize
Il metodo Finalize di un oggetto è speciale, poichè viene r ichiamato dal Gar bage Collector "in per sona" dur ante la
r accolta della memor ia. Come già detto, non è possibile saper e quando un oggetto logicamente distr utto lo sar à anche
fisicamente, quindi Finalize potr ebbe esser e eseguito anche diver si secondi, o minuti, o addir ittur a or e, dopo che sia
stato annullato ogni r ifer imento all'oggetto. Come seconda clausola impor tante, è necessar io non acceder e m ai ad
oggetti ester ni in una pr ocedur a Finalize: dato che il GC (acr onimo di gar bage collector ) può distr ugger e gli oggetti in
qualsiasi or dine, non si può esser e sicur i che l'oggetto a cui si sta facendo r ifer imento esista ancor a o sia già stato
distr utto. Questo vale anche per oggetti singleton come Console o Application, o addir ittur a per i tipi Str ing, Byte,
Date e tutti gli altr i (dato che, essendo anch'essi istanze di System.Type, che definisce le car atter istiche di ciascun tipo,
sono soggetti alla GC alla fine del pr ogr amma). Per saper e se il pr ocesso di distr uzione è stato avviato dalla chiusur a
del pr ogr amma si può r ichiamar e una semplice pr opr ietà booleana, Envir onment.HasShutdow nStar ted. Per
esemplificar e i concetti, in questo par agr afo far ò uso dell'oggetto singleton GC, che r appr esenta il Gar bage Collector ,
per mettendo di avviar e for zatamente la r accolta della memor ia e altr e cose: questo no n deve mai esser e fatto in
un'applicazione r eale, poichè potr ebbe compr ometter ne le pr estazioni.

   01. Module Module1
   02.     Class Oggetto
   03.         Sub New()
   04.              Console.WriteLine("Un oggetto sta per essere creato.")
   05.         End Sub
   06.         'La procedura Finalize è definita in System.Object, quindi,
   07.         'per ridefinirla dobbiamo usare il polimorfismo. Inoltre
   08.         'deve essere dichiarata Protected, poichè non può
   09.         'essere richiamata da altro ente se non dal GC e allo
   10.         'stesso tempo è ereditabile
   11.         Protected Overrides Sub Finalize()
   12.              Console.WriteLine("Un oggetto sta per essere distrutto.")
   13.              'Blocca il programma per 4 secondi circa, consentendoci
   14.              'di vedere cosa viene scritto a schermo
   15.              System.Threading.Thread.CurrentThread.Sleep(4000)
   16.         End Sub
   17.     End Class
   18.
   19.     Sub Main()
   20.         Dim O As New Oggetto
   21.         Console.WriteLine("Oggetto = Nothing")
   22.         Console.WriteLine("L'applicazione sta per terminare.")
   23.     End Sub
   24. End Module

L'output sar à:

    1.   Un oggetto sta per essere creato.
    2.   Oggetto = Nothing
    3.   L'applicazione sta per terminare.
    4.   Un oggetto sta per essere distrutto.

Come si vede, l'oggetto viene distr utto do po il ter mine dell'applicazione (siamo for tunati che Console è ancor a "in vita"
pr ima della distr uzione): questo significa che c'er a abbastanza spazio disponibile da non avviar e la GC, che quindi è
stata r imandata fino alla fine del pr ogr amma. Ripr oviamo invece in questo modo:

   01. Sub Main()
   02.     Dim O As New Oggetto
   03.     O = Nothing
   04.     Console.WriteLine("Oggetto = Nothing")
   05.
   06.     'NON PROVATECI A CASA!
   07.     'Forza una garbage collection
   08.     GC.Collect()
   09.     'Attende che tutti i metodi Finalize siano stati eseguiti
   10.     GC.WaitForPendingFinalizers()
   11.
   12.     Console.WriteLine("L'applicazione sta per terminare.")
   13.     Console.ReadKey()
   14. End Sub

Ciò che appar ir à sullo scher mo è:

    1.   Un oggetto sta per essere creato.
    2.   Oggetto = Nothing
    3.   Un oggetto sta per essere distrutto.
    4.   L'applicazione sta per terminare.

Si vede che l'or dine delle ultime due azioni è stato cambiato a causa delle GC avviata anzi tempo pr ima del ter mine del
pr ogr amma.
Anche se ci siamo diver titi con Finalize, questo metodo deve esser e definito solo se str ettamente necessar io, per
alcune r agioni. La pr ima è che il GC impiega non uno, ma due cicli per finalizzar e un oggetto in cui è stata definita
Finalize dal pr ogr ammator e. Il motivo consiste nella possibilità che venga usata la cosiddetta r esur r ezio ne
dell'o g g etto : in questa tecnica, ad una var iabile globale viene assegnato il r ifer imento alla classe stessa usando Me;
dato che in questo modo c'è ancor a un r ifer imento valido all'oggetto, questo non deve venir e distr utto. Tuttavia, per
r ilevar e questo fenomeno, il GC impiega due cicli e si r ischia di occupar e memor ia inutile. Inoltr e, sempr e per questa
causa, si impiega più tempo macchina che potr ebbe esser e speso in altr o modo.




Dispose
Si potr ebbe definir e Dispose come un Finalize manuale: esso per metto di r ilasciar e qualsiasi r isor sa che non sia la
memor ia (ossia connessioni a database, files, immagini, pennelli, oggetti di sistema, ecceter a...) manualmente, appena
pr ima di impostar e il r ifer imento a Nothing. In questo modo non si dovr à aspettar e una successiva GC affinchè sia
r ilasciato tutto cor r ettamente. Dispose non è un metodo definito da tutti gli oggetti, e per ciò ogni classe che intende
definir lo deve implementar e l'inter faccia IDisposable (per ulter ior i infor mazioni sulle inter facce, veder e capitolo 36):
per or a pr endete per buono il codice che for nisco, vedr emo in seguito più appr ofonditamente l'agor mento delle
inter facce.

   01. Class Oggetto
   02.     'Implementa l'interfaccia IDisposable
   03.     Implements IDisposable
   04.     'File da scrivere:
   05.     Dim W As IO.StreamWriter
   06.
   07.     Sub New()
   08.         'Inizializza l'oggetto
   09.         W = New IO.StreamWriter("C:test.txt")
   10.     End Sub
   11.
   12.     Public Sub Dispose() Implements IDisposable.Dispose
   13.         'Chiude il file
   14.         W.Close()
   15.     End Sub
   16. End Class

Invocando il metodo Dispose di Oggetto, è possibile chiuder e il file ed evitar e che venga lasciato aper to. Il Vb.NET
for nisce un costr utto, valido per tutti gli oggetti che implementano l'inter faccia IDisposable, che si assicur a di
r ichiamar e il metodo Dispose e impostar e il r ifer imento a Nothing automaticamente dopo l'uso. La sintassi è questa:

    1.   Using [Oggetto]
    2.     'Codice da eseguire
    3.   End Using
    4.
    5.   'Che corrisponde a scrivere:
    6.   'Codice da eseguire
    7.   [Oggetto].Dispose()
    8.   [Oggetto] = Nothing

Per convenzione, se una classe implementa un'inter faccia IDisposable e contiene altr e classi nidificate o altr i oggetti, il
suo metodo Dispose deve r ichiamar e il Dispose di tutti gli oggetti inter ni, almeno per quelli che ce l'hanno. Altr a
convenzione è che se viene r ichiamata Dispose da un oggetto già distr utto logicamente, deve gener ar si l'eccezione
ObjectDisposedEx ception.




Usare Dispose e Finalize
Ci sono alcune cir costanze che r ichiedono l'uso di una sola delle due, altr e che non le r ichiedono e altr e ancor a che
dovr ebber o r cihieder le entr ambe. Segue una piccola lista di sugger imenti su come metter e in pr atica questi
meccanismi:

       Nè Dispose, nè Finalize: la classe impiega solo la memor ia come unica r isor sa o, se ne impiegate altr e, le r ilascia
       pr ima di ter minar e le pr opr ie oper azioni.
       Solo Dispose: la classe impiega r isor se facendo r ifer imento ad altr i oggetti .NET e si vuole for nir e al chiamante
       la possibilità di r ilasciar e tali r isor se il pr ima possibile.
       Dispose e Finalize: la classe impiega dir ettamente una r isor sa, ad esempio invocando un metodo di una libr er ia
       unmanaged, che r ichiede un r ilascio esplicito; in più si vuole for nir e al client la possibilità di deallocar e
       manualmente gli oggetti.
       Solo Finalize: si deve eseguir e un cer to codice pr ima della distr uzione.

A questo punto ci si deve pr eoccupar e di due pr oblemi che possono pr esentar si: Finalize può esser e chiamato anche
dopo che l'oggetto è stato distr utto e le sue r isor se deallocate con Dispose, quindi potr ebbe tantar e di distr ugger e un
oggetto inesistente; il codice che viene eseguito in Finalize potr ebbe far r ifer imento a oggetti inesistenti. Le
convenzioni per mettono di aggir ar e il pr oblema facendo uso di ver sioni in over load di Dispose e di una var iabile
pr ivata a livello di classe. La var iabile booleana Disposed ha il compito di memor izzar e se l'oggetto è stato distr utto: in
questo modo eviter emo di r ipeter e il codice in Finalize. Il metodo in over load di Dispose accetta un par ametr o di tipo
booleano, di solito chiamato Disposing, che indica se l'oggetto sta subendo un pr ocesso di distr uzione manuale o di
finalizzazione: pr ocedendo con questo metodo si è cer ti di r ichiamar e eventuali altr i oggetti nel caso non ci sia
finalizzazione. Il codice seguente implementa una semplicissima classe FileWr iter e, tr amite messaggi a scher mo,
visualizza quando e come l'oggetto viene r imosso dalla memor ia:

 001. Module Module1
 002.     Class FileWriter
 003.         Implements IDisposable
 004.
 005.         Private Writer As IO.StreamWriter
 006.         'Indica se l'oggetto è già stato distrutto con Dispose
 007.         Private Disposed As Boolean
 008.         'Indica se il file è aperto
 009.         Private Opened As Boolean
 010.
 011.         Sub New()
 012.              Disposed = False
 013.              Opened = False
 014.              Console.WriteLine("FileWriter sta per essere creato.")
 015.              'Questa procedura comunica al GC di non richiamare più
 016.              'il metodo Finalize per questo oggetto. Scriviamo ciò
 017.              'perchè se file non viene esplicitamente aperto con
 018.              'Open non c'è alcun bisogno di chiuderlo
 019.              GC.SuppressFinalize(Me)
 020.         End Sub
 021.
 022.         'Apre il file
 023.         Public Sub Open(ByVal FileName As String)
 024.              Writer = New IO.StreamWriter(FileName)
 025.              Opened = True
 026.              Console.WriteLine("FileWriter sta per essere aperto.")
 027.              'Registra l'oggetto per eseguire Finalize: ora il file
 028.              'è aperto e può quindi essere chiuso
 029.              GC.ReRegisterForFinalize(Me)
 030.         End Sub
 031.
 032.         'Scrive del testo nel file
 033.         Public Sub Write(ByVal Text As String)
 034.              If Opened Then
 035.                  Writer.Write(Text)
 036.              End If
 037.         End Sub
 038.
 039.         'Una procedura analoga a Open aiuta a impostare meglio
 040.         'l'oggetto e non fa altro che richiamare Dispose: è
 041.         'più una questione di completezza
 042.
Public Sub Close()
 043.               Dispose()
 044.           End Sub
 045.
 046.           'Questa versione è in overload perchè l'altra viene
 047.           'chiamata solo dall'utente (è Public), mentre questa
 048.           'implementa tutto il codice che è necessario eseguire
 049.           'per rilasciare le risorse.
 050.           'Il parametro Disposing indica se l'oggetto sta per
 051.           'essere distrutto, quindi manualmente, o finalizzato,
 052.           'quindi nel processo di GC: nel secondo caso altri oggetti
 053.           'che questa classe utilizza potrebbero non esistere più,
 054.           'perciò si deve controllare se è possibile
 055.           'invocarli correttamente
 056.           Protected Overridable Overloads Sub Dispose(ByVal Disposing _
 057.               As Boolean)
 058.               'Esegue il codice solo se l'oggetto esiste ancora
 059.               If Disposed Then
 060.                   'Se è distrutto, esce dalla procedura
 061.                   Exit Sub
 062.               End If
 063.
 064.               If Disposing Then
 065.                    'Qui possiamo chiamare altri oggetti con la
 066.                    'sicurezza che esistano ancora
 067.                    Console.WriteLine("FileWriter sta per essere distrutto.")
 068.               Else
 069.                    Console.WriteLine("FileWriter sta per essere finalizzato.")
 070.               End If
 071.
 072.               'Chiude il file
 073.               Writer.Close()
 074.
 075.               Disposed = True
 076.               Opened = False
 077.           End Sub
 078.
 079.           Public Overloads Sub Dispose() Implements IDisposable.Dispose
 080.               'L'oggetto è stato distrutto
 081.               Dispose(True)
 082.               'Quindi non deve più essere finalizzato
 083.               GC.SuppressFinalize(Me)
 084.           End Sub
 085.
 086.           Protected Overrides Sub Finalize()
 087.               'Processo di finalizzazione:
 088.               Dispose(False)
 089.           End Sub
 090.       End Class
 091.
 092.       Sub Main()
 093.           Dim F As New FileWriter
 094.           'Questo blocco mostra l'esecuzione di Dispose
 095.           F.Open("C:test.txt")
 096.           F.Write("Ciao")
 097.           F.Close()
 098.
 099.           'Questo mostra l'esecuzione di Finalize
 100.           F = New FileWriter
 101.           F.Open("C:test2.txt")
 102.           F = Nothing
 103.
 104.           GC.Collect()
 105.           GC.WaitForPendingFinalizers()
 106.
 107.           Console.ReadKey()
 108.       End Sub
 109. End   Module

L'output:

    1. FileWriter sta per essere creato.
    2.
FileWriter   sta   per   essere   aperto.
3.   FileWriter   sta   per   essere   distrutto.
4.   FileWriter   sta   per   essere   creato.
5.   FileWriter   sta   per   essere   aperto.
6.   FileWriter   sta   per   essere   finalizzato.
A34. I Delegate

Con il ter mine Deleg ate si indica un par ticolar e tipo di dato che è in gr ado di "contener e" un metodo, ossia una
pr ocedur a o una funzione. Ho messo di pr oposito le vir golette sul ver bo "contener e", poiché non è pr opr iamente
esatto, ma ser ve per r ender e più incisiva la definizione. Come esistono tipi di dato per gli inter i, i decimali, le date, le
str inghe, gli oggetti, ne esistono anche per i metodi, anche se può sembr ar e un po' str ano. Per chi avesse studiato
altr i linguaggi pr ima di appr occiar si al VB.NET, possiamo assimilar e i Delegate ai tipi pr ocedur ali del Pascal o ai
puntator i a funzione del C. Ad ogni modo, i delegate sono legger mente diver si da questi ultimi e pr esentano alcuni
tr atti par ticolar i:

        Un delegate non può contener e quals ias i metodo, ma he dei limiti. Infatti, è in gr ado di contener e solo metodi
        con la stessa signatur e specificata nella definizione del tipo. Fr a br eve vedr emo in cosa consiste questo punto;
        Un delegate può contener e sia metodi di istanza sia metodi statici, a patto che questi r ispettino la r egole di cui
        al punto sopr a;
        Un delegate è un tipo r efer ence, quindi si compor ta come un comunissimo oggetto, seguendo quelle r egole che
        mi sembr a di aver già r ipetuto fino alla noia;
        Un oggetto di tipo delegate è un oggetto immutabile, ossia, una volta cr eato, non può esser e modificato. Per
        questo motivo, non espone alcuna pr opr ietà (tr anne due in sola lettur a). D'altr a par te, questo compor tamento
        er a pr evedibile fin dalla definizione: infatti, se un delegate contiene un r ifer imento ad un metodo - e quindi un
        metodo già esistente e magar i definito in un'altr a par te del codice - come si far ebbe a modificar lo? Non si
        potr ebbe modificar e la signatur e per chè questo andr ebbe in conflitto con la sua natur a, e non si potr ebbe
        modificar ne il cor po per chè si tr atta di codice già scr itto (r icor date che gli oggetti esistono solo a r un-time,
        per chè vengono cr eati solo dopo l'avvio del pr ogr amma, e tutto il codice è già stato compilato e tr asfor mato in
        linguaggio macchina inter medio);
        Un delegate è un tipo s afe, ossia non può mai contener e r ifer imenti ad indir izzi di memor ia che non indichino
        espr essamente un metodo (al contr ar io dei per icolosi puntator i del C).

Mi r endo conto che questa intr oduzione può appar ir e un po' tr oppo teor ica e fumosa, ma ser ve per compr ender e il
compor tamento dei delegate.




Dic hiarazione di un delegate
Un nuovo tipo delegate viene dichiar ato con questa sintassi:

     1. Delegate [Sub/Function] [Nome]([Elenco parametri])

Appar e subito chiar o il legame con i metodi data la for tissima somiglianza della sintassi con quella usata per definir e,
appunto, un metodo. Notate che in questo caso si specifica solo la signatur e (tipo e quantità dei par ametr i) e la
categor ia (pr ocedur a o funzione) del delegate, mentr e il [Nome] indica il nome del nuovo tipo cr eato (così come il nome
di una nuova classe o una nuova str uttur a), ma non vi è tr accia del "cor po" del delegate. Un delegate, infatti, non ha
cor po, per chè, se invocato da un oggetto, esegue i metodi che esso stesso contiene, e quindi esegue il codice contenuto
nei lor o cor pi. Da questo momento in poi, potr emo usar e nel codice questo nuovo tipo per immagazzinar e inter i
metodi con le stesse car atter istiche appena definite. Dato che si tr atta di un tipo r efer ence, per ò, bisogna anche
inizializzar e l'oggetto con un costr uttor e... Qui dovr ebbe sor ger e spontaneamente un dubbio: dove e come si dichiar a
il costr uttor e di un delegate? Fino ad or a, infatti, gli unici tipi r efer ence che abbiamo impar ato a dichiar ar e sono le
classi, e nelle classi è lecito scr iver e un nuovo costr uttor e New nel lor o cor po. Qui, invece, non c'è nessun cor po in cui
por r e un ipotetico costr uttor e. La r ealtà è che si usa sem pr e il costr uttor e di default, ossia quello pr edefinito, che
viene automaticamente cr eato all'atto stesso della dichiar azione, anche se noi non r iusciamo a veder lo. Questo
costr uttor e accetta sempr e e solo un par ametr o: un oggetto di tipo indeter minato r estituito da uno speciale
oper ator e, Addr essOf. Questo è un oper ator e unar io che accetta come oper ando il metodo di cui ottener e l'"indir izzo":

    1. AddressOf [NomeMetodo]

Ciò che Addr essOf r estituisce non è molto chiar o: la sua descr izione dice espr essamente che viene r estituito un oggetto
delegate (il che è già abbastanza str ano di per sé, dato che per cr ear e un delegate ci vuole un altr o delegate).
Tuttavia, se si utilizza come par ametr o del costr uttor e un oggetto System.Delegate viene r estituito un er r or e. Ma
lasciamo queste disquisizioni a chi ha tempo da per der e e pr ocediamo con le cose impor tanti.
N.B.: Dalla ver sione 2008, i costr uttor i degli oggetti delegate accettano anche espr essioni lambda!
Una volta dichiar ata ed inizializzata una var iabile di tipo delegate, è possibile usar la esattamente come se fosse un
metodo con la signatur e specificata. Ecco un esempio:

   01. Module Module1
   02.     'Dichiarazione di un tipo delegate Sub che accetta un parametro
   03.     'di tipo stringa.
   04.     Delegate Sub Display(ByVal Message As String)
   05.
   06.     'Una procedura dimostrativa
   07.     Sub Write1(ByVal S As String)
   08.         Console.WriteLine("1: " & S)
   09.     End Sub
   10.
   11.     'Un'altra procedura dimostrativa
   12.     Sub Write2(ByVal S As String)
   13.         Console.WriteLine("2: " & S)
   14.     End Sub
   15.
   16.     Sub Main()
   17.         'Variabile D di tipo Display, ossia il nuovo tipo
   18.         'delegate appena definito all'inizio del modulo
   19.         Dim D As Display
   20.
   21.         'Inizializa D con un nuovo oggetto delegate contenente
   22.         'un riferimento al metodo Console.WriteLine
   23.         D = New Display(AddressOf Console.WriteLine)
   24.
   25.         'Invoca il metodo referenziato da D: in questo caso
   26.         'equivarrebbe a scrivere Console.WriteLine("Ciao")
   27.         D("Ciao")
   28.
   29.         'Reinizializza D, assegnandogli l'indirizzo di Write1
   30.         D = New Display(AddressOf Write1)
   31.         'è come chiamare Write1("Ciao")
   32.         D("Ciao")
   33.
   34.         'Modo alternativo per inizializzare un delegate: si omette
   35.         'New e si usa solo AddressOf. Questo genera una conversione
   36.         'implicita che dà errore di cast nel caso in cui Write1
   37.         'non sia compatibile con la signature del delegate
   38.         D = AddressOf Write2
   39.         D("Ciao")
   40.
   41.         'Notare che D può contenere metodi di istanza
   42.         '(come Console.WriteLine) e metodi statici (come Write1
   43.         'e Write2)
   44.
   45.         Console.ReadKey()
   46.     End Sub
   47. End Module

La signatur e di un delegate no n può contener e par ametr i indefiniti (Par amAr r ay) od opzionali (Optional), tuttavia i
metodi memor izzati in un oggetto di tipo delegate possono aver e par ametr i di questo tipo. Eccone un esempio:

 001. Module Module1
 002.
'Tipo delegate che può contenere riferimenti a funzioni Single
003.   'che accettino un parametro di tipo array di Single
004.   Delegate Function ProcessData(ByVal Data() As Single) As Single
005.   'Tipo delegate che può contenere riferimenti a procedure
006.   'che accettino due parametri, un array di Single e un Boolean
007.   Delegate Sub PrintData(ByVal Data() As Single, ByVal ReverseOrder As Boolean)
008.
009.   'Funzione che calcola la media di alcuni valori. Notare che
010.   'l'unico parametro è indefinito, in quanto
011.   'dichiarato come ParamArray
012.   Function CalculateAverage(ByVal ParamArray Data() As Single) As Single
013.       Dim Total As Single = 0
014.
015.       For I As Int32 = 0 To Data.Length - 1
016.            Total += Data(I)
017.       Next
018.
019.       Return (Total / Data.Length)
020.   End Function
021.
022.   'Funzione che calcola la varianza di alcuni valori. Notare che
023.   'anche in questo caso il parametro è indefinito
024.   Function CalculateVariance(ByVal ParamArray Data() As Single) As Single
025.       Dim Average As Single = CalculateAverage(Data)
026.       Dim Result As Single = 0
027.
028.       For I As Int32 = 0 To Data.Length - 1
029.            Result += (Data(I) - Average) ^ 2
030.       Next
031.
032.       Return (Result / Data.Length)
033.   End Function
034.
035.   'Procedura che stampa i valori di un array in ordine normale
036.   'o inverso. Notare che il secondo parametro è opzionale
037.   Sub PrintNormal(ByVal Data() As Single, _
038.       Optional ByVal ReverseOrder As Boolean = False)
039.       If ReverseOrder Then
040.            For I As Int32 = Data.Length - 1 To 0 Step -1
041.                 Console.WriteLine(Data(I))
042.            Next
043.       Else
044.            For I As Int32 = 0 To Data.Length - 1
045.                 Console.WriteLine(Data(I))
046.            Next
047.       End If
048.   End Sub
049.
050.   'Procedura che stampa i valori di un array nella forma:
051.   '"I+1) Data(I)"
052.   'Notare che anche in questo caso il secondo parametro
053.   'è opzionale
054.   Sub PrintIndexed(ByVal Data() As Single, _
055.       Optional ByVal ReverseOrder As Boolean = False)
056.       If ReverseOrder Then
057.            For I As Int32 = Data.Length - 1 To 0 Step -1
058.                 Console.WriteLine("{0}) {1}", Data.Length - I, Data(I))
059.            Next
060.       Else
061.            For I As Int32 = 0 To Data.Length - 1
062.                 Console.WriteLine("{0}) {1}", (I + 1), Data(I))
063.            Next
064.       End If
065.   End Sub
066.
067.   Sub Main()
068.       Dim Process As ProcessData
069.       Dim Print As PrintData
070.       Dim Data() As Single
071.       Dim Len As Int32
072.       Dim Cmd As Char
073.
074.
Console.WriteLine("Quanti valori inserire?")
 075.         Len = Console.ReadLine
 076.
 077.         ReDim Data(Len - 1)
 078.         For I As Int32 = 1 To Len
 079.              Console.Write("Inserire il valore " & I & ": ")
 080.              Data(I - 1) = Console.ReadLine
 081.         Next
 082.
 083.         Console.Clear()
 084.
 085.         Console.WriteLine("Scegliere l'operazione da eseguire: ")
 086.         Console.WriteLine("m - Calcola la media dei valori;")
 087.         Console.WriteLine("v - Calcola la varianza dei valori;")
 088.         Cmd = Console.ReadKey().KeyChar
 089.         Select Case Cmd
 090.              Case "m"
 091.                  Process = New ProcessData(AddressOf CalculateAverage)
 092.              Case "v"
 093.                  Process = New ProcessData(AddressOf CalculateVariance)
 094.              Case Else
 095.                  Console.WriteLine("Comando non valido!")
 096.                  Exit Sub
 097.         End Select
 098.         Console.WriteLine()
 099.         Console.WriteLine("Scegliere il metodo di stampa: ")
 100.         Console.WriteLine("s - Stampa i valori;")
 101.         Console.WriteLine("i - Stampa i valori con il numero ordinale a fianco.")
 102.         Cmd = Console.ReadKey().KeyChar
 103.         Select Case Cmd
 104.              Case "s"
 105.                  Print = New PrintData(AddressOf PrintNormal)
 106.              Case "i"
 107.                  Print = New PrintData(AddressOf PrintIndexed)
 108.              Case Else
 109.                  Console.WriteLine("Comando non valido!")
 110.                  Exit Sub
 111.         End Select
 112.
 113.         Console.Clear()
 114.
 115.         Console.WriteLine("Valori:")
 116.         'Eccoci arrivati al punto. Come detto prima, i delegate
 117.         'non possono definire una signature che comprenda parametri
 118.         'opzionali o indefiniti, ma si
 119.         'può aggirare questa limitazione semplicemente dichiarando
 120.         'un array di valori al posto del ParamArray (in quanto si
 121.         'tratta comunque di due vettori) e lo stesso parametro
 122.         'non opzionale al posto del parametro opzionale.
 123.         'L'inconveniente, in questo ultimo caso, è che il
 124.         'parametro, pur essendo opzionale va sempre specificato
 125.         'quando il metodo viene richiamato attraverso un oggetto
 126.         'delegate. Questo escamotage permette di aumentare la
 127.         'portata dei delegate, includendo anche metodi che
 128.         'possono essere stati scritti tempo prima in un'altra
 129.         'parte inaccessibile del codice: così
 130.         'non è necessario riscriverli!
 131.         Print(Data, False)
 132.         Console.WriteLine("Risultato:")
 133.         Console.WriteLine(Process(Data))
 134.
 135.         Console.ReadKey()
 136.     End Sub
 137.
 138. End Module




Un esempio più signific ativo
I delegate sono par ticolar mente utili per r ispar miar e spazio nel codice. Tr amite i delegate, infatti, possiamo usar e lo
stesso metodo per eseguir e più compiti differ enti. Dato che una var iabile delegate contiene un r ifr iento ad un metodo
qualsiasi, semplicemente cambiando questo r ifer imento possiamo eseguir e codici diver si r ichiamando la stessa
var iabile. E' come se potessimo "innestar e" del codice sempr e diver so su un substr ato costante. Ecco un esempio
piccolo, ma significativo:

   01. Module Module2
   02.     'Nome del file da cercare
   03.     Dim File As String
   04.
   05.     'Questo delegate referenzia una funzione che accetta un
   06.     'parametro stringa e restituisce un valore booleano
   07.     Delegate Function IsMyFile(ByVal FileName As String) As Boolean
   08.
   09.     'Funzione 1, stampa il contenuto del file a schermo
   10.     Function PrintFile(ByVal FileName As String) As Boolean
   11.         'Io.Path.GetFileName(F) restituisce solo il nome del
   12.         'singolo file F, togliendo il percorso delle cartelle
   13.         If IO.Path.GetFileName(FileName) = File Then
   14.              'IO.File.ReadAllText(F) restituisce il testo contenuto
   15.              'nel file F in una sola operazione
   16.              Console.WriteLine(IO.File.ReadAllText(FileName))
   17.              Return True
   18.         End If
   19.         Return False
   20.     End Function
   21.
   22.     'Funzione 2, copia il file sul desktop
   23.     Function CopyFile(ByVal FileName As String) As Boolean
   24.         If IO.Path.GetFileName(FileName) = File Then
   25.              'IO.File.Copy(S, D) copia il file S nel file D:
   26.              'se D non esiste viene creato, se esiste viene
   27.              'sovrascritto
   28.              IO.File.Copy(FileName, _
   29.              My.Computer.FileSystem.SpecialDirectories.Desktop & _
   30.                  "" & File)
   31.              Return True
   32.         End If
   33.         Return False
   34.     End Function
   35.
   36.     'Procedura ricorsiva che cerca il file
   37.     Function SearchFile(ByVal Dir As String, ByVal IsOK As IsMyFile) _
   38.         As Boolean
   39.         'Ottiene tutte le sottodirectory
   40.         Dim Dirs() As String = IO.Directory.GetDirectories(Dir)
   41.         'Ottiene tutti i files
   42.         Dim Files() As String = IO.Directory.GetFiles(Dir)
   43.
   44.         'Analizza ogni file per vedere se è quello cercato
   45.         For Each F As String In Files
   46.              'È il file cercato, basta cercare
   47.              If IsOK(F) Then
   48.                  'Termina la funzione e restituisce Vero, cosicché
   49.                  'anche nel for sulle cartelle si termini
   50.                  'la ricerca
   51.                  Return True
   52.              End If
   53.         Next
   54.
   55.         'Analizza tutte le sottocartelle
   56.         For Each D As String In Dirs
   57.              If SearchFile(D, IsOK) Then
   58.                  'Termina ricorsivamente la ricerca
   59.                  Return True
   60.              End If
   61.         Next
   62.     End Function
   63.
   64.     Sub Main()
   65.         Dim Dir As String
   66.
67.         Console.WriteLine("Inserire il nome file da cercare:")
   68.         File = Console.ReadLine
   69.
   70.         Console.WriteLine("Inserire la cartella in cui cercare:")
   71.         Dir = Console.ReadLine
   72.
   73.         'Cerca il file e lo scrive a schermo
   74.         SearchFile(Dir, AddressOf PrintFile)
   75.
   76.         'Cerca il file e lo copia sul desktop
   77.         SearchFile(Dir, AddressOf CopyFile)
   78.
   79.         Console.ReadKey()
   80.     End Sub
   81. End Module

Nel sor gente si vede che si usano pochissime r ighe per far compier e due oper azioni molto differ enti alla stessa
pr ocedur a. In altr e condizioni, un aspir ante pr ogr ammator e che non conoscesse i delegate avr ebbe scr itto due
pr ocedur e inter e, spr ecando più spazio, e condannandosi, inoltr e, a r iscr iver e la stessa cosa per ogni futur a var iante.
A35. I Delegate Multicast

Al contr ar io di un delegate semplice, un delegate multicast può contener e r ifer imenti a più metodi insieme, pur ché
della stessa categor ia e con la stessa signatur e. Dato che il costr uttor e è sempr e lo stesso e accetta un solo par ametr o,
non è possibile cr ear e delegate multicast in fase di inizializzazione. L'unico modo per far lo è r ichiamar e il metodo
statico Combine della classe System.Delegate (ossia la classe base di tutti i delegate). Combine espone anche un over load
che per mette di unir e molti delegate alla volta, specificandoli tr amite un Par amAr r ay. Dato che un delegate multicast
contiene più r ifer imenti a metodi distinti, si par la di inv o catio n list (lista di invocazione) quando ci si r ifer isce
all'insieme di tutti i metodi memor izzati in un delegate multicast. Ecco un semplice esempio:

   01. Module Module2
   02.     'Vedi esempio precedente
   03.     Sub Main()
   04.         Dim Dir As String
   05.         Dim D As IsMyFile
   06.
   07.         Console.WriteLine("Inserire il nome file da cercare:")
   08.         File = Console.ReadLine
   09.
   10.         Console.WriteLine("Inserire la cartella in cui cercare:")
   11.         Dir = Console.ReadLine
   12.
   13.         'Crea un delegate multicast, unendo PrintFile e CopyFile.
   14.         'Da notare che in questa espressione è necessario usare
   15.         'delle vere e proprie variabili delegate, poiché
   16.         'l'operatore AddressOf da solo non è valido in questo caso
   17.         D = System.Delegate.Combine(New IsMyFile(AddressOf PrintFile), _
   18.              New IsMyFile(AddressOf CopyFile))
   19.         'Per la cronaca, Combine è un metodo factory
   20.
   21.         'Ora il file trovato viene sia visualizzato che copiato
   22.         'sul desktop
   23.         SearchFile(Dir, D)
   24.
   25.         'Se si vuole rimuovere uno o più riferimenti a metodi del
   26.         'delegate multicast si deve utilizzare il metodo statico Remove:
   27.         D = System.Delegate.Remove(D, New IsMyFile(AddressOf CopyFile))
   28.         'Ora D farà visualizzare solamente il file trovato
   29.
   30.         Console.ReadKey()
   31.     End Sub
   32. End Module

La funzione Combine, tuttavia, nasconde molte insidie. Infatti, essendo un metodo factor y della classe System.Delegate,
come abbiamo detto nel capitolo r elativo ai metodi factor y, r estituisce un oggetto di tipo System.Delegate.
Nell'esempio, noi abbiamo potuto assegnar e il valor e r estituito da Combine a D, che è di tipo IsMyFile, per chè
solitamente le opzioni di compilazione per mettono di eseguir e conver sioni implicite di questo tipo - ossia Option Str ict
è solitamente impostato su Off (per ulter ior i infor mazioni, veder e il capitolo sulle opzioni di compilazione). Come
abbiamo detto nel capitolo sulle conver sioni, assegnar e il valor e di una classe der ivata a una classe base è lecito, poichè
nel passaggio da una all'altr a non si per de alcun dato, ma si gener elizza soltanto il valor e r appr esentato; eseguir e il
passaggio inver so, invece, ossia assegnar e una classe base a una der ivata, può r isultar e in qualche str ano er r or e
per chè i membr i in più della classe der ivata sono vuoti. Nel caso dei delegate, che sono oggetti immutabili, e che quindi
non espongono pr opr ietà modificabili, questo non è un pr oblema, ma il compilator e questo non lo sa. Per esser e sicur i,
è meglio utilizzar e un oper ator e di cast come Dir ectCast:

    1. DirectCast(System.Delegate.Combine(A, B), IsMyFile)

N.B.: Quando un delegate multicast contiene delle funzioni e viene r ichiamato, il valor e r estituito è quello della pr ima
funzione memor izzata.
Ecco or a un altr o esempio molto ar ticolato sui delegate multicast:

 001.    'Questo esempio si basa completamente sulla manipolazione
 002.    'di file e cartelle, argomento non ancora affrontato. Se volete,
 003.    'potete dare uno sguardo ai capitoli relativi nelle parti
 004.    'successive della guida, oppure potete anche limitarvi a leggere
 005.    'i commenti, che spiegano tutto ciò che accade.
 006.    Module Module1
 007.        'In questo esempio eseguiremo delle operazioni su file con i delegate.
 008.        'Nel menù sarà possibile scegliere quali operazioni
 009.        'eseguire (una o tutte insieme) e sotto quali condizioni modificare
 010.        'un file.
 011.        'Il delegate FileFilter rappresenta una funzione che restituisce
 012.        'True se la condizione è soddisfatta. Le condizioni
 013.        'sono racchiuse in un delegate multicast che contiene più
 014.        'funzioni di questo tipo
 015.        Delegate Function FileFilter(ByVal FileName As String) As Boolean
 016.        'Il prossimo delegate rappresenta un'operazione su un file
 017.        Delegate Sub MassFileOperation(ByVal FileName As String)
 018.        'AskForData è un delegate del tipo più semplice.
 019.        'Servirà per reperire le informazioni necessarie ad
 020.        'eseguire le operazioni (ad esempio, se si sceglie di copiare
 021.        'tutti i file di una cartella, si dovrà anche scegliere
 022.        'dove copiare questi file).
 023.        Delegate Sub AskForData()
 024.
 025.         'Queste variabili globali rappresentano le informazioni necesarie
 026.         'per lo svolgimento delle operazioni o la verifica delle condizioni.
 027.
 028.         'Stringa di formato per rinominare i file
 029.         Dim RenameFormat As String
 030.         'Posizione di un file nella cartella
 031.         Dim FileIndex As Int32
 032.         'Directory in cui copiare i file
 033.         Dim CopyDirectory As String
 034.         'File in cui scrivere. Il tipo StreamWriter permette di scrivere
 035.         'facilmente stringhe su un file usando WriteLine come in Console
 036.         Dim LogFile As IO.StreamWriter
 037.         'Limitazioni sulla data di creazione del file
 038.         Dim CreationDateFrom, CreationDateTo As Date
 039.         'Limitazioni sulla data di ultimo accesso al file
 040.         Dim LastAccessDateFrom, LastAccessDateTo As Date
 041.         'Limitazioni sulla dimensione
 042.         Dim SizeFrom, SizeTo As Int64
 043.
 044.         'Rinomina un file
 045.         Sub Rename(ByVal Path As String)
 046.             'Ne prende il nome semplice, senza estensione
 047.             Dim Name As String = IO.Path.GetFileNameWithoutExtension(Path)
 048.             'Apre un oggetto contenente le informazioni sul file
 049.             'di percorso Path
 050.             Dim Info As New IO.FileInfo(Path)
 051.
 052.              'Formatta il nome secondo la stringa di formato RenameFormat
 053.              Name = String.Format(RenameFormat, _
 054.                      Name, FileIndex, Info.Length, Info.LastAccessTime, Info.CreationTime)
 055.              'E aggiunge ancora l'estensione al nome modificato
 056.              Name &= IO.Path.GetExtension(Path)
 057.              'Copia il vecchio file nella stessa cartella, ma con il nuovo nome
 058.              IO.File.Copy(Path, IO.Path.GetDirectoryName(Path) & "" & Name)
 059.              'Elimina il vecchio file
 060.              IO.File.Delete(Path)
 061.
 062.             'Aumenta l'indice di uno
 063.             FileIndex += 1
 064.         End Sub
 065.
 066.         'Funzione che richiede i dati necessari per far funzionare
 067.         'il metodo Rename
 068.         Sub InputRenameFormat()
 069.
Console.WriteLine("Immettere una stringa di formato valida per rinominare i file.")
070.       Console.WriteLine("I parametri sono:")
071.       Console.WriteLine("0 = Nome originale del file;")
072.       Console.WriteLine("1 = Posizione del file nella cartella, in base 0;")
073.       Console.WriteLine("2 = Dimensione del file, in bytes;")
074.       Console.WriteLine("3 = Data dell'ultimo accesso;")
075.       Console.WriteLine("4 = Data di creazione.")
076.       RenameFormat = Console.ReadLine
077.   End Sub
078.
079.   'Elimina un file di percorso Path
080.   Sub Delete(ByVal Path As String)
081.       IO.File.Delete(Path)
082.   End Sub
083.
084.   'Copia il file da Path alla nuova cartella
085.   Sub Copy(ByVal Path As String)
086.       IO.File.Copy(Path, CopyDirectory & "" & IO.Path.GetFileName(Path))
087.   End Sub
088.
089.   'Richiede una cartella valida in cui copiare i file. Se non esiste,
090.   la crea
091.   Sub InputCopyDirectory()
092.       Console.WriteLine("Inserire una cartella valida in cui copiare i file:")
093.       CopyDirectory = Console.ReadLine
094.       If Not IO.Directory.Exists(CopyDirectory) Then
095.           IO.Directory.CreateDirectory(CopyDirectory)
096.       End If
097.   End Sub
098.
099.   'Scrive il nome del file sul file aperto
100.   Sub Archive(ByVal Path As String)
101.       LogFile.WriteLine(IO.Path.GetFileName(Path))
102.   End Sub
103.
104.   'Chiede il nome di un file su cui scrivere tutte le informazioni
105.   Sub InputLogFile()
106.       Console.WriteLine("Inserire il percorso del file su cui scrivere:")
107.       LogFile = New IO.StreamWriter(Console.ReadLine)
108.   End Sub
109.
110.   'Verifica che la data di creazione del file cada tra i limiti fissati
111.   Function IsCreationDateValid(ByVal Path As String) As Boolean
112.       Dim Info As New IO.FileInfo(Path)
113.       Return (Info.CreationTime >= CreationDateFrom) And (Info.CreationTime >=
              CreationDateTo)
114.   End Function
115.
116.   'Richiede di immettere una limitazione temporale per considerare
117.   'solo certi file
118.   Sub InputCreationDates()
119.       Console.WriteLine("Verranno considerati solo i file con data di creazione:")
120.       Console.Write("Da: ")
121.       CreationDateFrom = Date.Parse(Console.ReadLine)
122.       Console.Write("A: ")
123.       CreationDateTo = Date.Parse(Console.ReadLine)
124.   End Sub
125.
126.   'Verifica che la data di ultimo accesso al file cada tra i limiti fissati
127.   Function IsLastAccessDateValid(ByVal Path As String) As Boolean
128.       Dim Info As New IO.FileInfo(Path)
129.       Return (Info.LastAccessTime >= LastAccessDateFrom) And (Info.LastAccessTime >=
              LastAccessDateTo)
130.   End Function
131.
132.   'Richiede di immettere una limitazione temporale per considerare
133.   'solo certi file
134.   Sub InputLastAccessDates()
135.       Console.WriteLine("Verranno considerati solo i file con data di creazione:")
136.       Console.Write("Da: ")
137.       LastAccessDateFrom = Date.Parse(Console.ReadLine)
138.       Console.Write("A: ")
139.
LastAccessDateTo = Date.Parse(Console.ReadLine)
140.   End Sub
141.
142.   'Verifica che la dimensione del file sia coerente coi limiti fissati
143.   Function IsSizeValid(ByVal Path As String) As Boolean
144.       Dim Info As New IO.FileInfo(Path)
145.       Return (Info.Length >= SizeFrom) And (Info.Length >= SizeTo)
146.   End Function
147.
148.   'Richiede di specificare dei limiti dimensionali per i file
149.   Sub InputSizeLimit()
150.       Console.WriteLine("Verranno considerati solo i file con dimensione compresa:")
151.       Console.Write("Tra (bytes):")
152.       SizeFrom = Console.ReadLine
153.       Console.Write("E (bytes):")
154.       SizeTo = Console.ReadLine
155.   End Sub
156.
157.   'Classe che rappresenta un'operazione eseguibile su file
158.   Class Operation
159.       Private _Description As String
160.       Private _Execute As MassFileOperation
161.       Private _RequireData As AskForData
162.       Private _Enabled As Boolean
163.
164.       'Descrizione
165.       Public Property Description() As String
166.           Get
167.               Return _Description
168.           End Get
169.           Set(ByVal value As String)
170.               _Description = value
171.           End Set
172.       End Property
173.
174.       'Variabile che contiene l'oggetto delegate associato
175.       'a questa operazione, ossia un riferimento a una delle Sub
176.       'definite poco sopra
177.       Public Property Execute() As MassFileOperation
178.           Get
179.               Return _Execute
180.           End Get
181.           Set(ByVal value As MassFileOperation)
182.               _Execute = value
183.           End Set
184.       End Property
185.
186.       'Variabile che contiene l'oggetto delegate che serve
187.       'per reperire informazioni necessarie ad eseguire
188.       'l'operazione, ossia un riferimento a una delle sub
189.       'di Input definite poco sopra. E' Nothing quando
190.       'non serve nessun dato ausiliario (come nel caso
191.       'di Delete)
192.       Public Property RequireData() As AskForData
193.           Get
194.               Return _RequireData
195.           End Get
196.           Set(ByVal value As AskForData)
197.               _RequireData = value
198.           End Set
199.       End Property
200.
201.       'Determina se l'operazione va eseguita oppure no
202.       Public Property Enabled() As Boolean
203.           Get
204.               Return _Enabled
205.           End Get
206.           Set(ByVal value As Boolean)
207.               _Enabled = value
208.           End Set
209.       End Property
210.
211.
Sub New(ByVal Description As String, _
212.           ByVal ExecuteMethod As MassFileOperation, _
213.           ByVal RequireDataMethod As AskForData)
214.           Me.Description = Description
215.           Me.Execute = ExecuteMethod
216.           Me.RequireData = RequireDataMethod
217.           Me.Enabled = False
218.       End Sub
219.   End Class
220.
221.   'Classe che rappresenta una condizione a cui sottoporre
222.   'i file nella cartella: verranno elaborati solo quelli che
223.   'soddisfano tutte le condizioni
224.   Class Condition
225.       Private _Description As String
226.       Private _Verify As FileFilter
227.       Private _RequireData As AskForData
228.       Private _Enabled As Boolean
229.
230.       Public Property Description() As String
231.           Get
232.               Return _Description
233.           End Get
234.           Set(ByVal value As String)
235.               _Description = value
236.           End Set
237.       End Property
238.
239.       'Contiene un oggetto delegate associato a una delle
240.       'precedenti funzioni
241.       Public Property Verify() As FileFilter
242.           Get
243.               Return _Verify
244.           End Get
245.           Set(ByVal value As FileFilter)
246.               _Verify = value
247.           End Set
248.       End Property
249.
250.       Public Property RequireData() As AskForData
251.           Get
252.               Return _RequireData
253.           End Get
254.           Set(ByVal value As AskForData)
255.               _RequireData = value
256.           End Set
257.       End Property
258.
259.       Public Property Enabled() As Boolean
260.           Get
261.               Return _Enabled
262.           End Get
263.           Set(ByVal value As Boolean)
264.               _Enabled = value
265.           End Set
266.       End Property
267.
268.       Sub New(ByVal Description As String, _
269.           ByVal VerifyMethod As FileFilter, _
270.           ByVal RequireDataMethod As AskForData)
271.           Me.Description = Description
272.           Me.Verify = VerifyMethod
273.           Me.RequireData = RequireDataMethod
274.       End Sub
275.   End Class
276.
277.   Sub Main()
278.       'Contiene tutte le operazioni da eseguire: sarà, quindi, un
279.       'delegate multicast
280.       Dim DoOperations As MassFileOperation
281.       'Contiene tutte le condizioni da verificare
282.       Dim VerifyConditions As FileFilter
283.
'Indica la cartella di cui analizzare i file
284.   Dim Folder As String
285.   'Hashtable di caratteri-Operation o carattri-Condition. Il
286.   'carattere indica quale tasto è necessario
287.   'premere per attivare/disattivare l'operazione/condizione
288.   Dim Operations As New Hashtable
289.   Dim Conditions As New Hashtable
290.   Dim Cmd As Char
291.
292.   'Aggiunge le operazioni esistenti. La 'c' messa dopo la stringa
293.   'indica che la costante digitata è un carattere e non una
294.   'stringa. Il sistema non riesce a distinguere tra stringhe di
295.   lunghezza 1 e caratteri, al contrario di come accade in C
296.   With Operations
297.       .Add("r"c, New Operation("Rinomina tutti i file nella cartella;", _
298.                New MassFileOperation(AddressOf Rename), _
299.                New AskForData(AddressOf InputRenameFormat)))
300.       .Add("c"c, New Operation("Copia tutti i file nella cartella in un'altra
              cartella;", _
301.                New MassFileOperation(AddressOf Copy), _
302.                New AskForData(AddressOf InputCopyDirectory)))
303.       .Add("a"c, New Operation("Scrive il nome di tutti i file nella cartella su un
              file;", _
304.                New MassFileOperation(AddressOf Archive), _
305.                New AskForData(AddressOf InputLogFile)))
306.       .Add("d"c, New Operation("Cancella tutti i file nella cartella;", _
307.                New MassFileOperation(AddressOf Delete), _
308.                Nothing))
309.   End With
310.
311.   'Aggiunge le condizioni esistenti
312.   With Conditions
313.       .Add("r"c, New Condition("Seleziona i file da elaborare in base alla data di
              creazione;", _
314.                New FileFilter(AddressOf IsCreationDateValid), _
315.                New AskForData(AddressOf InputCreationDates)))
316.       .Add("l"c, New Condition("Seleziona i file da elaborare in base all'ultimo
              accesso;", _
317.                New FileFilter(AddressOf IsLastAccessDateValid), _
318.                New AskForData(AddressOf InputLastAccessDates)))
319.       .Add("s"c, New Condition("Seleziona i file da elaborare in base alla dimensione;",
              _
320.                New FileFilter(AddressOf IsSizeValid), _
321.                New AskForData(AddressOf InputSizeLimit)))
322.   End With
323.
324.   Console.WriteLine("Modifica in massa di file ---")
325.   Console.WriteLine()
326.
327.   Do
328.       Console.WriteLine("Immetti il percorso della cartella su cui operare:")
329.       Folder = Console.ReadLine
330.   Loop Until IO.Directory.Exists(Folder)
331.
332.   Do
333.        Console.Clear()
334.        Console.WriteLine("Premere la lettera corrispondente per selezionare la voce.")
335.        Console.WriteLine("Premere 'e' per procedere.")
336.        Console.WriteLine()
337.        For Each Key As Char In Operations.Keys
338.            'Disegna sullo schermo una casella di spunta, piena:
339.            ' [X]
340.            'se l'operazione è attivata, altrimenti vuota:
341.            ' [ ]
342.            Console.Write("[")
343.            If Operations(Key).Enabled = True Then
344.                 Console.Write("X")
345.            Else
346.                 Console.Write(" ")
347.            End If
348.            Console.Write("] ")
349.            'Scrive quindi il carattere da premere e vi associa la descrizione
350.
Console.Write(Key)
351.           Console.Write(" - ")
352.           Console.WriteLine(Operations(Key).Description)
353.       Next
354.       Cmd = Console.ReadKey().KeyChar
355.       If Operations.ContainsKey(Cmd) Then
356.            Operations(Cmd).Enabled = Not Operations(Cmd).Enabled
357.       End If
358.   Loop Until Cmd = "e"c
359.
360.   Do
361.       Console.Clear()
362.       Console.WriteLine("Premere la lettera corrispondente per selezionare la voce.")
363.       Console.WriteLine("Premere 'e' per procedere.")
364.       Console.WriteLine()
365.       For Each Key As Char In Conditions.Keys
366.            Console.Write("[")
367.            If Conditions(Key).Enabled = True Then
368.                 Console.Write("X")
369.            Else
370.                 Console.Write(" ")
371.            End If
372.            Console.Write("] ")
373.            Console.Write(Key)
374.            Console.Write(" - ")
375.            Console.WriteLine(Conditions(Key).Description)
376.       Next
377.       Cmd = Console.ReadKey().KeyChar
378.       If Conditions.ContainsKey(Cmd) Then
379.            Conditions(Cmd).Enabled = Not Conditions(Cmd).Enabled
380.       End If
381.   Loop Until Cmd = "e"c
382.
383.   Console.Clear()
384.   Console.WriteLine("Acquisizione informazioni")
385.   Console.WriteLine()
386.
387.   'Cicla su tutte le operazioni presenti nell'Hashtable.
388.   For Each Op As Operation In Operations.Values
389.        'Se l'operazione è attivata...
390.        If (Op.Enabled) Then
391.            'Se richiede dati ausiliari, invoca il delegate memorizzato
392.            'nella proprietà RequireData. Invoke è un metodo
393.            'di istanza che invoca i metodi contenuti nel delegate.
394.            'Si può anche scrivere:
395.            ' Op.RequireData()()
396.            'Dove la prima coppia di parentesi indica che la proprietà
397.            'non è indicizzata e la seconda, in questo caso, specifica
398.            'che il metodo sotteso dal delegate non richiede parametri.
399.            'È più comprensibile la prima forma
400.            If Op.RequireData IsNot Nothing Then
401.                 Op.RequireData.Invoke()
402.            End If
403.            'Se DoOperations non contiene ancora nulla, vi inserisce Op.Execute
404.            If DoOperations Is Nothing Then
405.                 DoOperations = Op.Execute
406.            Else
407.                 'Altrimenti, combina gli oggetti delegate già memorizzati
408.                 'con il nuovo
409.                 DoOperations = System.Delegate.Combine(DoOperations, Op.Execute)
410.            End If
411.        End If
412.   Next
413.
414.   For Each C As Condition In Conditions.Values
415.       If C.Enabled Then
416.           If C.RequireData IsNot Nothing Then
417.                C.RequireData.Invoke()
418.           End If
419.           If VerifyConditions Is Nothing Then
420.                VerifyConditions = C.Verify
421.           Else
422.
VerifyConditions = System.Delegate.Combine(VerifyConditions, C.Verify)
 423.                  End If
 424.              End If
 425.         Next
 426.
 427.         FileIndex = 0
 428.         For Each File As String In IO.Directory.GetFiles(Folder)
 429.              'Ok indica se il file ha passato le condizioni
 430.              Dim Ok As Boolean = True
 431.              'Se ci sono condizioni da applicare, le verifica
 432.              If VerifyConditions IsNot Nothing Then
 433.                  'Dato che nel caso di delegate multicast contenenti
 434.                  'rifermenti a funzione, il valore restituito è
 435.                  'solo quello della prima funzione e a noi interessano
 436.                  '<b>tutti</b> i valori restituiti, dobbiamo enumerare
 437.                  'ogni singolo oggetto delegate presente nel
 438.                  'delegate multicast e invocarlo singolarmente.
 439.                  'Ci viene in aiuto il metodo di istanza GetInvocationList,
 440.                  'che restituisce un array di delegate singoli.
 441.                  For Each C As FileFilter In VerifyConditions.GetInvocationList()
 442.                       'Tutte le condizioni attive devono essere verificate,
 443.                       'quindi bisogna usare un And
 444.                       Ok = Ok And C(File)
 445.                  Next
 446.              End If
 447.              'Se le condizioni sono verificate, esegue le operazioni
 448.              If Ok Then
 449.                  Try
 450.                       DoOperations(File)
 451.                  Catch Ex As Exception
 452.                       Console.WriteLine("Impossibile eseguire l'operazione: " & Ex.Message)
 453.                  End Try
 454.              End If
 455.         Next
 456.         'Chiude il file di log se era aperto
 457.         If LogFile IsNot Nothing Then
 458.              LogFile.Close()
 459.         End If
 460.
 461.         Console.WriteLine("Operazioni eseguite con successo!")
 462.         Console.ReadKey()
 463.     End Sub
 464.
 465. End Module

Questo esempio molto ar tificioso è solo un assaggio delle potenzialità dei delegate (noter ete che ci sono anche molti
conflitti, ad esempio se si seleziona sia copia che elimina, i file potr ebber o esser e cancellati pr ima della copia a seconda
dell'or dine di invocazione). Vedr emo fr a poco come utilizzar e alcuni delegate piuttosto comuni messi a disposizione dal
Fr amew or k, e scopr ir emo nella sezione B che i delegate sono il meccanismo fondamentale alla base di tutto il sistema
degli ev enti.




Alc uni membri importanti per i delegate multic ast
La classe System.Delegate espone alcuni metodi statici pubblici, molti dei quali sono davver o utili quando si tr atta di
delegate multicast. Eccone una br eve lista:

       Combine(A, B) o Combine(A, B, C, ...) : fonde insieme più delegate per cr ear e un unico delegate multicast
       invocando il quale vengono invocati tutti i metodi in esso contenuti;
       GetInvocationList() : funzione d'istanza che r estituisce un ar r ay di oggetti di tipo System.Delegate, i quali
       r appr esentano i singoli delegate che sono stati memor izzati nell'unica var iabile
       Remove(A, B) : r imuove l'oggetto delegate B dalla invocation list di A (ossia dalla lista di tutti i singoli delegate
       memor izzati in A). Si suppone che A sia multicast. Se anche B è multicast, solo l'ultimo elemento dell'invocation
       list di B viene r imosso da quella di A
RemoveAll(A, B) : r imuove tutte le occor r enze degli elementi pr esenti nell'invocation list di B da quella di A. Si
suppone che sia A che B siano multicast
A36. Classi Astratte, Sigillate e Parziali

Classi Astratte
Le classi astr atte sono speciali classi che esistono con il solo scopo di esser e er editate da altr e classi: non possono
esser e usate da sole, non espongono costr uttor i e alcuni lor o metodi sono pr ivi di un cor po. Queste sono
car atter istiche molto peculiar i, e anche abbastanza str ane, che, tuttavia, nascondono un potenziale segr eto. Se
qualcuno dei miei venticinque lettor i avesse avuto l'occasione di osser var e qualcuno dei miei sor genti, avr ebbe notato
che in più di un occasione ho fatto uso di classi mar cate con la keyw or d MustInher it. Questa è la par ola r iser vata che si
usa per r ender e as tratta una classe. L'utilizzo pr incipale delle classi astr atte è quello di for nir e un o s cheletro o un a
bas e di as trazion e per altr e classi. Pr endiamo come esempio uno dei miei pr ogr ammi, che potete tr ovar e nella
sezione dow nload, Totem Char ting: ci r ifer ir emo al file Char t.vb. In questo sor gente, la pr ima classe che incontr ate è
definita come segue:

    1. <Serializable()> _
    2. Public MustInherit Class Chart

Per or a lasciamo per der e ciò che viene compr eso tr a le par entesi angolar i e focalizziamoci sulla dichiar azione nuda e
cr uda. Quella che avete visto è pr opr io la dichiar azione di una classe astr atta, dove MustInher it significa appunto "deve
er editar e", come r ipor tato nella definizione poco sopr a. Char t r appr esenta un gr afico: espone delle pr opr ietà
(Pr oper ties, Type, Sur face, Plane, ...) e un paio di metodi Pr otected. Sar ete d'accor do con me nell'asser ir e che ogni
gr afico può aver e una legenda e può contemplar e un insieme di dati limitato per cui esista un massimo: ne concludiamo
che i due metodi in questione ser vono a tutti i gr afici ed è cor r etto che siano stati definiti all'inter no del cor po di
Char t. Ma or a andiamo un po' più in su e tr oviamo questa singolar e dichiar azione di metodo:

    1. Public MustOverride Sub Draw()

Non c'è il cor po del metodo! Aiuto! L'hanno r ubato! No... Si dà il caso che nelle classi astr atte possano esister e anche
metodi astr atti, ossia che devono esser e per for za r idefiniti tr amite polimor fismo nelle classi der ivate. E questo è
abbastanza semplice da capir e: un gr afico deve poter esser e disegnato, quindi ogni oggetto gr afico deve espor r e il
metodo Dr aw , ma c'è un piccolo inconveniente. Dato che non esiste un solo tipo di gr afico - ce ne sono molti, e nel
codice di Totem Char ting vengono contemplati solo gli istogr ammi, gli ar eaogr ammi e i gr afici a disper sione - non
possiamo saper e a pr ior i che codice dovr emmo usar e per effettuar e il r ender ing (ossia per disegnar e ciò che ser ve).
Sappiamo, per ò, che dovr emo disegnar e qualcosa: allor a lasciamo il compito di definir e un codice adeguato alle classi
der ivate (nella fattispecie, Histogr am, PieChar t, LinesChar t, Disper sionChar t). Questo è pr opr io l'utilizzo delle classi
astr atte: definir e un ar chetipo, uno schema, sulla base del quale le classi che lo er editer anno dovr anno modellar e il
pr opr io compor tamento. Altr a osser vazione: le classi astr atte, come dice il nome stesso, sono utilizzate per
r appr esentar e concetti astr atti, che non possono concr etamente esser e istanziati: ad esempio, non ha senso un
oggetto di tipo Char t, per chè non esiste un gr afico gener ico pr ivo di qualsiasi car atter istica, ma esiste solo declinato
in una delle altr e for me sopr a r ipor tate. Natur almente, valgono ancor a tutte le r egole r elative agli specificator i di
accesso e all'er editar ietà e sono utilizzabili tutti i meccanismi già illustr ati, compr eso l'over loading; infatti, ho
dichiar ato due metodi Pr otected per chè ser vir anno alle classi der ivate. Inoltr e, una classe astr atta può anche
er editar e da un'altr a classe astr atta: in questo caso, tutti i metodi mar cati con MustOver r ide dovr anno subir e una di
queste sor ti:

       Esser e modificati tr amite polimor fismo, definendone, quindi, il cor po;
       Esser e r idichiar ati MustOver r ide, r imandandone ancor a la definizione.

Nel secondo caso, si r imanda ancor a la definizione di un cor po valido alla "discendenza", ma c'è un piccolo ar tifizio da
adottar e: eccone una dimostr azione nel pr ossimo esempio:

 001. Module Module1
 002.
 003.     'Classe astratta che rappresenta un risolutore di equazioni.
 004.     'Dato che di equazioni ce ne possono essere molte tipologie
 005.     'differenti, non ha senso rendere questa classe istanziabile.
 006.     'Provando a scrivere qualcosa come:
 007.     ' Dim Eq As New EquationSolver()
 008.     'Vi verrà comunicato un errore, in quanto le classi
 009.     'astratte sono per loro natura non istanziabili
 010.     MustInherit Class EquationSolver
 011.         'Per lo stesso discorso fatto prima, se non conosciamo come
 012.         'è fatta l'equazione che questo tipo contiene non
 013.         'possiamo neppure tentare di risolverla. Perciò
 014.         'ci limitiamo a dichiarare una funzione Solve come MustOverride.
 015.         'Notate che il tipo restituito è un array di Single,
 016.         'in quanto le soluzioni saranno spesso più di una.
 017.         Public MustOverride Function Solve() As Single()
 018.     End Class
 019.
 020.     'La prossima classe rappresenta un risolutore di equazioni
 021.     'polinomiali. Dato che la tipologia è ben definita,
 022.     'avremmo potuto anche <i>non</i> rendere astratta la classe
 023.     'e, nella funzione Solve, utilizzare un Select Case per
 024.     'controllare il grado dell'equazione. Ad ogni modo, è
 025.     'utile vedere come si comporta l'erediterietà attraverso
 026.     'più classi astratte.
 027.     'Inoltre, ci ritornerà molto utile in seguito disporre
 028.     'di questa classe astratta intermedia
 029.     MustInherit Class PolynomialEquationSolver
 030.         Inherits EquationSolver
 031.
 032.         Private _Coefficients() As Single
 033.
 034.         'Array di Single che contiene i coefficienti dei
 035.         'termini di i-esimo grado all'interno dell'equazione.
 036.         'L'elemento 0 dell'array indica il coefficiente del
 037.         'termine a grado massimo.
 038.         Public Property Coefficients() As Single()
 039.              Get
 040.                  Return _Coefficients
 041.              End Get
 042.              Set(ByVal value As Single())
 043.                  _Coefficients = value
 044.              End Set
 045.         End Property
 046.
 047.         'Ecco quello a cui volevo arrivare. Se un metodo astratto
 048.         'lo si vuole mantenere tale anche nella classe derivata,
 049.         'non basta scrivere:
 050.         ' MustOverride Function Solve() As Single()
 051.         'Percè in questo caso verrebbe interpretato come
 052.         'un membro che non c'entra niente con MyBase.Solve,
 053.         'e si genererebbe un errore in quanto stiamo tentando
 054.         'di dichiarare un nuovo membro con lo stesso nome
 055.         'di un membro della classe base.
 056.         'Per questo motivo, dobbiamo comunque usare il polimorfismo
 057.         'come se si trattasse di un normale metodo e dichiararlo
 058.         'Overrides. In aggiunta a questo, deve anche essere
 059.         'astratto, e perciò aggiungiamo MustOverride:
 060.         Public MustOverride Overrides Function Solve() As Single()
 061.
 062.         'Anche in questo caso usiamo il polimorfismo, ma ci riferiamo
 063.         'alla semplice funzione ToString, derivata dalla classe base
 064.         'di tutte le entità esistenti, System.Object.
 065.         'Questa si limita a restituire una stringa che rappresenta
 066.         'l'equazione a partire dai suoi coefficienti. Ad esempio:
 067.         ' 3x^2 + 2x^1 + 4x^0 = 0
 068.         'Potete modificare il codice per eliminare le forme ridondanti
 069.         'x^1 e x^0.
 070.         Public Overrides Function ToString() As String
 071.
Dim Result As String = ""
072.
073.           For I As Int16 = 0 To Me.Coefficients.Length - 1
074.                If I > 0 Then
075.                    Result &= " + "
076.                End If
077.                Result &= String.Format("{0}x^{1}", _
078.                    Me.Coefficients(I), Me.Coefficients.Length - 1 - I)
079.           Next
080.
081.           Result &= " = 0"
082.
083.           Return Result
084.       End Function
085.   End Class
086.
087.   'Rappresenta un risolutore di equazioni non polinomiali.
088.   'La classe non è astratta, ma non presenta alcun codice.
089.   'Per risolvere questo tipo di equazioni, è necessario
090.   'sapere qualche cosa in più rispetto al punto in cui siamo
091.   'arrivati, perciò mi limiterò a lasciare in bianco
092.   Class NonPolynomialEquationSolver
093.       Inherits EquationSolver
094.
095.       Public Overrides Function Solve() As Single()
096.           Return Nothing
097.       End Function
098.   End Class
099.
100.   'Rappresenta un risolutore di equazioni di primo grado. Eredita
101.   'da PolynomialEquationSolver poichè, ovviamente, si
102.   'tratta di equazioni polinomiali. In più, definisce
103.   'le proprietà a e b che sono utili per inserire i
104.   'coefficienti. Infatti, l'equazione standard è:
105.   ' ax + b = 0
106.   Class LinearEquationSolver
107.       Inherits PolynomialEquationSolver
108.
109.       Public Property a() As Single
110.           Get
111.               Return Me.Coefficients(0)
112.           End Get
113.           Set(ByVal value As Single)
114.               Me.Coefficients(0) = value
115.           End Set
116.       End Property
117.
118.       Public Property b() As Single
119.           Get
120.               Return Me.Coefficients(1)
121.           End Get
122.           Set(ByVal value As Single)
123.               Me.Coefficients(1) = value
124.           End Set
125.       End Property
126.
127.       'Sappiamo già quanti sono i coefficienti, dato
128.       'che si tratta di equazioni lineari, quindi ridimensioniamo
129.       'l'array il prima possibile.
130.       Sub New()
131.           ReDim Me.Coefficients(1)
132.       End Sub
133.
134.       'Funzione Overrides che sovrascrive il metodo astratto della
135.       'classe base. Avrete notato che quando scrivete:
136.       ' Inherits PolynomialEquationSolver
137.       'e premete invio, questa funzione viene aggiunta automaticamente
138.       'al codice. Questa è un'utile feature dell'ambiente
139.       'di sviluppo
140.       Public Overrides Function Solve() As Single()
141.           If a <> 0 Then
142.               Return New Single() {-b / a}
143.
Else
144.               Return Nothing
145.           End If
146.       End Function
147.   End Class
148.
149.   'Risolutore di equazioni di secondo grado:
150.   ' ax<sup>2</sup> + bx + c = 0
151.   Class QuadraticEquationSolver
152.       Inherits LinearEquationSolver
153.
154.       Public Property c() As Single
155.           Get
156.               Return Me.Coefficients(2)
157.           End Get
158.           Set(ByVal value As Single)
159.               Me.Coefficients(2) = value
160.           End Set
161.       End Property
162.
163.       Sub New()
164.           ReDim Me.Coefficients(2)
165.       End Sub
166.
167.       Public Overrides Function Solve() As Single()
168.           If b ^ 2 - 4 * a * c >= 0 Then
169.                Return New Single() { _
170.                    (-b - Math.Sqrt(b ^ 2 - 4 * a * c)) / 2, _
171.                    (-b + Math.Sqrt(b ^ 2 - 4 * a * c)) / 2}
172.           Else
173.                Return Nothing
174.           End If
175.       End Function
176.   End Class
177.
178.   'Risolutore di equazioni di grado superiore al secondo. So
179.   'che avrei potuto inserire anche una classe relativa
180.   'alle cubiche, ma dato che si tratta di un esempio, vediamo
181.   'di accorciare il codice...
182.   'Comunque, dato che non esiste formula risolutiva per
183.   'le equazioni di grado superiore al quarto (e già,
184.   'ci mancava un'altra classe!), usiamo in questo caso
185.   'un semplice ed intuitivo metodo di approssimazione degli
186.   'zeri, il metodo dicotomico o di bisezione (che vi può
187.   'essere utile per risolvere un esercizio dell'eserciziario)
188.   Class HighDegreeEquationSolver
189.       Inherits PolynomialEquationSolver
190.
191.       Private _Epsilon As Single
192.       Private _IntervalLowerBound, _IntervalUpperBound As Single
193.
194.       'Errore desiderato: l'algoritmo si fermerà una volta
195.       'raggiunta una precisione inferiore a Epsilon
196.       Public Property Epsilon() As Single
197.           Get
198.               Return _Epsilon
199.           End Get
200.           Set(ByVal value As Single)
201.               _Epsilon = value
202.           End Set
203.       End Property
204.
205.       'Limite inferiore dell'intervallo in cui cercare la soluzione
206.       Public Property IntervalLowerBound() As Single
207.           Get
208.               Return _IntervalLowerBound
209.           End Get
210.           Set(ByVal value As Single)
211.               _IntervalLowerBound = value
212.           End Set
213.       End Property
214.
215.
'Limite superiore dell'intervallo in cui cercare la soluzione
216.   Public Property IntervalUpperBound() As Single
217.       Get
218.           Return _IntervalUpperBound
219.       End Get
220.       Set(ByVal value As Single)
221.           _IntervalUpperBound = value
222.       End Set
223.   End Property
224.
225.
226.   'Valuta la funzione polinomiale. Dati i coefficienti immessi,
227.   'noi disponiamo del polinomio p(x), quindi possiamo calcolare
228.   'i valori che esso assume per ogni x
229.   Private Function EvaluateFunction(ByVal x As Single) As Single
230.       Dim Result As Single = 0
231.
232.       For I As Int16 = 0 To Me.Coefficients.Length - 1
233.            Result += Me.Coefficients(I) * x ^ (Me.Coefficients.Length - 1 - I)
234.       Next
235.
236.       Return Result
237.   End Function
238.
239.   Public Overrides Function Solve() As Single()
240.       Dim a, b, c As Single
241.       Dim fa, fb, fc As Single
242.       Dim Interval As Single = 100
243.       Dim I As Int16 = 0
244.       Dim Result As Single
245.
246.       a = IntervalLowerBound
247.       b = IntervalUpperBound
248.
249.       'Non esiste uno zero tra a e b se f(a) e f(b) hanno
250.       'lo stesso segno
251.       If EvaluateFunction(a) * EvaluateFunction(b) > 0 Then
252.           Return Nothing
253.       End If
254.
255.       Do
256.            'c è il punto medio tra a e b
257.            c = (a + b) / 2
258.            'Calcola f(a), f(b) ed f(c)
259.            fa = EvaluateFunction(a)
260.            fb = EvaluateFunction(b)
261.            fc = EvaluateFunction(c)
262.
263.            'Se uno tra f(a), f(b) e f(c) vale zero, allora abbiamo
264.            'trovato una soluzione perfetta, senza errori, ed
265.            'usciamo direttamente dal ciclo
266.            If fa = 0 Then
267.                c = a
268.                Exit Do
269.            End If
270.            If fb = 0 Then
271.                c = b
272.                Exit Do
273.            End If
274.            If fc = 0 Then
275.                Exit Do
276.            End If
277.
278.           'Altrimenti, controlliamo quale coppia di valori scelti
279.           'tra f(a), f(b) ed f(c) ha segni discorsi: lo zero si troverà
280.           'tra le ascisse di questi
281.           If fa * fc < 0 Then
282.                b = c
283.           Else
284.                a = c
285.           End If
286.       Loop Until Math.Abs(a - b) < Me.Epsilon
287.
'Cicla finchè l'ampiezza dell'intervallo non è
288.           'sufficientemente piccola, quindi assume come zero più
289.           'probabile il punto medio tra a e b:
290.           Result = c
291.
292.           Return New Single() {Result}
293.       End Function
294.   End Class
295.
296.
297.   Sub Main()
298.       'Contiene un generico risolutore di equazioni. Non sappiamo ancora
299.       'quale tipologia di equazione dovremo risolvere, ma sappiamo per
300.       'certo che lo dovremo fare, ed EquationSolver è la classe
301.       'base di tutti i risolutori che espone il metodo Solve.
302.       Dim Eq As EquationSolver
303.       Dim x() As Single
304.       Dim Cmd As Char
305.
306.       Console.WriteLine("Scegli una tipologia di equazione: ")
307.       Console.WriteLine(" l - lineare;")
308.       Console.WriteLine(" q - quadratica;")
309.       Console.WriteLine(" h - di grado superiore al secondo;")
310.       Console.WriteLine(" e - non polinomiale;")
311.       Cmd = Console.ReadKey().KeyChar
312.       Console.Clear()
313.
314.       If Cmd <> "e" Then
315.           'Ancora, sappiamo che si tratta di un'equazione polinomiale
316.           'ma non di quale grado
317.           Dim Poly As PolynomialEquationSolver
318.
319.           'Ottiene i dati relativi a ciascuna equazione
320.           Select Case Cmd
321.               Case "l"
322.                   Dim Linear As New LinearEquationSolver()
323.                   Poly = Linear
324.               Case "q"
325.                   Dim Quadratic As New QuadraticEquationSolver()
326.                   Poly = Quadratic
327.               Case "h"
328.                   Dim High As New HighDegreeEquationSolver()
329.                   Dim CoefNumber As Int16
330.                   Console.WriteLine("Inserire il numero di coefficienti: ")
331.                   CoefNumber = Console.ReadLine
332.                   ReDim High.Coefficients(CoefNumber - 1)
333.                   Console.WriteLine("Inserire i limti dell'intervallo in cui cercare gli
                          zeri:")
334.                   High.IntervalLowerBound = Console.ReadLine
335.                   High.IntervalUpperBound = Console.ReadLine
336.                   Console.WriteLine("Inserire la precisione (epsilon):")
337.                   High.Epsilon = Console.ReadLine
338.                   Poly = High
339.           End Select
340.
341.           'A questo punto la variabile Poly contiene sicuramente un oggetto
342.           '(LinearEquationSolver, QuadraticEquationSolver oppure
343.           'HighDegreeEquationSolver), anche se non sappiamo quale. Tuttavia,
344.           'tutti questi sono pur sempre polinomiali e perciò tutti
345.           'hanno bisogno di sapere i coefficienti del polinomio.
346.           'Ecco che allora possiamo usare Poly con sicurezza percè
347.           'sicuramente contiene un oggetto e la proprietà Coefficients
348.           'è stata definita proprio nella classe PolynomialEquationSolver.
349.           '<b>N.B.: ricordate tutto quello che abbiamo detto sull'assegnamento
350.           ' di un oggetto di classe derivata a uno di classe base!</b>
351.           Console.WriteLine("Inserire i coefficienti: ")
352.           For I As Int16 = 1 To Poly.Coefficients.Length - 1
353.                Console.Write("a{0} = ", Poly.Coefficients.Length - I)
354.                Poly.Coefficients(I - 1) = Console.ReadLine
355.           Next
356.
357.           'Assegnamo Poly a Eq. Osservate che siamo andati via via dal
358.
'caso più particolare al più generale:
 359.              ' - Abbiamo creato un oggetto specifico per un certo grado
 360.              '     di un'equazione polinomiale (Linear, Quadratic, High);
 361.              ' - Abbiamo messo quell'oggetto in uno che si riferisce
 362.              '     genericamente a tutti i polinomi;
 363.              ' - Infine, abbiamo posto quest'ultimo in uno ancora più
 364.              '     generale che si riferisce a tutte le equazioni;
 365.              'Questo percorso porta da oggetto molto specifici e ricchi di membri
 366.              '(tante proprietà e tanti metodi), a tipi molto generali
 367.              'e poveri di membri (nel caso di Eq, un solo metodo).
 368.              Eq = Poly
 369.         Else
 370.              'Inseriamo in Eq un nuovo oggetto per risolvere equazioni non
 371.              'polinomiali, anche se il codice è al momento vuoto
 372.              Eq = New NonPolynomialEquationSolver
 373.              Console.WriteLine("Non implementato")
 374.         End If
 375.
 376.         'Risolviamo l'equazione. Richiamare la funzione Solve da un oggetto
 377.         'EquationSolver potrebbe non dirvi nulla, ma ricordate che dentro Eq
 378.         'è memorizzato un oggetto più specifico in cui
 379.         'è stata definita la funzione Solve(). Per questo motivo,
 380.         'anche se Eq è di tipo classe base, purtuttavia contiene
 381.         'al proprio interno un oggetto di tipo classe derivata, ed
 382.         'è questo che conta: viene usato il metodo Solve della classe
 383.         'derivata.
 384.         'Se ci pensate bene, vi verrà più spontaneo capire,
 385.         'poiché noi, ora, stiamo guardando ATTRAVERSO il tipo
 386.         'EquationSolver un oggetto di altro tipo. È come osservare
 387.         'attraverso filtri via via sempre più fitti (cfr
 388.         'immagine seguente)
 389.         x = Eq.Solve()
 390.
 391.         If x IsNot Nothing Then
 392.              Console.WriteLine("Soluzioni trovate: ")
 393.              For Each s As Single In x
 394.                   Console.WriteLine(s)
 395.              Next
 396.         Else
 397.              Console.WriteLine("Nessuna soluzione")
 398.         End If
 399.
 400.         Console.ReadKey()
 401.     End Sub
 402. End Module

Eccovi un'immagine dell'ultimo commento:
Il piano r osso è l'oggetto che r ealmente c'è in memor ia (ad esempio, Linear EquationSolver ); il piano blu con tr e
aper tur e   è    ciò   che   r iusciamo   a   veder e   quando   l'oggetto   viene   memor izzato      in   una   classe   astr atta
PolynomialEquationSolver ; il piano blu iniziale, invece, è ciò a cui possiamo acceder e attr aver so un EquationSolver : il
fascio di luce indica le nostr e possibilità di accesso. È pr opr io il caso di dir e che c'è molto di più di ciò che si vede!




Classi Sigillate
Le classi sigillate sono esattamente l'opposto di quelle astr atte, ossia non possono mai esser e er editate. Si dichiar ano
con la keyw or d NotInher itable:

    1. NotInheritable Class Example
    2.     '...
    3. End Class

Allo stesso modo, penser ete voi, i membr i che non possono subir e over loading sar anno mar cati con qualcosa tipo
NotOver r idable... In par te esatto, ma in par te er r ato. La keyw or d NotOver r idable si può applicar e solo e soltanto a
metodi già modificati tr amite polimor fismo, ossia Over r ides.

   01.   Class A
   02.       Sub DoSomething()
   03.           '...
   04.       End Sub
   05.   End Class
   06.
   07.   Class B
   08.       Inherits A
   09.
   10.       'Questa procedura sovrascrive la precedente versione
   11.       'di DoSomething dichiarata in A, ma preclude a tutte le
   12.       'classi derivate da B la possibilità di fare lo stesso
   13.       NotOverridable Overrides Sub DoSomething()
   14.           '...
   15.       End Sub
   16.   End Class

Inoltr e, le classi sigillate no n possono mai espor r e membr i sigillati, anche per chè tutti i lor o membr i lo sono
implicitamente (se una classe non può esser e er editata, ovviamente non si potr anno r idefinir e i membr i con
polimor fismo).




Classi Parziali
Una classe si dice par ziale quando il suo cor po è suddiviso su più files. Si tr atta solamento di un'utilità pr atica che ha
poco a che veder e con la pr ogr ammazione ad oggetti. Mi sembr ava, per ò, or dinato espor r e tutte le keyw or d associate
alle classi in un solo capitolo. Semplicemente, una classe par ziale si dichiar a in questo modo:

    1. Partial Class [Nome]
    2.     '...
    3. End Class

È sufficiente dichiar ar e una classe come par ziale per chè il compilator e associ, in fase di assemblaggio, tutte le classi
con lo stesso nome in file diver si a quella definizione. Ad esempio:

   01. 'Nel file Codice1.vb :
   02. Partial Class A
   03.     Sub One()
   04.
'...
05.       End Sub
06.   End Class
07.
08.   'Nel file Codice2.vb
09.   Class A
10.       Sub Two()
11.           '...
12.       End Sub
13.   End Class
14.
15.   'Nel file Codice3.vb
16.   Class A
17.       Sub Three()
18.           '...
19.       End Sub
20.   End Class
21.
22.   'Tutte le classi A vengono compilate come un'unica classe
23.   'perchè una possiede la keyword Partial:
24.   Class A
25.       Sub One()
26.           '...
27.       End Sub
28.
29.       Sub Two()
30.           '...
31.       End Sub
32.
33.       Sub Three()
34.           '...
35.       End Sub
36.   End Class
A37. Le Interfacce

Sc opo delle Interfac c e
Le inter facce sono un'entità davver o singolar e all'inter no del .NET Fr amew or k. La lor o funzione è assimilabile a quella
delle classi astr atte, ma il modo con cui esse la svolgono è molto diver so da ciò che abbiamo visto nel capitolo
pr ecedente. Il pr incipale scopo di un'inter faccia è definir e lo scheletr o di una classe; potr ebbe esser e scher zosamente
assimilata alla r icetta con cui si pr epar a un dolce. Quello che l'inter faccia X fa, ad esempio, consiste nel dir e che per
costr uir e una classe Y che r ispetti "la r icetta" descr itta in X ser vono una pr opr ietà Id di tipo Integer , una funzione
GetSomething senza par ametr i che r estituisce una str inga e una pr ocedur a DoSomething con un singolo par ametr o
Double. Tutte le classi che avr anno intenzione di seguir e i pr ecetti di X (in ger go im plem entar e X) dovr anno definir e,
allo stesso modo, quella pr opr ietà di quel tipo e quei metodi con quelle specifiche signatur e (il nome ha impor tanza
r elativa).
Faccio subito un esempio. Fino ad or a, abbiamo visto essenzialmente due tipi di collezione: gli Ar r ay e gli Ar r ayList. Sia
per l'uno che per l'altr o, ho detto che è possibile eseguir e un'iter azione con il costr utto For Each:

   01.    Dim Ar() As Int32 = {1, 2, 3, 4, 5, 6}
   02.    Dim Al As New ArrayList
   03.
   04.    For I As Int32 = 1 To 40
   05.         Al.Add(I)
   06.    Next
   07.
   08.    'Stampa i valori di Ar:
   09.    For Each K As Int32 In Ar
   10.         Console.WriteLine(K)
   11.    Next
   12.    'Stampa i valori di Al
   13.    For Each K As Int32 In Al
   14.         Console.WriteLine(K)
   15.    Next

Ma il sistema come fa a saper e che Ar e Al sono degli insiemi di valor i? Dopotutto, il lor o nome è significativo solo per
noi pr ogr ammator i, mentr e per il calcolator e non è altr o che una sequenza di car atter i. Allo stesso modo, il codice di
Ar r ay e Ar r ayList, definito dai pr ogr ammator i che hanno scr itto il Fr amew or k, è intelligibile solo agli uomini, per chè
al computer non comunica nulla sullo scopo per il quale è stato scr itto. Allor a, siamo al punto di par tenza: nelle classi
Ar r ay e Ar r ayList non c'è nulla che possa far "capir e" al pr ogr amma che quelli sono a tutti gli effetti delle collezioni e
che, quindi, sono iter abili; e, anche se in qualche str ano modo l'elabor ator e lo potesse capir e, non "sapr ebbe" (in quanto
entità non senziente) come far per estr ar r e singoli dati e dar celi uno in fila all'altr o. Ecco che entr ano in scena le
inter facce: tutte le classi che r appr esentano un insieme o una collezione di elementi implemen tan o l'inter faccia
IEnumer able, la quale, se potesse par lar e, dir ebbe "Guar da che questa classe è una collezione, tr attala di conseguenza!".
Questa inter faccia obbliga le classi dalle quali è implementata a definir e alcuni metodi che ser vono per l'enumer azione
(Cur r ent, MoveNex t e Reset) e che vedr emo nei pr ossimi capitoli.
In conclusione, quindi, il For Each pr ima di tutto contr olla che l'oggetto posto dopo la clausola "In" implementi
l'inter faccia IEnumer able. Quindi r ichiama il metodo Reset per por si sul pr imo elemento, poi deposita in K il valor e
esposto dalla pr opr ietà Cur r ent, esegue il codice contenuto nel pr opr io cor po e, una volta ar r ivato a Nex t, esegue il
metodo MoveNex t per avanzar e al pr ossimo elemento. Il For Each "è sicur o" dell'esistenza di questi membr i per chè
l'inter faccia IEnumer able ne impone la definizione.
Riassumendo, le inter facce hanno il compito di infor mar e il sistema su quali siano le car atter istiche e i compiti di una
classe. Per questo motivo, il lor o nomi ter minano spesso in "-able", come ad esempio IEnumer able, IEquatable,
ICompr able, che ci dicono "- è enumer abile", "- è eguagliabile", "- è compar abile", "è ... qualcosa".
Dic hiarazione e implementazione
La sintassi usata per dichiar ar e un'inter faccia è la seguente:

    1. Interface [Nome]
    2.     'Membri
    3. End Interface

I membr i delle inter facce, tuttavia, sono un po' diver si dai membr i di una classe, e nello scr iver li bisogna r ispettar e
queste r egole:

         Nel caso di metodi, pr opr ietà od eventi, il cor po non va specificato;
         Non si possono mai usar e gli specificator i di accesso;
         Si possono comunque usar e dei modificator i come Shar ed, ReadOnly e Wr iteOnly.

Il pr imo ed il secondo punto sar anno ben compr esi se ci si soffer ma a pensar e che l'inter faccia ha il solo scopo di
definir e quali membr i una classe debba implementar e: per questo motivo, non se ne può scr iver e il cor po, dato che
spetta espr essamente alle classi implementanti, e non ci si pr eoccupa dello specificator e di accesso, dato che si sta
specificando solo il "cosa" e non il "come". Ecco alcuni semplici esempi di dichiar azioni:

   01.    'Questa interfaccia dal nome improbabile indica che
   02.    'la classe che la implementa rappresenta qualcosa di
   03.    '"identificabile" e per questo espone una proprietà Integer Id
   04.    'e una funzione ToString. Id e ToString, infatti, sono gli
   05.    'elementi più utili per identificare qualcosa, prima in
   06.    'base a un codice univoco e poi grazie ad una rappresentazione
   07.    'comprensibile dall'uomo
   08.    Interface IIdentifiable
   09.        ReadOnly Property Id() As Int32
   10.        Function ToString() As String
   11.    End Interface
   12.
   13.    'La prossima interfaccia, invece, indica qualcosa di resettabile
   14.    'e obbliga le classi implementanti a esporre il metodo Reset
   15.    'e la proprietà DefaultValue, che dovrebbe rappresentare
   16.    'il valore di default dell'oggetto. Dato che non sappiamo ora
   17.    'quali classi implementeranno questa interfaccia, dobbiamo
   18.    'per forza usare un tipo generico come Object per rappresentare
   19.    'un valore reference. Vedremo come aggirare questo ostacolo
   20.    'fra un po', con i Generics
   21.    Interface IResettable
   22.        Property DefaultValue() As Object
   23.        Sub Reset()
   24.    End Interface
   25.
   26.    'Come avete visto, i nomi di interfaccia iniziano per convenzione
   27.    'con la lettera I maiuscola

Or a che sappiamo come dichiar ar e un'inter faccia, dobbiamo scopr ir e come usar la. Per implementar e un'inter faccia in
una classe, si usa questa sintassi:

    1. Class Example
    2.     Implements [Nome Interfaccia]
    3.
    4.     [Membro] Implements [Nome Interfaccia].[Membro]
    5. End Class

Si capisce meglio con un esempio:

   01. Module Module1
   02.     Interface IIdentifiable
   03.         ReadOnly Property Id() As Int32
   04.         Function ToString() As String
   05.     End Interface
   06.
   07.
'Rappresenta un pacco da spedire
08.   Class Pack
09.       'Implementa l'interfaccia IIdentifiable, in quanto un pacco
10.       'dovrebbe poter essere ben identificato
11.       Implements IIdentifiable
12.
13.       'Notate bene che l'interfaccia ci obbliga a definire una
14.       'proprietà, ma non ci obbliga a definire un campo
15.       'ad essa associato
16.       Private _Id As Int32
17.       Private _Destination As String
18.       Private _Dimensions(2) As Single
19.
20.       'La classe definisce una proprietà id di tipo Integer
21.       'e la associa all'omonima presente nell'interfaccia in
22.       'questione. Il legame tra questa proprietà Id e quella
23.       'presenta nell'interfaccia è dato solamente dalla
24.       'clausola (si chiama così in gergo) "Implements",
25.       'la quale avvisa il sistema che il vincolo imposto
26.       'è stato soddisfatto.
27.       'N.B.: il fatto che il nome di questa proprietà sia uguale
28.       'a quella definita in IIdentifiable non significa nulla.
29.       'Avremmo potuto benissimo chiamarla "Pippo" e associarla
30.       'a Id tramite il codice "Implements IIdentifiable.Id", ma
31.       'ovviamente sarebbe stata una palese idiozia XD
32.       Public ReadOnly Property Id() As Integer Implements IIdentifiable.Id
33.           Get
34.               Return _Id
35.           End Get
36.       End Property
37.
38.       'Destinazione del pacco.
39.       'Il fatto che l'interfaccia ci obblighi a definire quei due
40.       'membri non significa che non possiamo definirne altri
41.       Public Property Destination() As String
42.           Get
43.               Return _Destination
44.           End Get
45.           Set(ByVal value As String)
46.               _Destination = value
47.           End Set
48.       End Property
49.
50.       'Piccolo ripasso delle proprietà indicizzate e
51.       'della gestione degli errori
52.       Public Property Dimensions(ByVal Index As Int32) As Single
53.           Get
54.               If (Index >= 0) And (Index < 3) Then
55.                    Return _Dimensions(Index)
56.               Else
57.                    Throw New IndexOutOfRangeException()
58.               End If
59.           End Get
60.           Set(ByVal value As Single)
61.               If (Index >= 0) And (Index < 3) Then
62.                    _Dimensions(Index) = value
63.               Else
64.                    Throw New IndexOutOfRangeException()
65.               End If
66.           End Set
67.       End Property
68.
69.       Public Overrides Function ToString() As String Implements IIdentifiable.ToString
70.           Return String.Format("{0}: Pacco {1}x{2}x{3}, Destinazione: {4}", _
71.               Me.Id, Me.Dimensions(0), Me.Dimensions(1), _
72.               Me.Dimensions(2), Me.Destination)
73.       End Function
74.   End Class
75.
76.   Sub Main()
77.       '...
78.   End Sub
79.
End Module

Or a che abbiamo implementato l'inter faccia nella classe Pack, tuttavia, non sappiamo che far cene. Siamo a conoscenza
del fatto che gli oggetti Pack sar anno sicur amente identificabili, ma nulla di più. Ritor niamo, allor a, all'esempio del
pr imo par agr afo: cos'è che r ende ver amente utile IEnumer able, al di là del fatto di r ender e funzionante il For Each? Si
applica a qualsiasi collezione o insieme, non impor ta di quale natur a o per quali scopi, non impor ta nemmeno il codice
che sottende all'enumer azione: l'impor tante è che una vastissima gamma di oggetti possano esser e r icondotti ad un
solo ar chetipo (io ne ho nominati solo due, ma ce ne sono a iosa). Allo stesso modo, potr emo usar e IIdentifiable per
manipolar e una gr an quantità di dati di natur a differ ente. Ad esempio, il codice di sopr a potr ebbe esser e sviluppato
per cr ear e un sistema di gestione di un ufficio postale. Eccone un esempio:

 001. Module Module1
 002.
 003.     Interface IIdentifiable
 004.         ReadOnly Property Id() As Int32
 005.         Function ToString() As String
 006.     End Interface
 007.
 008.     Class Pack
 009.         Implements IIdentifiable
 010.
 011.         Private _Id As Int32
 012.         Private _Destination As String
 013.         Private _Dimensions(2) As Single
 014.
 015.         Public ReadOnly Property Id() As Integer Implements IIdentifiable.Id
 016.              Get
 017.                  Return _Id
 018.              End Get
 019.         End Property
 020.
 021.         Public Property Destination() As String
 022.              Get
 023.                  Return _Destination
 024.              End Get
 025.              Set(ByVal value As String)
 026.                  _Destination = value
 027.              End Set
 028.         End Property
 029.
 030.         Public Property Dimensions(ByVal Index As Int32) As Single
 031.              Get
 032.                  If (Index >= 0) And (Index < 3) Then
 033.                       Return _Dimensions(Index)
 034.                  Else
 035.                       Throw New IndexOutOfRangeException()
 036.                  End If
 037.              End Get
 038.              Set(ByVal value As Single)
 039.                  If (Index >= 0) And (Index < 3) Then
 040.                       _Dimensions(Index) = value
 041.                  Else
 042.                       Throw New IndexOutOfRangeException()
 043.                  End If
 044.              End Set
 045.         End Property
 046.
 047.         Sub New(ByVal Id As Int32)
 048.              _Id = Id
 049.         End Sub
 050.
 051.         Public Overrides Function ToString() As String Implements IIdentifiable.ToString
 052.              Return String.Format("{0:0000}: Pacco {1}x{2}x{3}, Destinazione: {4}", _
 053.                  Me.Id, Me.Dimensions(0), Me.Dimensions(1), _
 054.                  Me.Dimensions(2), Me.Destination)
 055.         End Function
 056.     End Class
 057.
 058.
Class Telegram
059.       Implements IIdentifiable
060.
061.       Private _Id As Int32
062.       Private _Recipient As String
063.       Private _Message As String
064.
065.       Public ReadOnly Property Id() As Integer Implements IIdentifiable.Id
066.           Get
067.               Return _Id
068.           End Get
069.       End Property
070.
071.       Public Property Recipient() As String
072.           Get
073.               Return _Recipient
074.           End Get
075.           Set(ByVal value As String)
076.               _Recipient = value
077.           End Set
078.       End Property
079.
080.       Public Property Message() As String
081.           Get
082.               Return _Message
083.           End Get
084.           Set(ByVal value As String)
085.               _Message = value
086.           End Set
087.       End Property
088.
089.       Sub New(ByVal Id As Int32)
090.           _Id = Id
091.       End Sub
092.
093.       Public Overrides Function ToString() As String Implements IIdentifiable.ToString
094.           Return String.Format("{0:0000}: Telegramma per {1} ; Messaggio = {2}", _
095.               Me.Id, Me.Recipient, Me.Message)
096.       End Function
097.   End Class
098.
099.   Class MoneyOrder
100.       Implements IIdentifiable
101.
102.       Private _Id As Int32
103.       Private _Recipient As String
104.       Private _Money As Single
105.
106.       Public ReadOnly Property Id() As Integer Implements IIdentifiable.Id
107.           Get
108.               Return _Id
109.           End Get
110.       End Property
111.
112.       Public Property Recipient() As String
113.           Get
114.               Return _Recipient
115.           End Get
116.           Set(ByVal value As String)
117.               _Recipient = value
118.           End Set
119.       End Property
120.
121.       Public Property Money() As Single
122.           Get
123.               Return _Money
124.           End Get
125.           Set(ByVal value As Single)
126.               _Money = value
127.           End Set
128.       End Property
129.
130.
Sub New(ByVal Id As Int32)
131.           _Id = Id
132.       End Sub
133.
134.       Public Overrides Function ToString() As String Implements IIdentifiable.ToString
135.           Return String.Format("{0:0000}: Vaglia postale per {1} ; Ammontare = {2}€", _
136.               Me.Id, Me.Recipient, Me.Money)
137.       End Function
138.   End Class
139.
140.   'Classe che elabora dati di tipo IIdentifiable, ossia qualsiasi
141.   'oggetto che implementi tale interfaccia
142.   Class PostalProcessor
143.       'Tanto per tenersi allenati coi delegate, ecco una
144.       'funzione delegate che funge da filtro per i vari id
145.       Public Delegate Function IdSelector(ByVal Id As Int32) As Boolean
146.
147.       Private _StorageCapacity As Int32
148.       Private _NextId As Int32 = 0
149.       'Un array di interfacce. Quando una variabile viene
150.       'dichiarata come di tipo interfaccia, ciò
151.       'che può contenere è qualsiasi oggetto
152.       'che implementi quell'interfaccia. Per lo stesso
153.       'discorso fatto nel capitolo precedente, noi
154.       'possiamo vedere <i>attraverso</i> l'interfaccia
155.       'solo quei membri che essa espone direttamente, anche
156.       'se il contenuto vero e proprio è qualcosa
157.       'di più
158.       Private Storage() As IIdentifiable
159.
160.       'Capacità del magazzino. Assumeremo che tutti
161.       'gli oggetti rappresentati dalle classi Pack, Telegram
162.       'e MoneyOrder vadano in un magazzino immaginario che,
163.       'improbabilmente, riserva un solo posto per ogni
164.       'singolo elemento
165.       Public Property StorageCapacity() As Int32
166.           Get
167.               Return _StorageCapacity
168.           End Get
169.           Set(ByVal value As Int32)
170.               _StorageCapacity = value
171.               ReDim Preserve Storage(value)
172.           End Set
173.       End Property
174.
175.       'Modifica od ottiene un riferimento all'Index-esimo
176.       'oggetto nell'array Storage
177.       Public Property Item(ByVal Index As Int32) As IIdentifiable
178.           Get
179.               If (Index >= 0) And (Index < Storage.Length) Then
180.                    Return Me.Storage(Index)
181.               Else
182.                    Throw New IndexOutOfRangeException()
183.               End If
184.           End Get
185.           Set(ByVal value As IIdentifiable)
186.               If (Index >= 0) And (Index < Storage.Length) Then
187.                    Me.Storage(Index) = value
188.               Else
189.                    Throw New IndexOutOfRangeException()
190.               End If
191.           End Set
192.       End Property
193.
194.       'Restituisce la prima posizione libera nell'array
195.       'Storage. Anche se in questo esempio non l'abbiamo
196.       'contemplato, gli elementi possono anche essere rimossi
197.       'e quindi lasciare un posto libero nell'array
198.       Public ReadOnly Property FirstPlaceAvailable() As Int32
199.           Get
200.               For I As Int32 = 0 To Me.Storage.Length - 1
201.                    If Me.Storage(I) Is Nothing Then
202.
Return I
203.               End If
204.           Next
205.           Return (-1)
206.       End Get
207.   End Property
208.
209.   'Tutti gli oggetti che inizializzeremo avranno bisogno
210.   'di un id: ce lo fornisce la stessa classe Processor
211.   'tramite questa proprietà che si autoincrementa
212.   Public ReadOnly Property NextId() As Int32
213.       Get
214.           _NextId += 1
215.           Return _NextId
216.       End Get
217.   End Property
218.
219.
220.   'Due possibili costruttori: uno che accetta un insieme
221.   'già formato di elementi...
222.   Public Sub New(ByVal Items() As IIdentifiable)
223.       Me.Storage = Items
224.       _SorageCapacity = Items.Length
225.   End Sub
226.
227.   '... e uno che accetta solo la capacità del magazzino
228.   Public Sub New(ByVal Capacity As Int32)
229.       Me.StorageCapacity = Capacity
230.   End Sub
231.
232.   'Stampa a schermo tutti gli elementi che la funzione
233.   'contenuta nel parametro Selector di tipo delegate
234.   'considera validi (ossia tutti quelli per cui
235.   'Selector.Invoke restituisce True)
236.   Public Sub PrintByFilter(ByVal Selector As IdSelector)
237.       For Each K As IIdentifiable In Storage
238.            If K Is Nothing Then
239.                Continue For
240.            End If
241.            If Selector.Invoke(K.Id) Then
242.                Console.WriteLine(K.ToString())
243.            End If
244.       Next
245.   End Sub
246.
247.   'Stampa l'oggetto con Id specificato
248.   Public Sub PrintById(ByVal Id As Int32)
249.       For Each K As IIdentifiable In Storage
250.            If K Is Nothing Then
251.                Continue For
252.            End If
253.            If K.Id = Id Then
254.                Console.WriteLine(K.ToString())
255.                Exit For
256.            End If
257.       Next
258.   End Sub
259.
260.   'Cerca tutti gli elementi che contemplano all'interno
261.   'della propria descrizione la stringa Str e li
262.   'restituisce come array di Id
263.   Public Function SearchItems(ByVal Str As String) As Int32()
264.       Dim Temp As New ArrayList
265.
266.       For Each K As IIdentifiable In Storage
267.            If K Is Nothing Then
268.                Continue For
269.            End If
270.            If K.ToString().Contains(Str) Then
271.                Temp.Add(K.Id)
272.            End If
273.       Next
274.
275.           Dim Result(Temp.Count - 1) As Int32
276.           For I As Int32 = 0 To Temp.Count - 1
277.                Result(I) = Temp(I)
278.           Next
279.
280.           Temp.Clear()
281.           Temp = Nothing
282.
283.           Return Result
284.       End Function
285.   End Class
286.
287.   Private Processor As New PostalProcessor(10)
288.   Private Cmd As Char
289.   Private IdFrom, IdTo As Int32
290.
291.   Function SelectId(ByVal Id As Int32) As Boolean
292.       Return (Id >= IdFrom) And (Id <= IdTo)
293.   End Function
294.
295.   Sub InsertItems(ByVal Place As Int32)
296.       Console.WriteLine("Scegliere la tipologia di oggetto:")
297.       Console.WriteLine(" p - pacco;")
298.       Console.WriteLine(" t - telegramma;")
299.       Console.WriteLine(" v - vaglia postale;")
300.       Cmd = Console.ReadKey().KeyChar
301.
302.       Console.Clear()
303.       Select Case Cmd
304.           Case "p"
305.               Dim P As New Pack(Processor.NextId)
306.               Console.WriteLine("Pacco - Id:{0:0000}", P.Id)
307.               Console.Write("Destinazione: ")
308.               P.Destination = Console.ReadLine
309.               Console.Write("Larghezza: ")
310.               P.Dimensions(0) = Console.ReadLine
311.               Console.Write("Lunghezza: ")
312.               P.Dimensions(1) = Console.ReadLine
313.               Console.Write("Altezza: ")
314.               P.Dimensions(2) = Console.ReadLine
315.               Processor.Item(Place) = P
316.           Case "t"
317.               Dim T As New Telegram(Processor.NextId)
318.               Console.WriteLine("Telegramma - Id:{0:0000}", T.Id)
319.               Console.Write("Destinatario: ")
320.               T.Recipient = Console.ReadLine
321.               Console.Write("Messaggio: ")
322.               T.Message = Console.ReadLine
323.               Processor.Item(Place) = T
324.           Case "v"
325.               Dim M As New MoneyOrder(Processor.NextId)
326.               Console.WriteLine("Vaglia - Id:{0:0000}", M.Id)
327.               Console.Write("Beneficiario: ")
328.               M.Recipient = Console.ReadLine
329.               Console.Write("Somma: ")
330.               M.Money = Console.ReadLine
331.               Processor.Item(Place) = M
332.           Case Else
333.               Console.WriteLine("Comando non riconosciuto.")
334.               Console.ReadKey()
335.               Exit Sub
336.       End Select
337.
338.       Console.WriteLine("Inserimento eseguito!")
339.       Console.ReadKey()
340.   End Sub
341.
342.   Sub ProcessData()
343.       Console.WriteLine("Selezionare l'operazione:")
344.       Console.WriteLine(" c - cerca;")
345.       Console.WriteLine(" v - visualizza;")
346.
Cmd = Console.ReadKey().KeyChar
347.
348.           Console.Clear()
349.           Select Case Cmd
350.               Case "c"
351.                   Dim Str As String
352.                   Console.WriteLine("Inserire la parola da cercare:")
353.                   Str = Console.ReadLine
354.
355.                   Dim Ids() As Int32 = Processor.SearchItems(Str)
356.                   Console.WriteLine("Trovati {0} elementi. Visualizzare? (y/n)", Ids.Length)
357.                   Cmd = Console.ReadKey().KeyChar
358.                   Console.WriteLine()
359.
360.                   If Cmd = "y" Then
361.                        For Each Id As Int32 In Ids
362.                             Processor.PrintById(Id)
363.                        Next
364.                   End If
365.               Case "v"
366.                   Console.WriteLine("Visualizzare gli elementi")
367.                   Console.Write("Da Id: ")
368.                   IdFrom = Console.ReadLine
369.                   Console.Write("A Id: ")
370.                   IdTo = Console.ReadLine
371.                   Processor.PrintByFilter(AddressOf SelectId)
372.               Case Else
373.                   Console.WriteLine("Comando sconosciuto.")
374.           End Select
375.
376.           Console.ReadKey()
377.       End Sub
378.
379.       Sub Main()
380.           Do
381.               Console.WriteLine("Gestione ufficio")
382.               Console.WriteLine()
383.               Console.WriteLine("Selezionare l'operazione da effettuare:")
384.               Console.WriteLine(" i - inserimento oggetti;")
385.               Console.WriteLine(" m - modifica capacità magazzino;")
386.               Console.WriteLine(" p - processa i dati;")
387.               Console.WriteLine(" e - esci.")
388.               Cmd = Console.ReadKey().KeyChar
389.
390.               Console.Clear()
391.               Select Case Cmd
392.                   Case "i"
393.                       Dim Index As Int32 = Processor.FirstPlaceAvailable
394.
395.                       Console.WriteLine("Inserimento oggetti in magazzino")
396.                       Console.WriteLine()
397.
398.                        If Index > -1 Then
399.                             InsertItems(Index)
400.                        Else
401.                             Console.WriteLine("Non c'è più spazio in magazzino!")
402.                             Console.ReadKey()
403.                        End If
404.                   Case "m"
405.                        Console.WriteLine("Attuale capacità: " & Processor.StorageCapacity)
406.                        Console.WriteLine("Inserire una nuova dimensione: ")
407.                        Processor.StorageCapacity = Console.ReadLine
408.                        Console.WriteLine("Operazione effettuata.")
409.                        Console.ReadKey()
410.                   Case "p"
411.                        ProcessData()
412.               End Select
413.               Console.Clear()
414.           Loop Until Cmd = "e"
415.       End Sub
416. End   Module
Avevo in mente di definir e anche un'altr a inter faccia, IPayable, per calcolar e anche il costo di spedizione di ogni pezzo:
volevo far notar e come, sebbene il costo vada calcolato in manier a diver sa per i tr e tipi di oggetto (in base alle
dimensioni per il pacco, in base al numer o di par ole per il telegr amma e in base all'ammontar e inviato per il vaglia),
bastasse r ichiamar e una funzione attr aver so l'inter faccia per ottener e il r isultato. Poi ho consider ato che un esempio
di 400 r ighe er a già abbastanza. Ad ogni modo, user ò adesso quel'idea in uno spezzone tr atto dal pr ogr amma appena
scr itto per mostr ar e l'uso di inter facce multiple:

   01. Module Module1
   02.     '...
   03.
   04.     Interface IPayable
   05.          Function CalculateSendCost() As Single
   06.     End Interface
   07.
   08.     Class Telegram
   09.          'Nel caso di più interfacce, le si separa con la virgola
   10.          Implements IIdentifiable, IPayable
   11.
   12.          '...
   13.
   14.          Public Function CalculateSendCost() As Single Implements IPayable.CalculateSendCost
   15.               'Come vedremo nel capitolo dedicato alle stringhe,
   16.               'la funzione Split(c) spezza la stringa in tante
   17.               'parti, divise dal carattere c, e le restituisce
   18.               'sottoforma di array. In questo caso, tutte le sottostringhe
   19.               'separate da uno spazio sono all'incirca tante
   20.               'quanto il numero di parole nella frase
   21.               Select Case Me.Message.Split(" ").Length
   22.                   Case Is <= 20
   23.                       Return 4.39
   24.                   Case Is <= 50
   25.                       Return 6.7
   26.                   Case Is <= 100
   27.                       Return 10.3
   28.                   Case Is <= 200
   29.                       Return 19.6
   30.                   Case Is <= 500
   31.                       Return 39.75
   32.               End Select
   33.          End Function
   34.     End Class
   35.
   36.     '...
   37. End Class




Definizione di tipi in un'interfac c ia
Così come è possibile dichiar ar e una nuova classe all'inter no di un'altr a, o una str uttur a in una classe, o un'inter faccia in
una classe, o una str uttur a in una str uttur a, o tutte le altr e possibili combinazioni, è anche possibile dichiar ar e un
nuovo tipo in un'inter faccia. In questo caso, solo le classi che implementer anno quell'inter faccia sar anno in gr ado di
usar e quel tipo. Ad esempio:

   01. Interface ISaveable
   02.     Structure FileInfo
   03.         'Assumiamo per brevità che queste variabili Public
   04.         'siano in realtà proprietà
   05.         Public Path As String
   06.         'FileAttribues è un enumeratore su bit che contiene
   07.         'informazioni sugli attributi di un file (nascosto, a sola
   08.         'lettura, archivio, compresso, eccetera...)
   09.         Public Attributes As FileAttributes
   10.     End Structure
   11.     Property SaveInfo() As FileInfo
   12.     Sub Save()
   13.
End Interface
   14.
   15.    Class A
   16.        Private _SaveInfo As ISaveable.FileInfo 'SBAGLIATO!
   17.        '...
   18.    End Class
   19.
   20.
   21.    Class B
   22.        Implements ISaveable
   23.
   24.          Private _SaveInfo As ISaveable.FileInfo 'GIUSTO
   25.
   26.        '...
   27.    End Class




Ereditarietà, polimorfismo e overloading per le interfac c e
Anche le inter facce possono er editar e da un'altr a inter faccia base. In questo caso, dato che in un'inter faccia non si
possono usar e specificator i di accesso, la classe der ivata acquisisce tutti i membr i di quella base:

    1.    Interface A
    2.        Property PropA() As Int32
    3.    End Interface
    4.
    5.    Interface B
    6.        Inherits A
    7.        Sub SubB()
    8.    End Interface

Non si può usar e il polimor fismo per chè non c'è nulla da r idefinir e, in quanto i metodi non hanno un cor po.
Si può, invece, usar e l'over loading come si fa di consueto: non ci sono differ enze significative in questo ambito.




Perc hè preferire un'interfac c ia a una c lasse astratta
La differ enza sostanziale tr a una classe astr atta e un'inter faccia è che la pr ima definisce l'es s en za di un oggetto (che
cosa è), mentr e la seconda ne indica il comportamen to (che cosa fa). Inoltr e una classe astr atta è in gr ado di definir e
membr i che ver r anno acquisiti dalla classe der ivata, e quindi dichiar a delle funzionalità di base er editabili da tutti i
discendenti; l'inter faccia, al contr ar io, "or dina" a chi la implementa di definir e un cer to membr o con una cer ta
funzione, ma non for nisce alcun codice di base, né alcuna dir ettiva su come un dato compito debba esser e svolto. Ecco
che, sulla base di queste osser vazioni, possiamo individuar e alcune casistiche in cui sia meglio l'una o l'altr a:

         Quando esistono compor tamenti comuni : inter facce
         Quando esistono classi non r iconducibili ad alcun ar chetipo o classe base: inter facce
         Quando tutte le classi hanno fondamentalmente la stessa essenza : classe astr atta
         Quando tutte le classi, assimilabili ad un unico ar chetipo, hanno bisogno di implementar e la stessa funzionalità o
         gli stessi membr i : classe astr atta
A38. Utilizzo delle Interfacce - Parte I

L'aspetto più inter essante e sicur amente più utile delle inter facce è che il lor o utilizzo è fondamentale per l'uso di alcuni
costr utti par ticolar i, quali il For Each e l'Using, e per molte altr e funzioni e pr ocedur e che inter vengono nella gestione
delle collezioni. Impar ar e a manipolar e con facilità questo str umento per metter à di scr iver e non solo meno codice, più
efficace e r iusabile, ma anche di impostar e l'applicazione in una manier a solida e r obusta.




IComparable e IComparer
Un oggetto che implementa ICompar able comunica implicitamente al .NET Fr amew or k che può esser e confr ontato con
altr i oggetti, stabilendo se uno di essi è maggior e, minor e o uguale all'altr o e abilitando in questo modo l'or dinamento
automatico attr aver so il metodo Sor t di una collection. Infatti, tale metodo confr onta uno ad uno ogni elemento di una
collezione o di un ar r ay e tr amite la funzione Compar eTo che ogni inter faccia ICompar able espone e li or dina in or dine
cr escente o decr escente. Compar eTo è una funzione di istanza che implementa ICompar able.Compar eTo e ha dei
r isultati pr edefiniti: r estituisce 1 se l'oggetto passato come par ametr o è minor e dell'oggetto dalla quale viene
r ichiamata, 0 se è uguale e -1 se è maggior e. Ad esempio, questo semplice pr ogr amma illustr a il funzionamento di
Compar eTo e Sor t:

   01. Module Module1
   02.     Sub Main()
   03.         Dim A As Int32
   04.
   05.         Console.WriteLine("Inserisci un numero intero:")
   06.         A = Console.ReadLine
   07.
   08.         'Tutti i tipi di base espongono il metodo CompareTo, poichè
   09.         'tutti implementano l'interfaccia IComparable:
   10.         If A.CompareTo(10) = 1 Then
   11.              Console.WriteLine(A & " è maggiore di 10")
   12.         ElseIf A.CompareTo(10) = 0 Then
   13.              Console.WriteLine(A & " è uguale a 10")
   14.         Else
   15.              Console.WriteLine(A & " è minore di 10")
   16.         End If
   17.
   18.         'Il fatto che i tipi di base siano confrontabili implica
   19.         'che si possano ordinare tramite il metodo Sort di una
   20.         'qualsiasi collezione o array di elementi
   21.         Dim B() As Int32 = {1, 5, 2, 8, 10, 56}
   22.         'Ordina l'array
   23.         Array.Sort(B)
   24.         'E visualizza i numeri in ordine crescente
   25.         For I As Int16 = 0 To UBound(B)
   26.              Console.WriteLine(B(I))
   27.         Next
   28.
   29.         'Anche String espone questo metodo, quindi si può ordinare
   30.         'alfabeticamente un insieme di stringhe:
   31.         Dim C As New ArrayList
   32.         C.Add("Banana")
   33.         C.Add("Zanzara")
   34.         C.Add("Anello")
   35.         C.Add("Computer")
   36.         'Ordina l'insieme
   37.         C.Sort()
   38.         For I As Int16 = 0 To C.Count - 1
   39.              Console.WriteLine(C(I))
   40.         Next
   41.
   42.         Console.ReadKey()
   43.
End Sub
   44. End Module

Dopo aver immesso un input, ad esempio 8, avr emo la seguente scher mata:

 Inserire un numero intero:
 8
 8 è minore di 10
 1
 2
 5
 8
 10
 56
 Anello
 Banana
 Computer
 Zanzara


Come si osser va, tutti gli elementi sono stati or dinati cor r ettamente. Or a che abbiamo visto la potenza di
ICompar able, vediamo di capir e come implementar la. L'esempio che pr ender ò come r ifer imento or a pone una semplice
classe Per son, di cui si è già par lato addietr o, e or dina un Ar r ayList di questi oggetti pr endendo come r ifer imento il
nome completo:

   01. Module Module1
   02.     Class Person
   03.         Implements IComparable
   04.         Private _FirstName, _LastName As String
   05.         Private ReadOnly _BirthDay As Date
   06.
   07.         Public Property FirstName() As String
   08.              Get
   09.                  Return _FirstName
   10.              End Get
   11.              Set(ByVal Value As String)
   12.                  If Value <> "" Then
   13.                      _FirstName = Value
   14.                  End If
   15.              End Set
   16.         End Property
   17.
   18.         Public Property LastName() As String
   19.              Get
   20.                  Return _LastName
   21.              End Get
   22.              Set(ByVal Value As String)
   23.                  If Value <> "" Then
   24.                      _LastName = Value
   25.                  End If
   26.              End Set
   27.         End Property
   28.
   29.         Public ReadOnly Property BirthDay() As Date
   30.              Get
   31.                  Return _BirthDay
   32.              End Get
   33.         End Property
   34.
   35.         Public ReadOnly Property CompleteName() As String
   36.              Get
   37.                  Return _FirstName & " " & _LastName
   38.              End Get
   39.         End Property
   40.
   41.         'Per definizione, purtroppo, CompareTo deve sempre usare
   42.         'un parametro di tipo Object: risolveremo questo problema
   43.
'più in là utilizzando i Generics
   44.             Public Function CompareTo(ByVal obj As Object) As Integer _
   45.                 Implements IComparable.CompareTo
   46.                 'Un oggetto non-nothing (questo) è sempre maggiore di
   47.                 'un oggetto Nothing (ossia obj)
   48.                 If obj Is Nothing Then
   49.                     Return 1
   50.                 End If
   51.                 'Tenta di convertire obj in Person
   52.                 Dim P As Person = DirectCast(obj, Person)
   53.                 'E restituisce il risultato dell'operazione di
   54.                 'comparazione tra stringhe dei rispettivi nomi
   55.                 Return String.Compare(Me.CompleteName, P.CompleteName)
   56.             End Function
   57.
   58.            Sub New(ByVal FirstName As String, ByVal LastName As String, _
   59.                ByVal BirthDay As Date)
   60.                Me.FirstName = FirstName
   61.                Me.LastName = LastName
   62.                Me._BirthDay = BirthDay
   63.            End Sub
   64.        End Class
   65.
   66.        Sub Main()
   67.            'Crea un array di oggetti Person
   68.            Dim Persons() As Person = _
   69.            {New Person("Marcello", "Rossi", Date.Parse("10/10/1992")), _
   70.            New Person("Guido", "Bianchi", Date.Parse("01/12/1980")), _
   71.            New Person("Bianca", "Brega", Date.Parse("23/06/1960")), _
   72.            New Person("Antonio", "Felice", Date.Parse("16/01/1930"))}
   73.
   74.             'E li ordina, avvalendosi di IComparable.CompareTo
   75.             Array.Sort(Persons)
   76.
   77.             For I As Int16 = 0 To UBound(Persons)
   78.                  Console.WriteLine(Persons(I).CompleteName)
   79.             Next
   80.
   81.            Console.ReadKey()
   82.        End Sub
   83. End    Module

Dato che il nome viene pr ima del congnome, la lista sar à: Antonio, Bianca, Guido, Mar cello.
E se si volesse or dinar e la lista di per sone in base alla data di nascita? Non è possibile definir e due ver sioni di
Compar eTo, poichè devono aver e la stessa signatur e, e cr ear e due metodi che or dinino l'ar r ay sar ebbe scomodo: è qui
che entr a in gioco l'inter faccia ICompar er . Essa r appr esenta un oggetto che deve eseguir e la compar azione tr a due
altr i oggetti, facendo quindi da tramite nell'or dinamento. Dato che Sor t accetta in una delle sue ver sioni un oggetto
ICompar er , è possibile or dinar e una lista di elementi con qualsiasi cr iter io si voglia semplicemente cambiando il
par ametr o. Ad esempio, in questo sor gente scr ivo una classe Bir thDayCompar er che per mette di or dinar e oggetti
Per son in base all'anno di nascita:

   01. Module Module2
   02.     'Questa classe fornisce un metodo per comparare oggetti Person
   03.     'utilizzando la proprietà BirthDay.
   04.     'Per convenzione, classi che implementano IComparer dovrebbero
   05.     'avere un suffisso "Comparer" nel nome.
   06.     'Altra osservazione: se ci sono molte interfacce il cui nome
   07.     'termina in "-able", definendo una caratteristica dell'oggetto
   08.     'che le implementa (ad es.: un oggetto enumerabile,
   09.     'comparabile, distruggibile, ecc...), ce ne sono altrettante
   10.     'che terminano in "-er", indicando, invece, un oggetto
   11.     'che "fa" qualcosa di specifico.
   12.     'Nel nostro esempio, oggetti di tipo BirthDayComparer
   13.     'hanno il solo scopo di comparare altre oggetti
   14.     Class BirthDayComparer
   15.         'Implementa l'interfaccia
   16.         Implements IComparer
   17.
   18.
'Anche questa funzione deve usare parametri object
   19.            Public Function Compare(ByVal x As Object, ByVal y As Object) _
   20.                As Integer Implements System.Collections.IComparer.Compare
   21.                'Se entrambi gli oggetti sono Nothing, allora sono
   22.                'uguali
   23.                If x Is Nothing And y Is Nothing Then
   24.                     Return 0
   25.                ElseIf x Is Nothing Then
   26.                     'Se x è Nothing, y è maggiore
   27.                     Return -1
   28.                ElseIf y Is Nothing Then
   29.                     'Se y è Nothing, x è maggiore
   30.                     Return 1
   31.                Else
   32.                     Dim P1 As Person = DirectCast(x, Person)
   33.                     Dim P2 As Person = DirectCast(y, Person)
   34.                     'Compara le date
   35.                     Return Date.Compare(P1.BirthDay, P2.BirthDay)
   36.                End If
   37.            End Function
   38.        End Class
   39.
   40.        Sub Main()
   41.            Dim Persons() As Person = _
   42.            {New Person("Marcello", "Rossi", Date.Parse("10/10/1992")), _
   43.            New Person("Guido", "Bianchi", Date.Parse("01/12/1980")), _
   44.            New Person("Bianca", "Brega", Date.Parse("23/06/1960")), _
   45.            New Person("Antonio", "Felice", Date.Parse("16/01/1930"))}
   46.
   47.             'Ordina gli elementi utilizzando il nuovo oggetto
   48.             'inizializato in linea BirthDayComparer
   49.             Array.Sort(Persons, New BirthDayComparer())
   50.
   51.             For I As Int16 = 0 To UBound(Persons)
   52.                  Console.WriteLine(Persons(I).CompleteName)
   53.             Next
   54.
   55.            Console.ReadKey()
   56.        End Sub
   57. End    Module

Usando questo meccanismo è possibile or dinar e qualsiasi tipo di lista o collezione fin'or a analizzata (tr anne Sor tedList,
che si or dina automaticamente), in modo semplice e veloce, par ticolar mente utile nell'ambito delle liste visuali a
colonne, come vedr emo nei capitoli sulle ListView .




IDisposable
Nel capitolo sui distr uttor i si è visto come sia possibile utilizzar e il costr utto Using per gestir e un oggetto e poi
distr ugger lo in poche r ighe di codice. Ogni classe che espone il metodo Dispose deve obbligator iamente implementar e
anche l'inter faccia IDisposable, la quale comunica implicitamente che essa ha questa car atter istica. Dato che già molti
esempi sono stati fatti sull'ar gomento distr uttor i, eviter ò di tr attar e nuovamente Dispose in questo capitolo.
A39. Utilizzo delle Interfacce - Parte II

IEnumerable e IEnumerator
Una classe che implementa IEnumer able diventa enum er abile agli occhi del .NET Fr amew or k: ciò significa che si può
usar e su di essa un costr utto For Each per scor r er ne tutti gli elementi. Di solito questo tipo di classe r appr esenta una
collezione di elementi e per questo motivo il suo nome, secondo le convenzioni, dovr ebbe ter minar e in "Collection". Un
motivo per costr uir e una nuova collezione al posto di usar e le classiche liste può consister e nel voler definir e nuovi
metodi o pr opr ietà per modificar la. Ad esempio, si potr ebbe scr iver e una nuova classe Per sonCollection che per mette
di r aggr uppar e ed enumer ar e le per sone ivi contenute e magar i calcolar e anche l'età media.

   01. Module Module1
   02.     Class PersonCollection
   03.         Implements IEnumerable
   04.         'La lista delle persone
   05.         Private _Persons As New ArrayList
   06.
   07.         'Teoricamente, si dovrebbero ridefinire tutti i metodi
   08.         'di una collection comune, ma per mancanza di spazio,
   09.         'accontentiamoci
   10.         Public ReadOnly Property Persons() As ArrayList
   11.              Get
   12.                  Return _Persons
   13.              End Get
   14.         End Property
   15.
   16.         'Restituisce l'età media. TimeSpan è una struttura che si
   17.         'ottiene sottraendo fra loro due oggetti date e indica un
   18.         'intervallo di tempo
   19.         Public ReadOnly Property AverageAge() As String
   20.              Get
   21.                  'Variabile temporanea
   22.                  Dim Temp As TimeSpan
   23.                  'Somma tutte le età
   24.                  For Each P As Person In _Persons
   25.                       Temp = Temp.Add(Date.Now - P.BirthDay)
   26.                  Next
   27.                  'Divide per il numero di persone
   28.                  Temp = TimeSpan.FromSeconds(Temp.TotalSeconds / _Persons.Count)
   29.
   30.                  'Dato che TimeSpan può contenere al massimo
   31.                  'giorni e non mesi o anni, dobbiamo fare qualche
   32.                  'calcolo
   33.                  Dim Years As Int32
   34.                  'Gli anni, ossia il numero dei giorni fratto 365
   35.                  'Divisione intera
   36.                  Years = Temp.TotalDays  365
   37.                  'Sottrae gli anni: da notare che
   38.                  '(Temp.TotalDays  365) * 365) non è un passaggio
   39.                  'inutile. Infatti, per determinare il numero di
   40.                  'giorni che rimangono, bisogna prendere la
   41.                  'differenza tra il numero totale di giorni e
   42.                  'il multiplo più vicino di 365
   43.                  Temp = _
   44.                  Temp.Subtract(TimeSpan.FromDays((Temp.TotalDays  365) * 365))
   45.
   46.                  Return Years & " anni e " & CInt(Temp.TotalDays) & " giorni"
   47.              End Get
   48.         End Property
   49.
   50.         'La funzione GetEnumerator restituisce un oggetto di tipo
   51.         'IEnumerator che vedremo fra breve: esso permette di
   52.         'scorrere ogni elemento ordinatamente, dall'inizio
   53.         'alla fine. In questo caso, poichè non abbiamo ancora
   54.         'analizzato questa interfaccia, ci limitiamo a restituisce
   55.
'l'IEnumerator predefinito per un ArrayList
   56.            Public Function GetEnumerator() As IEnumerator _
   57.                Implements IEnumerable.GetEnumerator
   58.                Return _Persons.GetEnumerator
   59.            End Function
   60.        End Class
   61.
   62.        Sub Main()
   63.            Dim Persons As New PersonCollection
   64.            With Persons.Persons
   65.                .Add(New Person("Marcello", "Rossi", Date.Parse("10/10/1992")))
   66.                .Add(New Person("Guido", "Bianchi", Date.Parse("01/12/1980")))
   67.                .Add(New Person("Bianca", "Brega", Date.Parse("23/06/1960")))
   68.                .Add(New Person("Antonio", "Felice", Date.Parse("16/01/1930")))
   69.            End With
   70.
   71.             For Each P As Person In Persons
   72.                  Console.WriteLine(P.CompleteName)
   73.             Next
   74.             Console.WriteLine("Età media: " & Persons.AverageAge)
   75.             '> 41 anni e 253 giorni
   76.
   77.            Console.ReadKey()
   78.        End Sub
   79. End    Module

Come si vede dall'esempio, è lecito usar e Per sonCollection nel costr utto For Each: l'iter azione viene svolta dal pr imo
elemento inser ito all'ultimo, poichè l'IEnumer ator dell'Ar r ayList oper a in questo modo. Tuttavia, cr eando una diver sa
classe che implementa IEnumer ator si può scor r er e la collezione in qualsiasi modo: dal più giovane al più vecchio, al
pr imo all'ultimo, dall'ultimo al pr imo, a caso, saltandone alcuni, a seconda dell'or a di cr eazione ecceter a. Quindi in
questo modo si può per sonalizzar e la pr opr ia collezione.
Ciò che occor r e per costr uir e cor r ettamente una classe basata su IEnumer ator sono tr e metodi fondamentali definiti
nell'inter faccia: MoveNex t è una funzione che r estituisce Tr ue se esiste un elemento successivo nella collezione (e in
questo caso lo imposta come elemento cor r ente), altr imenti False; Cur r ent è una pr opr ietà ReadOnly di tipo Object che
r estituisce l'elemento cor r ente; Reset è una pr ocedur a senza par ametr i che r esetta il contator e e fa iniziar e il ciclo
daccapo. Quest'ultimo metodo non viene mai utilizzato, ma nell'esempio che segue ne scr iver ò comunque il cor po:

 001. Module Module1
 002.     Class PersonCollection
 003.         Implements IEnumerable
 004.         'La lista delle persone
 005.         Private _Persons As New ArrayList
 006.
 007.         'Questa classe ha il compito di scorrere ordinatamente gli
 008.         'elementi della lista, dal più vecchio al più giovane
 009.         Private Class PersonAgeEnumerator
 010.              Implements IEnumerator
 011.
 012.              'Per enumerare gli elementi, la classe ha bisogno di un
 013.              'riferimento ad essi: perciò si deve dichiarare ancora
 014.              'un nuovo ArrayList di Person. Questo passaggio è
 015.              'facoltativo nelle classi nidificate come questa, ma è
 016.              'obbligatorio in tutti gli altri casi
 017.              Private Persons As New ArrayList
 018.              'Per scorrere la collezione, si userà un comune indice
 019.              Private Index As Int32
 020.
 021.              'Essendo una normalissima classe, è lecito definire un
 022.              'costruttore, che in questo caso inizializza la
 023.              'collezione
 024.              Sub New(ByVal Persons As ArrayList)
 025.                  'Ricordate: poichè ArrayList deriva da Object, è
 026.                  'un tipo reference. Assegnare Persons a Me.Persons
 027.                  'equivale ad assegnarne l'indirizzo e quindi ogni
 028.                  'modifica su questo arraylist privato si rifletterà
 029.                  'su quello passato come parametro. Si può
 030.                  'evitare questo problema clonando la lista
 031.
Me.Persons = Persons.Clone
032.                'MoveNext viene richiamato prima di usare Current,
033.                'quindi Index verrà incrementata subito.
034.                'Per farla diventare 0 al primo ciclo la si
035.                'deve impostare a -1
036.                Index = -1
037.
038.               'Dato che l'enumeratore deve scorrere la lista
039.               'secondo l'anno di nascita, bisogna prima ordinarla
040.               Me.Persons.Sort(New BirthDayComparer)
041.           End Sub
042.
043.           'Restituisce l'elemento corrente
044.           Public ReadOnly Property Current() As Object _
045.               Implements System.Collections.IEnumerator.Current
046.               Get
047.                   Return Persons(Index)
048.               End Get
049.           End Property
050.
051.           'Restituisce True se esiste l'elemento successivo e lo
052.           'imposta, altrimenti False
053.           Public Function MoveNext() As Boolean _
054.               Implements System.Collections.IEnumerator.MoveNext
055.               If Index = Persons.Count - 1 Then
056.                    Return False
057.               Else
058.                    Index += 1
059.                    Return True
060.               End If
061.           End Function
062.
063.           'Resetta il ciclo
064.           Public Sub Reset() _
065.               Implements System.Collections.IEnumerator.Reset
066.               Index = -1
067.           End Sub
068.       End Class
069.
070.       Public ReadOnly Property Persons() As ArrayList
071.           Get
072.               Return _Persons
073.           End Get
074.       End Property
075.
076.       Public ReadOnly Property AverageAge() As String
077.           Get
078.               Dim Temp As TimeSpan
079.               For Each P As Person In _Persons
080.                    Temp = Temp.Add(Date.Now - P.BirthDay)
081.               Next
082.               Temp = TimeSpan.FromSeconds(Temp.TotalSeconds / _Persons.Count)
083.
084.               Dim Years As Int32
085.               Years = Temp.TotalDays  365
086.               Temp = _
087.               Temp.Subtract(TimeSpan.FromDays((Temp.TotalDays  365) * 365))
088.               Return Years & " anni e " & CInt(Temp.TotalDays) & " giorni"
089.           End Get
090.       End Property
091.
092.       'La funzione GetEnumerator restituisce ora un oggetto di
093.       'tipo IEnumerator che abbiamo definito in una classe
094.       'nidificata e il ciclo For Each scorrerà quindi
095.       'dal più vecchio al più giovane
096.       Public Function GetEnumerator() As IEnumerator _
097.           Implements IEnumerable.GetEnumerator
098.           Return New PersonAgeEnumerator(_Persons)
099.       End Function
100.   End Class
101.
102.   Sub Main()
103.
Dim Persons As New PersonCollection
 104.         With Persons.Persons
 105.              .Add(New Person("Marcello", "Rossi", Date.Parse("10/10/1992")))
 106.              .Add(New Person("Guido", "Bianchi", Date.Parse("01/12/1980")))
 107.              .Add(New Person("Bianca", "Brega", Date.Parse("23/06/1960")))
 108.              .Add(New Person("Antonio", "Felice", Date.Parse("16/01/1930")))
 109.         End With
 110.
 111.         'Enumera ora per data di nascita, ma senza modificare
 112.         'l'ordine degli elementi
 113.         For Each P As Person In Persons
 114.              Console.WriteLine(P.BirthDay.ToShortDateString & ", " & _
 115.                  P.CompleteName)
 116.         Next
 117.
 118.         'Stampa la prima persona, dimostrando che l'ordine
 119.         'della lista è intatto
 120.         Console.WriteLine(Persons.Persons(0).CompleteName)
 121.
 122.         Console.ReadKey()
 123.     End Sub
 124. End Module




ICloneable
Come si è visto nell'esempio appena scr itto, si pr esentano alcune difficoltà nel manipolar e oggetti di tipo Refer ence, in
quanto l'assegnazione di questi cr eer ebbe due istanze che puntano allo stesso oggetto piuttosto che due oggetti
distinti. È in questo tipo di casi che il metodo Clone e l'inter faccia ICloneable assumono un gr an valor e. Il pr imo
per mette di eseguir e una copia dell'oggetto, cr eando un nuo v o og g etto a tutti gli effetti, totalmente disgiunto da
quello di par tenza: questo per mette di non intaccar ne accidentalmente l'integr ità. Una dimostr azione:

   01. Module Esempio
   02.     Sub Main()
   03.         'Il tipo ArrayList espone il metodo Clone
   04.         Dim S1 As New ArrayList
   05.         Dim S2 As New ArrayList
   06.
   07.         S2 = S1
   08.
   09.         'Verifica che S1 e S2 puntano lo stesso oggetto
   10.         Console.WriteLine(S1 Is S2)
   11.         '> True
   12.
   13.         'Clona l'oggetto
   14.         S2 = S1.Clone
   15.         'Verifica che ora S2 referenzia un oggetto differente,
   16.         'ma di valore identico a S1
   17.         Console.WriteLine(S1 Is S2)
   18.         '> False
   19.
   20.         Console.ReadKey()
   21.     End Sub
   22. End Module

L'inter faccia, invece, come accadeva per IEnumer able e ICompar able, indica al .NET Fr amew or k che l'oggetto è clonabile.
Questo codice mostr a la funzione Close all'oper a:

   01. Module Module1
   02.     Class UnOggetto
   03.         Implements ICloneable
   04.         Private _Campo As Int32
   05.
   06.         Public Property Campo() As Int32
   07.              Get
   08.                  Return _Campo
   09.              End Get
   10.
Set(ByVal Value As Int32)
   11.                      _Campo = Value
   12.                  End Set
   13.              End Property
   14.
   15.              'Restituisce una copia dell'oggetto
   16.              Public Function Clone() As Object Implements ICloneable.Clone
   17.                  'La funzione Protected MemberwiseClone, ereditata da
   18.                  'Object, esegue una copia superficiale dell'oggetto,
   19.                  'come spiegherò fra poco: è quello che
   20.                  'serve in questo caso
   21.                  Return Me.MemberwiseClone
   22.              End Function
   23.
   24.              'L'operatore = permette di definire de due oggetti hanno un
   25.              'valore uguale
   26.              Shared Operator =(ByVal O1 As UnOggetto, ByVal O2 As UnOggetto) As _
   27.                  Boolean
   28.                  Return O1.Campo = O2.Campo
   29.              End Operator
   30.
   31.            Shared Operator <>(ByVal O1 As UnOggetto, ByVal O2 As UnOggetto) As _
   32.                Boolean
   33.                Return Not (O1 = O2)
   34.            End Operator
   35.        End Class
   36.
   37.        Sub Main()
   38.            Dim O1 As New UnOggetto
   39.            Dim O2 As UnOggetto = O1.Clone
   40.
   41.              'I due oggetti NON sono lo stesso oggetto: il secondo
   42.              'è solo una copia, disgiunta da O1
   43.              Console.WriteLine(O1 Is O2)
   44.              '> False
   45.
   46.              'Tuttavia hanno lo stesso identico valore
   47.              Console.WriteLine(O1 = O2)
   48.              '> True
   49.
   50.            Console.ReadKey()
   51.        End Sub
   52. End    Module

Or a, è impor tante distinguer e due tipi di copia: quella Shallo w e quella Deep. La pr ima cr ea una copia super ficiale
dell'oggetto, ossia si limita a clonar e tutti i campi. La seconda, invece, è in gr ado di eseguir e questa oper azione anche
su tutti gli oggetti inter ni e i r ifer imenti ad altr i oggetti: così, se si ha una classe Per son che al pr opr io inter no
contiene il campo Childer n, di tipo ar r ay di Per son, la copia Shallow cr eer à un clone della classe in cui Childr en punta
sempr e allo stesso oggetto, mentr e una copia Deep cloner à anche Childr en. Si nota meglio con un gr afico: le fr ecce
ver di indicano oggetti clonati, mentr e la fr eccia ar ancio si r ifer isce allo stesso oggetto.
Non è possibile specificar e nella dichiar azione di Clone quale tipo di copia ver r à eseguita, quindi tutto viene lasciato
all'ar bitr io del pr ogr ammator e.
Dal codice sopr a scr itto, si nota che Clone deve r estituir e per for za un tipo Object. In questo caso, il metodo si dice a
tipizzazio ne debo le, ossia ser ve un oper ator e di cast per conver tir lo nel tipo desider ato; per cr ear ne una ver sione
a tipizzazio ne fo r te è necessar io scr iver e una funzione che r estituisca, ad esempio, un tipo Per son. Quest'ultima
ver sione avr à il nome Clone, mentr e quella che implementa ICloneable.Clone() avr à un nome differ ente, come CloneMe().
A40. Le librerie di classi

Cer te volte accade che non si voglia scr iver e un pr ogr amma, ma piuttosto un insieme di utilità per gestir e un cer to
tipo di infor mazioni. In questi casi, si scr ive una libr er ia di classi, ossia un insieme, appunto, di namespace, classi e tipi
che ser vono ad un deter minato scopo. Potete tr ovar e un esempio tr a i sor genti della sezione Dow nload: mi r ifer isco a
Mp3 Deep Analyzer , una libr er ia di classi che for nisce str umenti per legger e e scr iver e tag ID3 nei file mp3 (per
ulter ior i infor mazioni sull'ar gomento, consultar e la sezione FFS). Con quel pr ogetto non ho voluto scr iver e un
pr ogr amma che svolgesse quei compiti, per chè da solo sar ebe stato poco utile, ma piuttosto metter e a disposizione
anche agli altr i pr ogr ammator i un modo semplice per manipolar e quel tipo di infor mazioni. Così facendo, uno potr ebbe
usar e le funzioni di quella libr er ia in un pr opr io pr ogr amma. Le libr er ie, quindi, sono un inventar io di classi scr itto
appositamente per esser e r iusato.




Creare una nuova libreria di c lassi
Per cr ear e una libr er ia, cliccate su File > New Pr oject e, invece si selezionar e la solita "Console Application",
selezionate "Class Libr ar y". Una volta inizializzato il pr ogetto, vi tr over ete di fr onte a un codice pr eimpostato diver so
dal solito:

    1. Class Class1
    2.
    3. End Class

Noter ete, inoltr e, che, pr emendo F5, vi ver r à comunicato un er r or e: non stiamo scr ivendo un pr ogr amma, infatti, ma
solo una libr er ia, che quindi non può esser e "eseguita" (non avr ebbe senso neanche pensar e di far lo).
Per far e un esempio, significativo, r ipr endiamo il codice di esempio del capitolo sulle inter facce e scor por iamolo dal
pr ogr amma, estr aendone solo le classi:

  001. Namespace PostalManagement
  002.
  003.     Public Interface IIdentifiable
  004.         ReadOnly Property Id() As Int32
  005.         Function ToString() As String
  006.     End Interface
  007.
  008.     Public Class Pack
  009.         Implements IIdentifiable
  010.
  011.         Private _Id As Int32
  012.         Private _Destination As String
  013.         Private _Dimensions(2) As Single
  014.
  015.         Public ReadOnly Property Id() As Integer Implements IIdentifiable.Id
  016.              Get
  017.                  Return _Id
  018.              End Get
  019.         End Property
  020.
  021.         Public Property Destination() As String
  022.              Get
  023.                  Return _Destination
  024.              End Get
  025.              Set(ByVal value As String)
  026.                  _Destination = value
  027.              End Set
  028.         End Property
  029.
  030.         Public Property Dimensions(ByVal Index As Int32) As Single
  031.              Get
  032.
If (Index >= 0) And (Index < 3) Then
033.                    Return _Dimensions(Index)
034.               Else
035.                    Throw New IndexOutOfRangeException()
036.               End If
037.           End Get
038.           Set(ByVal value As Single)
039.               If (Index >= 0) And (Index < 3) Then
040.                    _Dimensions(Index) = value
041.               Else
042.                    Throw New IndexOutOfRangeException()
043.               End If
044.           End Set
045.       End Property
046.
047.       Public Sub New(ByVal Id As Int32)
048.           _Id = Id
049.       End Sub
050.
051.       Public Overrides Function ToString() As String Implements IIdentifiable.ToString
052.           Return String.Format("{0:0000}: Pacco {1}x{2}x{3}, Destinazione: {4}", _
053.               Me.Id, Me.Dimensions(0), Me.Dimensions(1), _
054.               Me.Dimensions(2), Me.Destination)
055.       End Function
056.   End Class
057.
058.   Public Class Telegram
059.       Implements IIdentifiable
060.
061.       Private _Id As Int32
062.       Private _Recipient As String
063.       Private _Message As String
064.
065.       Public ReadOnly Property Id() As Integer Implements IIdentifiable.Id
066.           Get
067.               Return _Id
068.           End Get
069.       End Property
070.
071.       Public Property Recipient() As String
072.           Get
073.               Return _Recipient
074.           End Get
075.           Set(ByVal value As String)
076.               _Recipient = value
077.           End Set
078.       End Property
079.
080.       Public Property Message() As String
081.           Get
082.               Return _Message
083.           End Get
084.           Set(ByVal value As String)
085.               _Message = value
086.           End Set
087.       End Property
088.
089.       Public Sub New(ByVal Id As Int32)
090.           _Id = Id
091.       End Sub
092.
093.       Public Overrides Function ToString() As String Implements IIdentifiable.ToString
094.           Return String.Format("{0:0000}: Telegramma per {1} ; Messaggio = {2}", _
095.               Me.Id, Me.Recipient, Me.Message)
096.       End Function
097.   End Class
098.
099.   Public Class MoneyOrder
100.       Implements IIdentifiable
101.
102.       Private _Id As Int32
103.       Private _Recipient As String
104.
Private _Money As Single
105.
106.       Public ReadOnly Property Id() As Integer Implements IIdentifiable.Id
107.           Get
108.               Return _Id
109.           End Get
110.       End Property
111.
112.       Public Property Recipient() As String
113.           Get
114.               Return _Recipient
115.           End Get
116.           Set(ByVal value As String)
117.               _Recipient = value
118.           End Set
119.       End Property
120.
121.       Public Property Money() As Single
122.           Get
123.               Return _Money
124.           End Get
125.           Set(ByVal value As Single)
126.               _Money = value
127.           End Set
128.       End Property
129.
130.       Public Sub New(ByVal Id As Int32)
131.           _Id = Id
132.       End Sub
133.
134.       Public Overrides Function ToString() As String Implements IIdentifiable.ToString
135.           Return String.Format("{0:0000}: Vaglia postale per {1} ; Ammontare = {2}€", _
136.               Me.Id, Me.Recipient, Me.Money)
137.       End Function
138.   End Class
139.
140.   Public Class PostalProcessor
141.       Public Delegate Function IdSelector(ByVal Id As Int32) As Boolean
142.
143.       Private _StorageCapacity As Int32
144.       Private _NextId As Int32 = 0
145.       Private Storage() As IIdentifiable
146.
147.       Public Property StorageCapacity() As Int32
148.           Get
149.               Return _StorageCapacity
150.           End Get
151.           Set(ByVal value As Int32)
152.               _StorageCapacity = value
153.               ReDim Preserve Storage(value)
154.           End Set
155.       End Property
156.
157.       Public Property Item(ByVal Index As Int32) As IIdentifiable
158.           Get
159.               If (Index >= 0) And (Index < Storage.Length) Then
160.                    Return Me.Storage(Index)
161.               Else
162.                    Throw New IndexOutOfRangeException()
163.               End If
164.           End Get
165.           Set(ByVal value As IIdentifiable)
166.               If (Index >= 0) And (Index < Storage.Length) Then
167.                    Me.Storage(Index) = value
168.               Else
169.                    Throw New IndexOutOfRangeException()
170.               End If
171.           End Set
172.       End Property
173.
174.       Public ReadOnly Property FirstPlaceAvailable() As Int32
175.           Get
176.
For I As Int32 = 0 To Me.Storage.Length - 1
177.                    If Me.Storage(I) Is Nothing Then
178.                        Return I
179.                    End If
180.               Next
181.               Return (-1)
182.           End Get
183.       End Property
184.
185.       Public ReadOnly Property NextId() As Int32
186.           Get
187.               _NextId += 1
188.               Return _NextId
189.           End Get
190.       End Property
191.
192.
193.       Public Sub New(ByVal Items() As IIdentifiable)
194.           Me.Storage = Items
195.           _StorageCapacity = Items.Length
196.       End Sub
197.
198.       Public Sub New(ByVal Capacity As Int32)
199.           Me.StorageCapacity = Capacity
200.       End Sub
201.
202.       Public Sub PrintByFilter(ByVal Selector As IdSelector)
203.           For Each K As IIdentifiable In Storage
204.                If K Is Nothing Then
205.                    Continue For
206.                End If
207.                If Selector.Invoke(K.Id) Then
208.                    Console.WriteLine(K.ToString())
209.                End If
210.           Next
211.       End Sub
212.
213.       Public Sub PrintById(ByVal Id As Int32)
214.           For Each K As IIdentifiable In Storage
215.                If K Is Nothing Then
216.                    Continue For
217.                End If
218.                If K.Id = Id Then
219.                    Console.WriteLine(K.ToString())
220.                    Exit For
221.                End If
222.           Next
223.       End Sub
224.
225.       Public Function SearchItems(ByVal Str As String) As Int32()
226.           Dim Temp As New ArrayList
227.
228.           For Each K As IIdentifiable In Storage
229.                If K Is Nothing Then
230.                    Continue For
231.                End If
232.                If K.ToString().Contains(Str) Then
233.                    Temp.Add(K.Id)
234.                End If
235.           Next
236.
237.           Dim Result(Temp.Count - 1) As Int32
238.           For I As Int32 = 0 To Temp.Count - 1
239.                Result(I) = Temp(I)
240.           Next
241.
242.           Temp.Clear()
243.           Temp = Nothing
244.
245.           Return Result
246.       End Function
247.   End Class
248.
249. End Namespace

Notate che ho r acchiuso tutto in un namespace e ho anche messo lo scope Public a tutti i membr i non pr ivati. Se non
avessi messo Public, infatti, i membr i senza scope sar ebber o stati automaticamente mar cati con Fr iend. Suppongo vi
r icor diate che Fr iend r ende accessibile un membr o solo dalle classi appar tenenti allo stesso assembly (in questo caso,
allo stesso pr ogetto): questo equivale a dir e che tutti i membr i Fr iend non sar anno accessibili al di fuor i della libr er ia e
quindi chi la user à non potr à acceder vi. Ovviamente, dato che il tutto si basa sull'inter faccia IIdentifiable non potevo
pr ecluder ne l'accesso agli utenti della libr er ia, e allo stesso modo i costr uttor i senza Public sar ebber o stati inaccessibili
e non si sar ebbe potuto istanziar e alcun oggetto. Ecco che concludiamo la lista di tutti gli specificator i di accesso con
Pr otected Fr iend: un membr o dichiar ato Pr otected Fr iend sar à accessibile solo ai membr i delle classi der ivate
appar tenenti allo stesso assembly. Per r icapitolar vi tutti gli scope, ecco uno schema dove le fr ecce ver di indicano gli
unici accessi consentiti:




Importare la libreria in un altro progetto
Una volta compilata la libr er ia, al posto dell'eseguibile, nella sottocar tella binRelease del vostr o pr ogetto, si tr over à un
file con estensione *.dll. Per usar e le classi contenute in questa libr er ia (o r ifer im ento , nome tecnico che si confonde
spesso con i nomi comuni), bisogna impor tar la nel pr ogetto cor r ente. Per far e questo, nel Solution Ex plor er (la finestr a
che mostr a tutti gli elementi del pr ogetto) cliccate col pulsante destr o sul nome del pr ogetto e selezionate "Add
Refer ence" ("Aggiungi r ifer imento"):
Quindi r ecatevi fino alla car tella della libr er ia cr eata, selezionate il file e pr emete OK (nell'esempio c'è una delle libr er ie
che ho scr itto e che potete tr ovar e nella sezione Dow nload):




or a il r ifer imento è stato aggiunto al pr ogetto, ma non potete ancor a usar e le classi della libr er ia. Pr ima dovete "dir e"
al compilator e che nel codice che sta per esser e letto potr este far e r ifer imento ad esse. Questo si fa "impor tando" il
namespace, con il codice:

    1. Imports [Nome Libreria].[Nome Namespace]

Io ho chiamato la libr er ia con lo stesso nome del namespace, ma potete usar e anche nomi diver si, poiché in una libr er ia
ci possono esser e tanti namespace differ enti:

    1. Imports PostalManagement.PostalManagement

Impor ts è una "dir ettiva", ossia non costituisce codice eseguibile, ma infor ma il compilator e che alcune classi del
sor gente potr ebber o appar tener e a questo namespace (omettendo questa r iga, dovr ete scr iver e ogni volta
PostalManagement.Pack, ad esempio, per usar e la classe Pack, per chè altr imenti il compilator e non sar ebbe in gr ado di
tr ovar e il name Pack nel contesto cor r ente). Ecco un esempio:

   01. Imports PostalManagement.PostalManagement
   02.
   03. Module Module1
   04.
   05.     Sub Main()
   06.         Dim P As New PostalProcessor(10)
   07.         Dim Pk As New Pack(P.NextId)
   08.
   09.         P.Item(P.FirstPlaceAvailable) = Pk
   10.         '...
   11.     End Sub
   12.
   13. End Module

che equivale a:

   01. Module Module1
   02.
   03.     Sub Main()
   04.         Dim P As New PostalManagement.PostalManagement.PostalProcessor(10)
   05.         Dim Pk As New PostalManagement.PostalManagement.Pack(P.NextId)
   06.
   07.         P.Item(P.FirstPlaceAvailable) = Pk
   08.         '...
   09.     End Sub
   10.
   11. End Module

Nella scheda ".NET" che vedete nella seconda immagine di sopr a, ci sono molte libr er ie facenti par te del Fr amew or k che
user emo nelle pr ossime sezioni della guida.
A41. I Generics - Parte I

Panoramic a sui Generic s
I Gener ics sono un concetto molto impor tante per quanto r iguar da la pr ogr ammazione ad oggetti, specialmente in
.NET e, se fino ad or a non ne conoscevate nemmeno l'esistenza, d'or a in poi non potr ete far ne a meno. Cominciamo col
far e un par agone per esemplificar e il concetto di gener ics. Ammettiamo di dichiar ar e una var iabile I di tipo Int32: in
questa var iabile potr emo immagazzinar e qualsiasi infor mazione che consista di un numer o inter o r appr esentabile su
32 bit. Possiamo dir e, quindi, che il tipo Int32 costituisce un'astr azione di tutti i numer i inter i esistenti da
-2'147'483'648 a +2'147'483'647. Analogamente un tipo g ener ic può assumer e come valor e un altr o tipo e, quindi, astr ae
tutti i possibili tipi usabili in quella classe/metodo/pr opr ietà ecceter a. È come dir e: definiamo la funzione Somma(A, B),
dove A e B sono di un tipo T che non conosciamo. Quando utilizziamo la funzione Somma, oltr e a specificar e i par ametr i
r ichiesti, dobbiamo anche "dir e" di quale tipo essi siano (ossia immetter e in T non un valor e ma un tipo): in questo
modo, definendo un solo metodo, potr emo eseguir e somme tr a inter i, decimali, str inghe, date, file, classi, ecceter a...
In VB.NET, l'oper azione di specificar e un tipo per un entità gener ic si attua con questa sintassi:

    1. [NomeEntità](Of [NomeTipo])

Dato i gener ics di possono applicar e ad ogni entità del .NET (metodi, classi, pr opr ietà, str uttur e, inter facce, delegate,
ecceter a...), ho scr itto solo "NomeEntità" per indicar e il nome del tar get a cui si applicano. Il pr ossimo esempio mostr a
come i gener ics, usati sulle liste, possano aumentar e di molto le per for mance di un pr ogr amma.
La collezione Ar r ayList, molte volte impiegata negli esempi dei pr ecedeti capitoli, per mette di immagazzinar e
qualsiasi tipo di dato, memor izzando, quindi, var iabili di tipo Object. Come già detto all'inizio del cor so, l'uso di Object
compor ta molti r ischi sia a livello di pr estazioni, dovute alle continue oper azioni di box ing e unbox ing (e le gar bage
collection che ne conseguono, data la cr eazione di molti oggetti tempor anei), sia a livello di cor r ettezza del codice. Un
esempio di questo ultimo caso si ver ifica quando si tenta di scor r er e un Ar r ayList mediante un ciclo For Each e si
incontr a un r ecor d che non è del tipo specificato, ad esempio:

   01.   Dim A As New ArrayList
   02.   A.Add(2)
   03.   A.Add(3)
   04.   A.Add("C")
   05.   'A run-time, sarà lanciata un'eccezione inerente il cast
   06.   'poichè la stringa "C" non è del tipo specificato
   07.   'nel blocco For Each
   08.   For Each V As Int32 In A
   09.     Console.WriteLine(V)
   10.   Next

Infatti, se l'applicazione dovesse er r oneamente inser ir e una str inga al posto di un numer o inter o, non ver r ebbe
gener ato nessun er r or e, ma si ver ificher ebbe un'eccezione successivamente. Altr a pr oblematica legata all'uso di
collezioni a tipizzazione debole (ossia che r egistr ano gener ici oggetti Object, come l'Ar r ayList, l'HashTable o la
Sor tedList) è dovuta al fatto che sia necessar ia una conver sione esplicita di tipo nell'uso dei suoi elementi, almeno nella
maggior anza dei casi. La soluzione adottata da un pr ogr ammator e che non conoscesse i gener ics per r isolver e tali
inconvenienti sar ebbe quella di cr ear e una nuova lista, ex novo, er editandola da un tipo base come CollectionBase e
r idefinendone tutti i metodi (Add, Remove, Index Of ecc...). L'uso dei Gener ics, invece, r ende molto più veloce e meno
insidiosa la scr ittur a di un codice r obusto e solido nell'ambito non solo delle collezioni, ma di molti altr i ar gomenti. Ecco
un esempio di come implementar e una soluzione basata sui Gener ics:

   01.   'La lista accetta solo oggetti di tipo Int32: per questo motivo
   02.   'si genera un'eccezione quando si tenta di inserirvi elementi di
   03.   'tipo diverso e la velocità di elaborazione aumenta!
   04.   Dim A As New List(Of Int32)
   05.
A.Add(1)
   06.   A.Add(4)
   07.   A.Add(8)
   08.   'A.Add("C") '<- Impossibile
   09.   For Each V As Int32 In A
   10.     Console.WriteLine(V)
   11.   Next

E questa è una dimostr azione dell'incr emento delle pr estazioni:

   01. Module Module1
   02.     Sub Main()
   03.         Dim TipDebole As New ArrayList
   04.         Dim TipForte As New List(Of Int32)
   05.         Dim S As New Stopwatch
   06.
   07.         'Cronometra le operazioni su ArrayList
   08.         S.Start()
   09.         For I As Int32 = 1 To 1000000
   10.              TipDebole.Add(I)
   11.         Next
   12.         S.Stop()
   13.         Console.WriteLine(S.ElapsedMilliseconds & _
   14.              " millisecondi per ArrayList!")
   15.
   16.         'Cronometra le operazioni su List
   17.         S.Reset()
   18.         S.Start()
   19.         For I As Int32 = 1 To 1000000
   20.              TipForte.Add(I)
   21.         Next
   22.         S.Stop()
   23.         Console.WriteLine(S.ElapsedMilliseconds & _
   24.              " millisecondi per List(Of T)!")
   25.
   26.         Console.ReadKey()
   27.     End Sub
   28. End Module

Sul mio computer por tatile l'Ar r ayList impiega 197ms, mentr e List 33ms: i Gener ics incr ementano la velocità di 6
volte!
Oltr e a List, esistono anche altr e collezioni gener ic, ossia Dictionar y e Sor tedDictionar y: tutti questi sono la ver sione a
tipizzazione for te delle nor mali collezioni già viste. Ma or a vediamo come scr iver e nuove classi e metodi gener ic.




Generic s Standard
Una volta impar ato a dichiar ar e e scr iver e entità gener ics, sar à anche altr ettanto semplice usar e quelli esistenti,
per ciò iniziamo col dar e le pr ime infor mazioni su come scr iver e, ad esempio, una classe gener ics.
Una classe gener ics si r ifer isce ad un qualsiasi tipo T che non possiamo conoscer e al momento dela scr ittur a del codice,
ma che il pr ogr ammator e specificher à all'atto di dichiar azione di un oggetto r appr esentato da questa classe. Il fatto
che essa sia di tipo gener ico indica che anche i suoi membr i, molto pr obabilmente, avr anno lo stesso tipo: più nello
specifico, potr ebber o esser ci campi di tipo T e metodi che lavor ano su oggetti di tipo T. Se nessuna di queste due
condizioni è ver ificata, allor a non ha senso scr iver e una classe gener ics. Ma iniziamo col veder e un semplice esempio:

 001. Module Module1
 002.     'Collezione generica che contiene un qualsiasi tipo T di
 003.     'oggetto. T si dice "tipo generic aperto"
 004.     Class Collection(Of T)
 005.         'Per ora limitiamoci a dichiarare un array interno
 006.         'alla classe.
 007.         'Vedremo in seguito che è possibile ereditare da
 008.         'una collezione generics già esistente.
 009.         'Notate che la variabile è di tipo T: una volta che
 010.         'abbiamo dichiarato la classe come generics su un tipo T,
 011.
'è come se avessimo "dichiarato" l'esistenza di T
012.       'come tipo fittizio.
013.       Private _Values() As T
014.
015.       'Restituisce l'Index-esimo elemento di Values (anch'esso
016.       'è di tipo T)
017.       Public Property Values(ByVal Index As Int32) As T
018.           Get
019.               If (Index >= 0) And (Index < _Values.Length) Then
020.                    Return _Values(Index)
021.               Else
022.                    Throw New IndexOutOfRangeException()
023.               End If
024.           End Get
025.           Set(ByVal value As T)
026.               If (Index >= 0) And (Index < _Values.Length) Then
027.                    _Values(Index) = value
028.               Else
029.                    Throw New IndexOutOfRangeException()
030.               End If
031.           End Set
032.       End Property
033.
034.       'Proprietà che restituiscono il primo e l'ultimo
035.       'elemento della collezione
036.       Public ReadOnly Property First() As T
037.           Get
038.               Return _Values(0)
039.           End Get
040.       End Property
041.
042.       Public ReadOnly Property Last() As T
043.           Get
044.               Return _Values(_Values.Length - 1)
045.           End Get
046.       End Property
047.
048.       'Stampa tutti i valori presenti nella collezione a schermo.
049.       'Su un tipo generic è sempre possibile usare
050.       'l'operatore Is (ed il suo corrispettivo IsNot) e
051.       'confrontarlo con Nothing. Se si tratta di un tipo value
052.       'l'uguaglianza con Nothing sarà sempre falsa.
053.       Public Sub PrintAll()
054.           For Each V As T In _Values
055.                If V IsNot Nothing Then
056.                    Console.WriteLine(V.ToString())
057.                End If
058.           Next
059.       End Sub
060.
061.       'Inizializza la collezione con Count elementi, tutti del
062.       'valore DefaultValue
063.       Sub New(ByVal Count As Int32, ByVal DefaultValue As T)
064.           If Count < 1 Then
065.               Throw New ArgumentOutOfRangeException()
066.           End If
067.
068.           ReDim _Values(Count - 1)
069.
070.           For I As Int32 = 0 To _Values.Length - 1
071.                _Values(I) = DefaultValue
072.           Next
073.       End Sub
074.
075.   End Class
076.
077.   Sub Main()
078.       'Dichiara quattro variabili contenenti quattro nuovi
079.       'oggetti Collection. Ognuno di questi, però,
080.       'è specifico per un solo tipo che decidiamo
081.       'noi durante la dichiarazione. String, Int32, Date
082.       'e Person, ossia i tipi che stiamo inserendo nel tipo
083.
'generico T, si dicono "tipi generic collegati",
  084.         'poiché collegano il tipo fittizio T con un
  085.         'reale tipo esistente
  086.         Dim Strings As New Collection(Of String)(10, "null")
  087.         Dim Integers As New Collection(Of Int32)(5, 12)
  088.         Dim Dates As New Collection(Of Date)(7, Date.Now)
  089.         Dim Persons As New Collection(Of Person)(10, Nothing)
  090.
  091.         Strings.Values(0) = "primo"
  092.         Integers.Values(3) = 45
  093.         Dates.Values(6) = New Date(2009, 1, 1)
  094.         Persons.Values(3) = New Person("Mario", "Rossi", Dates.Last)
  095.
  096.         Strings.PrintAll()
  097.         Integers.PrintAll()
  098.         Dates.PrintAll()
  099.         Persons.PrintAll()
  100.
  101.         Console.ReadKey()
  102.     End Sub
  103.
  104. End Module

Ognuna della quattr o var iabili del sor gente contiene un oggetto di tipo Collection, ma tali oggetti non sono dello stesso
tipo, poiché ognuno espone un differ ente tipo gener ics collegato. Quindi, nonostante si tr atti sempr e della stessa classe
Collection, Collection(Of Int32) e Collection(Of Str ing) sono a tutti gli effetti due tipi diver si: è come se esistesser o due
classi in cui T è sostituito in una da Int32 e nell'altr a da Str ing. Per dimostr ar e la lor o diver sità, basta scr iver e:

    1. Console.WriteLine(Strings.GetType() Is Integers.GetType())
    2. 'Output : False




Metodi Generic s e tipi generic s c ollegati implic iti
Se si decide di scr iver e un solo metodo gener ics, e di focalizzar e su di esso l'attenzione, solo accanto al suo nome
appar ir à la dichiar azione di un tipo gener ics aper to, con la consueta clausola "(Of T)". Anche se fin'or a ho usato come
nome solamente T, nulla vieta di specificar e un altr o identificator e valido (ad esempio Pippo): tuttavia, è convenzione
che il nome dei tipi gener ics aper ti sia Tn (con n numer o inter o, ad esempio T1, T2, T3, eccetr a...) o, in caso contr ar io,
che inizi almeno con la letter a T (ad esempio TSize, TClass, ecceter a...).

    1.   Sub [NomeProcedura](Of T)([Parametri])
    2.       '...
    3.   End Sub
    4.
    5.   Function [NomeFunzione](Of T)([Parametri]) As [TipoRestituito]
    6.       '...
    7.   End Function

Ecco un semplice esempio:

   01. Module Module1
   02.
   03.     'Scambia i valori di due variabili, passate
   04.     'per indirizzo
   05.     Public Sub Swap(Of T)(ByRef Arg1 As T, ByRef Arg2 As T)
   06.         Dim Temp As T = Arg1
   07.         Arg1 = Arg2
   08.         Arg2 = Temp
   09.     End Sub
   10.
   11.     Sub Main()
   12.         Dim X, Y As Double
   13.         Dim Z As Single
   14.         Dim A, B As String
   15.
   16.         X = 90.0
   17.
Y = 67.58
   18.         Z = 23.01
   19.         A = "Ciao"
   20.         B = "Mondo"
   21.
   22.         'Nelle prossime chiamate, Swap non presenta un
   23.         'tipo generics collegato: il tipo viene dedotto dai
   24.         'tipi degli argomenti
   25.
   26.         'X e Y sono Double, quindi richiama il metodo con
   27.         'T = Double
   28.         Swap(X, Y)
   29.         'A e B sono String, quindi richiama il metodo con
   30.         'T = String
   31.         Swap(A, B)
   32.
   33.         'Qui viene generato un errore: nonostante Z sia
   34.         'convertibile in Double implicitamente senza perdita
   35.         'di dati, il suo tipo non corrisponde a quello di X,
   36.         'dato che c'è un solo T, che può assumere
   37.         'un solo valore-tipo. Per questo è necessario
   38.         'utilizzare una scappatoia
   39.         'Swap(Z, X)
   40.
   41.         'Soluzione 1: si esplicita il tipo generic collegato
   42.         Swap(Of Double)(Z, X)
   43.         'Soluzione 2: si converte Z in double esplicitamente
   44.         Swap(CDbl(Z), X)
   45.
   46.         Console.ReadKey()
   47.     End Sub
   48. End Module




Generic s multipli
Quando, anziché un solo tipo gener ics, se ne specificano due o più, si par la di genr ics multipli. La dichiar azione avviene
allo stesso modo di come abbiamo visto pr ecedentemente e i tipi vengono separ ati da una vir gola:

   01. Module Module2
   02.     'Una relazione qualsiasi fra due oggetti di tipo indeterminato
   03.     Public Class Relation(Of T1, T2)
   04.         Private Obj1 As T1
   05.         Private Obj2 As T2
   06.
   07.         Public ReadOnly Property FirstObject() As T1
   08.              Get
   09.                  Return Obj1
   10.              End Get
   11.         End Property
   12.
   13.         Public ReadOnly Property SecondObject() As T2
   14.              Get
   15.                  Return Obj2
   16.              End Get
   17.         End Property
   18.
   19.         Sub New(ByVal Obj1 As T1, ByVal Obj2 As T2)
   20.              Me.Obj1 = Obj1
   21.              Me.Obj2 = Obj2
   22.         End Sub
   23.     End Class
   24.
   25.     Sub Main()
   26.         'Crea una relazione fra uno studente e un insegnante,
   27.         'utilizzando le classi create nei capitoli precedenti
   28.         Dim R As Relation(Of Student, Teacher)
   29.         Dim S As New Student("Pinco", "Pallino", Date.Parse("25/06/1990"), _
   30.              "Liceo Scientifico N. Copernico", 4)
   31.         Dim T As New Teacher("Mario", "Rossi", Date.Parse("01/07/1950"), _
   32.
"Matematica")
   33.
   34.         'Crea una nuova relazione tra lo studente e l'insegnante
   35.         R = New Relation(Of Student, Teacher)(S, T)
   36.         Console.WriteLine(R.FirstObject.CompleteName)
   37.         Console.WriteLine(R.SecondObject.CompleteName)
   38.
   39.         Console.ReadKey()
   40.     End Sub
   41. End Module

Notate che è anche possibile cr ear e una r elazione tr a due r elazioni (e la cosa diventa complicata):

   01. Dim S As New Student("Pinco", "Pallino", Date.Parse("25/06/1990"), "Liceo Scientifico N.
          Copernico", 4)
   02. Dim T As New Teacher("Mario", "Rossi", Date.Parse("01/07/1950"), "Matematica")
   03. Dim StudentTeacherRelation As Relation(Of Student, Teacher)
   04. Dim StudentClassRelation As Relation(Of Student, String)
   05. Dim Relations As Relation(Of Relation(Of Student, Teacher), Relation(Of Student, String))
   06.
   07. StudentTeacherRelation = New Relation(Of Student, Teacher)(S, T)
   08. StudentClassRelation = New Relation(Of Student, String)(S, "5A")
   09. Relations = New Relation(Of Relation(Of Student, Teacher), Relation(Of Student, String))
          (StudentTeacherRelation, StudentClassRelation)
   10.
   11. 'Relations.FirstObject.FirstObject
   12. ' > Student "Pinco Pallino"
   13. 'Relations.FirstObject.SecondObject
   14. ' > Teacher "Mario Rossi"
   15. 'Relations.SecondObject.FirstObject
   16. ' > Student "Pinco Pallino"
   17. 'Relations.SecondObject.SecondObject
   18. ' > String "5A"




Alc une regole per l'uso dei Generic s

       Si può sempr e assegnar e Nothing a una var iabile di tipo gener ics. Nel caso il tipo gener ics collegato sia
       r efer ence, alla var iabile ver r à assegnato nor malmente Nothing; in caso contr ar io, essa assumer à il valor e di
       default per il tipo;
       Non si può er editar e da un tipo gener ic aper to:

           1. Class Example(Of T)
           2.     Inherits T
           3.     ' SBAGLIATO
           4. End Class

       Tuttavia si può er editar e da una classe gener ics specificando come tipo gener ics collegato lo stesso tipo aper to:

           1. Class Example(Of T)
           2.     Inherits List(Of T)
           3.     ' CORRETTO
           4. End Class

       Allo stesso modo, non si può implementar e T come se fosse un'inter faccia:

           1. Class Example(Of T)
           2.     Implements T
           3.     ' SBAGLIATO
           4. End Class

       Ma si può implementar e un'inter faccia gener ics di tipo T:

           1. Class Example(Of T)
           2.     Implements IEnumerable(Of T)
           3.     ' CORRETTO
           4.
End Class

Entità con lo stesso nome ma con gener ics aper ti differ enti sono consider ate in over load. Per tanto, è lecito
scr iver e:

    1.    Sub Example(Of T)(ByVal A As T)
    2.        '...
    3.    End Sub
    4.
    5.    Sub Example(Of T1, T2)(ByVal A As T1)
    6.        '...
    7.    End Sub
A42. I Generics - Parte II

Interfac c e Generic s
Pr oviamo or a a scr iver e qualche inter faccia gener ics per veder ne il compor tamento. Ripr endiamo l'inter faccia
ICompar er , che indica qualcosa con il compito di compar ar e oggetti: esiste anche la sua cor r ispettiva gener ics, ossia
ICompar er (Of T). Non fa nessun differ enza il compor tamento di quest'ultima: l'unica cosa che cambia è il tipo degli
oggetti da compar ar e.

   01. Module Module1
   02.     'Questa classe implementa un comaparatore di oggetti Student
   03.     'in base al loro anno di corso
   04.     Class StudentByGradeComparer
   05.         Implements IComparer(Of Student)
   06.
   07.         'Come potete osservare, in questo metodo non viene eseguito
   08.         'nessun tipo di cast, poiché l'interfaccia IComparer(Of T)
   09.         'prevede un metodo Compare a tipizzazione forte. Dato che
   10.         'abbiamo specificato come tipo generic collegato Student,
   11.         'anche il tipo a cui IComparer si riferisce sarà
   12.         'Student. Possiamo accedere alle proprietà di x e y
   13.         'senza nessun late binding (per ulteriori informazioni,
   14.         'vedere i capitoli sulla reflection)
   15.         Public Function Compare(ByVal x As Student, ByVal y As Student) As Integer Implements
                  IComparer(Of Student).Compare
   16.              Return x.Grade.CompareTo(y.Grade)
   17.         End Function
   18.     End Class
   19.
   20.     Sub Main()
   21.         'Crea un nuovo array di oggeti Student
   22.         Dim S(2) As Student
   23.
   24.         'Inizializza ogni oggetto
   25.         S(0) = New Student("Mario", "Rossi", New Date(1993, 2, 3), "Liceo Classico Ugo
                  Foscolo", 2)
   26.         S(1) = New Student("Luigi", "Bianchi", New Date(1991, 6, 27), "Liceo Scientifico
                  Fermi", 4)
   27.         S(2) = New Student("Carlo", "Verdi", New Date(1992, 5, 12), "ITIS Cardano", 1)
   28.
   29.         'Ordina l'array con il comparer specificato
   30.         Array.Sort(S, New StudentByGradeComparer())
   31.
   32.         'Stampa il profilo di ogni studente: vedrete che essi sono
   33.         'in effetti ordinati in base all'anno di corso
   34.         For Each St As Student In S
   35.              Console.WriteLine(St.Profile)
   36.         Next
   37.         Console.ReadKey()
   38.     End Sub
   39.
   40. End Module




I V inc oli
I tipi gener ics sono molto utili, ma spesso sono un po' tr oppo... "gener ici" XD Faccio un esempio. Ammettiamo di aver e
un metodo gener ics (Of T) che accetta due par ametr i A e B. Pr oviamo a scr iver e:

    1. If A = B Then '...

L'IDE ci comunica subito un er r or e: "Oper ator '=' is not definited for type 'T' and 'T'." In effetti, poiché T può esser e un
qualsiasi tipo, non possiamo neanche saper e se questo tipo implementi l'oper ator e uguale =. In questo caso, vogliamo
impor r e come condizione, ossia come v inco lo , che, per usar e il metodo in questione, il tipo gener ic collegato debba
obbligator iamente espor r e un modo per saper e se due oggetti di quel tipo sono uguali. Come si r ende in codice? Se fate
mente locale sulle inter facce, r icor der ete che una classe r appr esenta un concetto con deter minate car atter istiche se
implementa deter minate inter facce. Dovr emo, quindi, tr ovar e un'inter faccia che r appr esenta l'"eguagliabilità":
l'inter faccia in questione è IEquatable(Of T). Per poter saper e se due oggetti T sono uguali, quindi, T dovr à esser e un
qualsiasi tipo che implementa IEquatable(Of T). Ecco che dobbiamo impor r e un vincolo al tipo.
Esistono cinque categor ie di vincoli:

       Vincolo di inter faccia;
       Vincolo di er editar ietà;
       Vincolo di classe;
       Vincolo di str uttur a;
       Vincolo New .

Iniziamo con l'analizzar e il pr imo di cui abbiamo par lato.




V inc olo di Interfac c ia
Il vincolo di inter faccia è indubbiamente uno dei più utili e usati accanto a quello di er editar ietà. Esso impone che il tipo
gener ic collegato implementi l'inter faccia specificata. Dato che dopo l'imposizione del vincolo sappiamo per ipotesi che il
tipo T espor r à sicur amente tutti i membr i di quell'inter faccia, possiamo r ichiamar e tali membr i da tutte le var iabili di
tipo T. La sintassi è molto semplice:

    1. (Of T As [Interfaccia])

Ecco un esempio:

 001. Module Module1
 002.
 003.     'Questa classe rappresenta una collezione di
 004.     'elementi che possono essere comparati. Per questo
 005.     'motivo, il tipo T espone un vincolo di interfaccia
 006.     'che obbliga tutti i tipi generics collegati ad
 007.     'implementare tale interfaccia.
 008.     'Notate bene che in questo caso particolare ho usato
 009.     'un generics doppio, poiché il vincolo non
 010.     'si riferisce a IComparable, ma a IComparable(Of T).
 011.     'D'altra parte, è abbastanza ovvio che se
 012.     'una collezione contiene un solo tipo di dato,
 013.     'basterà che la comparazione sia possibile
 014.     'solo attraverso oggetti di quel tipo
 015.     Class ComparableCollection(Of T As IComparable(Of T))
 016.         'Ereditiamo direttamente da List(Of T), acquisendone
 017.         'automaticamente tutti i membri base e le caratteristiche.
 018.         'In questo modo, godremo di due grandi vantaggi:
 019.         ' - non dovremo definire tutti i metodi per aggiungere,
 020.         '   rimuovere o cercare elementi, in quanto vengono tutti
 021.         '   ereditati dalla classe base List;
 022.         ' - non dovremo neanche implementare l'interfaccia
 023.         '   IEnumerable(Of T), poiché la classe base la
 024.         '   implementa di per sé.
 025.         Inherits List(Of T)
 026.
 027.         'Dato che gli oggetti contenuti in oggetti di
 028.         'questo tipo sono per certo comparabili, possiamo
 029.         'trovarne il massimo ed il minimo.
 030.
 031.         'Trova il massimo elemento
 032.         Public ReadOnly Property Max() As T
 033.              Get
 034.
If Me.Count > 0 Then
035.                   Dim Result As T = Me(0)
036.
037.                      For Each Element As T In Me
038.                           'Ricordate che A.CompareTo(B) restituisce
039.                           '1 se A > B
040.                           If Element.CompareTo(Result) = 1 Then
041.                               Result = Element
042.                           End If
043.                      Next
044.
045.                      Return Result
046.               Else
047.                    Return Nothing
048.               End If
049.           End Get
050.       End Property
051.
052.       'Trova il minimo elemento
053.       Public ReadOnly Property Min() As T
054.           Get
055.               If Me.Count > 0 Then
056.                    Dim Result As T = Me(0)
057.
058.                      For Each Element As T In Me
059.                           If Element.CompareTo(Result) = -1 Then
060.                               Result = Element
061.                           End If
062.                      Next
063.
064.                      Return Result
065.               Else
066.                    Return Nothing
067.               End If
068.           End Get
069.       End Property
070.
071.       'Trova tutti gli elementi uguali ad A e ne restituisce
072.       'gli indici
073.       Public Function FindEquals(ByVal A As T) As Int32()
074.           Dim Result As New List(Of Int32)
075.
076.           For I As Int32 = 0 To Me.Count - 1
077.                If Me(I).CompareTo(A) = 0 Then
078.                    Result.Add(I)
079.                End If
080.           Next
081.
082.           'Converte la lista di interi in un array di interi
083.           'con gli stessi elementi
084.           Return Result.ToArray()
085.       End Function
086.
087.   End Class
088.
089.   Sub Main()
090.       'Tre collezioni, una di interi, una di stringhe e
091.       'una di date
092.       Dim A As New ComparableCollection(Of Int32)
093.       Dim B As New ComparableCollection(Of String)
094.       Dim C As New ComparableCollection(Of Date)
095.
096.       A.AddRange(New Int32() {4, 19, 6, 90, 57, 46, 4, 56, 4})
097.       B.AddRange(New String() {"acca", "casa", "zen", "rullo", "casa"})
098.       C.AddRange(New Date() {New Date(2008, 1, 1), New Date(1999, 12, 31), New Date(2100, 4,
              12)})
099.
100.       Console.WriteLine(A.Min())
101.       ' > 4
102.       Console.WriteLine(A.Max())
103.       ' > 90
104.       Console.WriteLine(B.Min())
105.
' > acca
  106.         Console.WriteLine(B.Max())
  107.         ' > zen
  108.         Console.WriteLine(C.Min().ToShortDateString)
  109.         ' > 31/12/1999
  110.         Console.WriteLine(C.Max().ToShortDateString)
  111.         ' > 12/4/2100
  112.
  113.         'Trova la posizione degli elementi uguali a 4
  114.         Dim AEqs() As Int32 = A.FindEquals(4)
  115.         ' > 0 6 8
  116.         Dim BEqs() As Int32 = B.FindEquals("casa")
  117.         ' > 1 4
  118.
  119.         Console.ReadKey()
  120.     End Sub
  121.
  122. End Module




V inc olo di ereditarietà
Ha la stessa sintassi del vincolo di inter faccia, con la sola differ enza che al posto dell'inter faccia si specifica la classe dalla
quale il tipo gener ics collegato deve er editar e. I vantaggi sono pr aticamente uguali a quelli offer ti dal vincolo di
inter faccia: possiamo tr attar e T come se fosse un oggetto di tipo [Classe] (una classe qualsiasi) ed utilizzar ne i membr i,
poiché tutti i tipi possibili per T sicur amente der ivano da [Classe]. Un esempio anche per questo vincolo mi sembr a
abbastanza r idondante, ma c'è una caso par ticolar e che mi piacer ebbe sottolinear e. Mi r ifer isco al caso in cui al posto
della classe base viene specificato un altr o tipo gener ic (aper to), e di questo, data la non immediatezza di
compr ensione, posso dar e un veloce esempio:

    1. Class IsARelation(Of T, U As T)
    2.     Public Base As T
    3.     Public Derived As U
    4. End Class

Questa classe r appr esenta una r elazione is-a ("è un"), quella famosa r elazione che avevo intr odotto come esempio una
quar antina di capitoli fa dur ante i pr imi par agr afi di spiegazione. Questa r elazione è r appr esentata par ticolar mente
bene, dicevo, se si pr ende una classe base e la sua classe der ivata. I tipi gener ics aper ti non fanno altr o che astr ar r e
questo concetto: T è un tipo qualsiasi e U un qualsiasi altr o tipo der ivato da T o uguale T (non c'è un modo per impor r e
che sia solo der ivato e non lo stesso tipo). Ad esempio, potr ebbe esser e valido un oggetto del gener e:

    1.   Dim P As Person
    2.   Dim S As Student
    3.   '...
    4.   Dim A As New IsARelation(Of Person, Student)(P, S)




V inc oli di c lasse e struttura
Il vincolo di classe impone che il tipo gener ics collegato sia un tipo r efer ence, mentr e il vincolo di str uttur a impone che
sia un tipo value. Le sintassi sono le seguenti:

    1. (Of T As Class)
    2. (Of T As Structure)

Questi due vincoli non sono molto usati, a dir e il ver o, e la lor o utilità non è così mar cata e lampante come appar e per
i pr imi due vincoli analizzati. Cer to, possiamo evitar e alcuni compor tamenti str ani dovuti ai tipi r efer ence, o
sfr uttar e alcune car atter istiche dei tipi value, ma nulla di più. Ecco un esempio dei possibili vantaggi:
Vincolo di classe:
               Possiamo assegnar e Nothing con la sicur ezza di distr ugger e l'oggetto e non di cambiar ne semplicemente
               il valor e in 0 (o in quello di default per un tipo non numer ico);
               Possiamo usar e con sicur ezza gli oper ator i Is, IsNot, TypeOf e Dir ectCast che funzionano solo con i tipi
               r efer ence;
       Vincolo di str uttur a:
               Possiamo usar e l'oper ator e = per compar ar e due valor i sulla base di quello che contengono e non di quello
               che "sono";
               Possiamo evitar e gli inconvenienti dell'assegnamento dovuti ai tipi r efer ence.

User ò il vincolo di classe in un esempio molto significativo, ma solo quando intr odur r ò la Reflection, quindi fatevi un
aster isco su questo capitolo.




V inc olo New
Questo vincolo impone al tipo gener ic collegato di espor r e almeno un costr uttor e senza par ametr i. Par ticolar mente
utile quando si devono inizializzar e dei valor i gener ics:

   01. Module Module1
   02.
   03.     'Con molta fantasia, il vincolo New si dichiara postponendo
   04.     '"As New" al tipo generic aperto.
   05.     Function CreateArray(Of T As New)(ByVal Count As Int32) As T()
   06.         Dim Result(Count - 1) As T
   07.
   08.         For I As Int32 = 0 To Count - 1
   09.              'Possiamo usare il costruttore perchè il
   10.              'vincolo ce lo assicura
   11.              Result(I) = New T()
   12.         Next
   13.
   14.         Return Result
   15.     End Function
   16.
   17.     Sub Main()
   18.         'Crea 10 flussi di dati in memoria. Non abbiamo
   19.         'mai usato questa classe perchè rientra in
   20.         'un argomento che tratterò più avanti, ma
   21.         'è una classe particolarmente utile e versatile
   22.         'che trova applicazioni in molte situazioni.
   23.         'Avere un bel metodo generics che ne crea 10 in una
   24.         'volta è una gran comodità.
   25.         'Ovviamente possiamo fare la stessa cosa con tutti
   26.         'i tipi che espongono almeno un New senza parametri
   27.         Dim Streams As IO.MemoryStream() = CreateArray(Of IO.MemoryStream)(10)
   28.
   29.         '...
   30.     End Sub
   31.
   32. End Module




V inc oli multipli
Un tipo gener ic aper to può esser e sottoposto a più di un vincolo, ossia ad un vincolo multiplo, che altr o non è se non la
combinazione di due o più vincoli semplici di quelli appena visti. La sintassi di un vincolo multiplo è legger mente diver sa
e pr evede che tutti i vincoli siano r aggr uppati in una copia di par entesi gr affe e separ ati da vir gole:

    1. (Of T As {Vincolo1, Vincolo2, ...})
Ecco un esempio:

  01. Module Module1
  02.
  03.     'Classe che filtra dati di qualsiasi natura
  04.     Class DataFilter(Of T)
  05.         Delegate Function FilterData(ByVal Data As T) As Boolean
  06.
  07.         'La signature chilometrica è fatta apposta per
  08.         'farvi impazzire XD Vediamo le parti una per una:
  09.         ' - TSerach: deve essere un tipo uguale a T o derivato
  10.         '   da T, in quanto stiamo elaborando elementi di tipo T;
  11.         '   inoltre deve anche essere clonabile, poiché
  12.         '   salveremo solo una copia dei valor trovati.
  13.         '   Questo implica che TSearch sia un tipo reference, e che
  14.         '   quindi lo sia anche T: questa complicazione è solo
  15.         '   per mostrare dei vincoli multipli e potete anche
  16.         '   rimuoverla se vi pare;
  17.         ' - TList: deve essere un tipo reference, esporre un
  18.         '   costruttore senza parametri ed implementare
  19.         '   l'interfaccia IList(Of TSearch), ossia deve
  20.         '   essere una lista;
  21.         ' - ResultList: lista in cui riporre i risultati (passata
  22.         '   per indirizzo);
  23.         ' - Filter: delegate che punta alla funzione usata per
  24.         '   selezionare i valori;
  25.         ' - Data: paramarray contenente i valori da filtrare.
  26.         Sub Filter(Of TSearch As {ICloneable, T}, TList As {IList(Of TSearch), New, Class}) _
  27.              (ByRef ResultList As TList, ByVal Filter As FilterData, ByVal ParamArray Data() As
                      TSearch)
  28.
  29.              'Se la lista è Nothing, la inizializza.
  30.              'Notare che non avremmo potuto compararla a Nothing
  31.              'senza il vincolo Class, né inizializzarla
  32.              'senza il vincolo New
  33.              If ResultList Is Nothing Then
  34.                   ResultList = New TList()
  35.              End If
  36.
  37.              'Itera sugli elementi di data
  38.              For Each Element As TSearch In Data
  39.                   'E aggiunge una copia di quelli che
  40.                   'soddisfano la condizione
  41.                   If Filter.Invoke(Element) Then
  42.                       'Aggiunge una copia dell'elemento alla lista.
  43.                       'Anche in questo non avremmo potuto richiamare
  44.                       'Add senza il vincolo interfaccia su IList, né
  45.                       'clonare Element senza il vincolo interfaccia ICloneable
  46.                       ResultList.Add(Element.Clone())
  47.                   End If
  48.              Next
  49.         End Sub
  50.     End Class
  51.
  52.     'Controlla se la stringa A è palindroma
  53.     Function IsPalindrome(ByVal A As String) As Boolean
  54.         Dim Result As Boolean = True
  55.
  56.         For I As Int32 = 0 To (A.Length / 2) - 1
  57.              If A.Chars(I) <> A.Chars(A.Length - 1 - I) Then
  58.                   Result = False
  59.                   Exit For
  60.              End If
  61.         Next
  62.
  63.         Return Result
  64.     End Function
  65.
  66.     Sub Main()
  67.         Dim DF As New DataFilter(Of String)
  68.         'Lista di stringhe: notare che la variabile non
  69.         'contiene nessun oggetto perchè non abbiamo usato New.
  70.
'Serve per mostrare che verrà inizializzata
71.         'da DF.Filter.
72.         Dim L As List(Of String)
73.
74.         'Analizza le stringhe passate, trova quelle palindrome
75.         'e le pone in L
76.         DF.Filter(L, AddressOf IsPalindrome, _
77.              "casa", "pane", "anna", "banana", "tenet", "radar")
78.
79.         For Each R As String In L
80.              Console.WriteLine(R)
81.         Next
82.
83.         Console.ReadKey()
84.     End Sub
85.
86. End Module
A43. I tipi Nullable

I tipi Nullable costituiscono una utile applicazione dei gener ics alla gestione dei database. Infatti, quando si lavor a con
dei database, capita molto spesso di tr ovar e alcune celle vuote, ossia il cui valor e non è stato impostato. In questo caso,
l'oggetto che media tr a il database e il pr ogr amma - oggetto che analizzer emo solo nella sezione C - pone in tali celle
uno speciale valor e che significa "non contiene nulla". Questo valor e è par i a DBNull.Value, una costante statica
pr eimpostata di tipo DBNull, appunto. Essendo un tipo r efer ence, l'assegnar e il valor e di una cella a una var iabile value
può compor tar e er r or i nel caso tale cella contenga il famiger ato DBNull, poiché non si è in gr ado di effettuar e una
conver sione. Compor tamenti del gener e costr ingono (anzi, costr ingevano) i pr ogr ammator i a scr iver e una quantità
eccessiva di costr utti di contr ollo del tipo:

   01. If Cell.Value IsNot DBNull.Value Then
   02.      Variable = Cell.Value
   03. Else
   04.      Variable = 0
   05.      'Per impostare il valore di default, bisognava ripetere
   06.      'questi If tante volte quanti erano i tipi in gioco, poiché
   07.      'non c'era modo di assegnare un valore Null a tutti
   08.      'in un solo colpo
   09. End If

Tuttavia, con l'avvento dei gener ics, nella ver sione 2005 del linguaggio, questi pr oblemi sono stati ar ginati, almeno in
par te e almeno per chi conosce i tipi nullable. Questi speciali tipi sono str uttur e gener ics che possono anche accettar e
valor i r efer ence come Nothing: ovviamente, dato che i pr oblemi insor gono solo quando si tr atta di tipi value, i tipi
gener ics collegati che è lecito specificar e quando si usa nullable devono esser e tipi value (quindi c'è un vincolo di
str uttur a).
Ci sono due sintassi molto diver se per dichiar ar e tipi nullable, una esplicita e una implicita:

    1.    'Dichiarazione esplicita:
    2.    Dim [Nome] As Nullable(Of [Tipo])
    3.
    4.    'Dichiarazione implicita:
    5.    Dim [Nome] As [Tipo]?

La seconda si attua postponendo un punto inter r ogativo al nome del tipo: una sintassi molto br eve e concisa che
tuttavia può anche sfuggir e facilmente all'occhio. Una volta dichiar ata, una var iabile nullable può esser e usata come una
comunissima var iabile del tipo gener ic collegato specificato. Essa, tuttavia, espone alcuni membr i in più r ispetto ai
nor mali tipi value, nella fattispecie:

         HasValue : pr opr ietà r eadonly che r estituisce Tr ue se l'oggetto contiene un valor e;
         Value : pr opr ietà r eadonly che r estituisce il valor e dell'oggetto, nel caso esista;
         GetValueOr Default() : funzione che r estituisce Value se l'oggetto contiene un valor e, altr imenti il valor e di
         default per quel tipo (ad esempio 0 per i tipi numer ici). Ha un over load che accetta un par ametr o -
         GetValur Or Default(X): in questo caso, se l'oggetto non contiene nulla, viene r estituito X al posto del valor e di
         default.

Ecco un esempio:

   01. Module Module1
   02.
   03.     Sub Main()
   04.         'Tre variabili di tipo value dichiarate come
   05.         'nullable nei due modi diversi consentiti
   06.         Dim Number As Integer?
   07.         Dim Data As Nullable(Of Date)
   08.
Dim Cost As Double?
   09.         Dim Sent As Nullable(Of Boolean)
   10.
   11.         'Ammettiamo di star controllando un database:
   12.         'questo array di oggetti rappresenta il contenuto
   13.         'di una riga
   14.         Dim RowValues() As Object = {DBNull.Value, New Date(2009, 7, 1), 67.99, DBNull.Value}
   15.
   16.         'Con un solo ciclo trasforma tutti i DBNull.Value
   17.         'in Nothing, poiché i nullable supportano solo
   18.         'Nothing come valore nullo
   19.         For I As Int16 = 0 To RowValues.Length - 1
   20.              If RowValues(I) Is DBNull.Value Then
   21.                  RowValues(I) = Nothing
   22.              End If
   23.         Next
   24.
   25.         'Assegna alle variabili i valori contenuti nell'array:
   26.         'non ci sono mai problemi in questo codice, poiché,
   27.         'trattandosi di tipi nullable, questi oggetti possono
   28.         'accettare anche valori Nothing. In questo esempio,
   29.         'Number e Sent riceveranno un Nothing come valore: la
   30.         'loro proprietà HasValue varrà False.
   31.         Number = RowValues(0)
   32.         Data = RowValues(1)
   33.         Cost = RowValues(2)
   34.         Sent = RowValues(3)
   35.
   36.         'Scrive a schermo il valore di ogni variabile, se ne
   37.         'contiene uno, oppure il valore di default se non
   38.         'contiene alcun valore.
   39.         Console.WriteLine("{0} {1} {2} {3}", _
   40.              Number.GetValueOrDefault, _
   41.              Data.GetValueOrDefault, _
   42.              Cost.GetValueOrDefault, _
   43.              Sent.GetValueOrDefault)
   44.
   45.         'Provando a stampare una variabile nullable priva
   46.         'di valore senza usare la funzione GetValueOrDefault,
   47.         'semplicemente non stamperete niente:
   48.         ' Console.WriteLine(Number)
   49.         'Non stampa niente e va a capo.
   50.
   51.         Console.ReadKey()
   52.     End Sub
   53.
   54. End Module




Logic a booleana a tre valori
Un valor e nullable Boolean può assumer e vir tualmente tr e valor i: ver o (Tr ue), falso (False) e null (senza valor e). Usando
una var iabile booleana nullable come oper ando per gli oper ator i logici, si otter r anno r isultati diver si a seconda che
essa abbia o non abbia un valor e. Le nuove combinazioni che possono esser e eseguite si vanno ad aggiunger e a quelle
già esistenti per cr ear e un nuovo tipo di logica elementar e, detta, appunto, "logica booleana a tr e valor i". Essa segue
questo schema nei casi in cui un oper ando sia null:


                                    Valor e 1      Oper ator e     Valor e 2       Risultato

                                      Tr ue            And            Null            Null

                                      False            And            Null           False

                                      Tr ue            Or             Null           Tr ue

                                      False            Or             Null            Null
Tr ue   Xor   Null   Null

False   Xor   Null   Null
A44. La Reflection - Parte I

Con il ter mine gener ale di r eflection si intendono tutte le classi del Fr amew or k che per mettono di acceder e o
manipolar e assembly e moduli.


 A ssem bly
 L'assembly è l'unità logica più piccola su cui si basa il Fr amew or k .NET. Un assembly altr o non è che un pr ogr amma o
 una libr er ia di classi (compilati in .NET). Il Fr amew or k stesso è composto da una tr entina di assembly pr incipali che
 costituiscono le libr er ie di classi più impor tanti per         la pr ogr ammazione .NET (ad esempio System.dll,
 System.Dr aw ing.dll, System.Cor e.dll, ecceter a...).


Il ter mine Reflection ha un significato molto pr egnante: la sua tr aduzione in italiano è alquanto lampante e significa
"r iflessione". Dato che viene usata per ispezionar e, analizzar e e contr ollar e il contenuto di assembly, r isulta evidente
che mediante r eflection noi scr iviamo del codice che analizza altr o codice, anche se compilato: è una specie di
our obor os, il ser pente che si mor de la coda; una r iflessione della pr ogr ammazione su se stessa, appunto.
Lasciando da par te questo inter cor so filosofico, c'è da dir e che la r eflection è di gr an lunga una delle tecniche più
utilizzate dall'IDE e dal Fr amew or k stesso, anche se spesso questi meccanismi si svolgono "dietr o le quinte" e vengono
mascher ati per non far li appar ir e evidenti. Alcuni esempi sono la ser ializzazione, di cui mi occuper ò in seguito, ed il
late binding.


 Late Binding
 L'azione del legar e (in inglese, appunto, "bind") un identificator e a un valor e viene detta binding: si esegue un
 binding, ad esempio, quando si assegna un nome a una var iabile. Questo consente un'astr azione fondamentale
 affinché il pr ogr ammator e possa compr ender e ciò che sta scr itto nel codice: nessuno r iuscir ebbe a capir e alcunché se
 al posto dei nomi di var iabile ci fosser o degli indir izzi di memor ia a otto cifr e. Ebbene, esistono due tipi di binding:
 quello statico o "ear ly", e quello dinam ico o "late". Il pr imo viene effetuato pr ima che il pr ogr amma sia eseguito, ed
 è quello che per mette al compilator e di tr adur r e in linguaggio inter medio le istr uzioni scr itte in for ma testuale dal
 pr ogr ammator e. Quando assegnamo un nome ad una var iabile, o r ichiamiamo un metodo da un oggetto stiamo
 attuando un ear ly binding: sappiamo che quell'identificator e è logicamente legato a quel pr eciso valor e di quel pr eciso
 tipo e che, allo stesso modo, quel nome r ichiamer à pr opr io quel metodo da quell'oggetto e, non, magar i, un metodo a
 caso disper so nella memor ia. Il secondo, al contr ar io, viene por tato a ter mine mentr e il pr ogr amma è in esecuzione:
 ad esempio, r ichiamar e dei metodi d'istanza di una classe Per son da un oggetto Object è un esempio di late binding,
 poiché solo a r un-time, il nome del membr o ver r à letto, ver ificato, e, in caso di successo, r ichiamato. Tuttavia, non
 esiste alcun legame tr a una var iabile Object e una di tipo Per son, se non che, a r untime, la pr ima potr à contener e
 un valor e di tipo Per son, ma questo il compilator e non può saper lo in anticipo (mentr e noi sì).


Esiste un unico namespace dedicato inter amente alla r eflection e si chiama, appunto, System.Reflection.
Una delle classi più impor tanti in questo ambito, invece, è System.Type. Quest'ultima è una classe molto speciale, poiché
ne esistono molte istanze, ognuna unica, ma non è possibile cr ear ne di nuove. Ogni istanza di Type r appr esenta un
tipo: ad esempio, c'è un oggetto Type per Str ing, uno per Per son, uno per Integer , e via dicendo. Risulta logico che non
possiamo cr ear e un oggetto Type, per chè non sar ebbe associato ad alcun tipo e non avr ebbe motivo di esister e:
possiamo, al contr ar io, ottener e un oggetto Type già esistente.
I Contesti
Pr ima di iniziar e a veder e come analizzar e un assembly, dobbiamo fer mar ci un attimo a capir e come funziona il
sistema oper ativo a livello un po' più basso del nor male. Questo ci sar à utile per sceglier e una modalità di accesso
all'assembly coer ente con le nostr e necessità.
Quasi ogni sistema oper ativo è composto di più str ati sovr apposti, ognuno dei quali ha il compito di gestir e una
deter minata r isor sa dell'elabor ator e e di for nir e per essa un'astr azione, ossia una visione semplificata ed estesa. Il
pr imo str ato è il gestor e di pr ocessi (o ker nel), che ha lo scopo di coor dinar e ed isolar e i pr ogr ammi in esecuzione
r acchiudendoli in ar ee di memor ia separ ate, i pr ocessi appunto. Un pr ocesso r appr esenta un "pr ogr amma in
esecuzione" e non contiene solo il semplice codice eseguibile, ma, oltr e a questo, mantiene tutti i dati iner enti al
funzionamento del pr ogr amma, ivi compr esi var iabili, collegamenti a r isor se ester ne, stato della CPU, ecceter a... Oltr e
ad assegnar e un dato per iodo di tempo macchina ad ogni pr ocesso, il ker nel separ a le ar ee di memor ia r iser vate a
ciascuno, r endendo impossibile per un pr ocesso modificar e i dati di un altr o pr ocesso, causando, in questo modo, un
possibile cr ash di entr ambi i pr ogr ammi o del sistema stesso. Questa politica di coor dinamento, quindi, r ende sicur a e
isolata l'esecuzione di un pr ogr amma. Il CLR del .NET, tuttavia, aggiunge un'ulter ior e suddivisione, basata sui dom ini
applicativ i o A ppDo m ain o co ntesti di esecuzio ne. All'inter no di un singolo pr ocesso possono esister e più domini
applicativi, i quali sono tr a lor o isolati come se fosser o due pr ocessi differ enti: in questo modo, un assembly
appar tenente ad un cer to AppDomain non può modificar e un altr o assembly in un altr o AppDomain. Tuttavia, come è
lecito scambiar e dati fr a pr ocessi, è anche lecito scambiar e dati tr a contesti di esecuzione: l'unica differ enza sta nel
fatto che questi ultimi sono allocati nello stesso pr ocesso e, quindi, possono comunicar e molto più velocemente. Così
facendo, un singolo pr ogr ama può cr ear e due domini applicativi che cor r ono in par allelo come se fosser o pr ocessi
differ enti, ma attr aver so i quali è molto più semplice la comunicazione e lo scambio di dati. Un semplice esempio lo
potr ete tr ovar e osser vando il Task Manager di Window s quando ci sono due finestr e di Fir eFox aper te allo stesso
tempo: noter e che vi è un solo pr ocesso fir efox .ex e associato.
Caric are un assembly
Un assembly è r appr esentato dalla classe System.Reflection.Assembly. Tutte le oper azioni effettuabili su di esso sono
esposte mediante metodi della classe assembly. Pr imi fr a tutti, spiccano i metodi per il car icamento, che si distinguono
dagli altr i per la lor o copiosa quantità. Esistono, infatti, ben sette metodi statici per car icar e od ottener e un
r ifer imento ad un assembly, e tutti offr ono una modalità di car icamento diver sa dagli altr i. Eccone una lista:

       Assembly.GetEx cecutingAssembly()
       Restituisce un r ifer imento all'assembly che è in esecuzione e dal quale questa chiamata a funzione viene lanciata.
       In poche par ole, l'oggetto che ottenete invocando questo metodo si r ifer isce al pr ogr amma o alla libr er ia che
       state scr ivendo;
       Assembly.GetAssembly(ByVal T As System.Type) oppur e T.Assembly()
       Restituiscono un r ifer imento all'assembly in cui è definito il tipo T specificato;
       Assembly.Load("Nome")
       Car ica un assembly a par tir e dal nome completo o par ziale. Ad esempio, si può car icar e System.Xml.dll
       dinamicamente con Assembly.Load("System.Xml"). Restituisce un r ifer imento all'assembly car icato. "Nome" può
anche esser e il no m e co m pleto dell'assembly, che compr ende nome, ver sione, cultur a e token della chiave
       pubblica. La chiave pubblica è un lunghissimo codice for mato da cifr e esadecimali che identificano univocamente
       il file; il suo token ne è una ver sione "abbr eviata", utile per non scr iver e la chiave inter a. Vedr emo tr a poco una
       descr izione dettagliata del nome di un assembly.
       Se un assembly viene car icato con Load, esso diviene par te del contesto di esecuzione cor r ente, e inoltr e il
       Fr amew or k è capace di tr ovar e e car icar e le sue dipendenze da altr i file, ossia tutti gli assembly che ser vono a
       questo per funzionar e (in gener e tutti quelli specificati nelle dir ettive Impor ts). In ger go, quest'ultima azione si
       dice "r isolver e le dipendenze";
       Assembly.LoadFr om("File")
       Car ica un assembly a par tir e dal suo per cor so su disco, che può esser e r elativo o assoluto, e ne r estituisce un
       r ifer imento. Il file car icato in questo modo diventa par te del contesto di esecuzione di LoadFr om. Inoltr e, il
       Fr amew or k è in gr ado di r isolver ne le dipendenze solo nel caso in cui queste siano pr esenti nella car tella
       pr incipale dell'applicazione;
       Assembly.LoadFile("File")
       Agisce in modo analogo a LoadFr om, ma l'assembly viene car icato in un contesto di esecuzione differ ente, e il
       Fr amew or k non è in gr ado di r isolver ne le dipendenze, a meno che queste non siano state già car icate con i
       metodi sopr a r ipor tati;
       Assembly.ReflectionOnlyLoad("Nome")
       Restituisce un r ifer imento all'assembly con dato Nome. Questo non viene car icato in memor ia, poichè il metodo
       ser ve solamente a ispezionar ne gli elementi;
       Assembly.ReflectionOnlyLoadFr om("File")
       Restituisce un r ifer imento all'assembly specificato nel per cor so File. Questo non viene car icato in memor ia,
       poichè il metodo ser ve solamente a ispezionar ne gli elementi.

Gli ultimi due metodi hanno anche un par ticolar e effetto collater ale. Anche se gli assembly non vengono car icati in
memor ia, ossia non diventano par te attiva dal dominio applicativo, pur tuttavia vengono posti in un altr o contesto
speciale, detto co ntesto di ispezio ne. Quest'ultimo è unico per ogni pr ocesso e condiviso da tutti gli AppDomain
pr esenti nel pr ocesso.




Nome dell'assembly e analisi superfic iale
Una volta ottenuto un r ifer imento ad un oggetto di tipo Assembly, possiamo usar ne i membr i per ottener e le più var ie
infor mazioni. Ecco una br eve lista delle pr opr ietà e dei metodi più significativi:

       Fullname : r estituisce il nome completo dell'assembly, specificando nome, cultur a, ver sione e token della chiave
       pubblica;
       CodeBase : nel caso l'assembly sia scar icato da inter net, ne r estituisce la locazione in for mato oppor tuno;
       Location : r estituisce il per cor so su disco dell'assembly;
       GlobalAssemblyChace : pr opr ietà che value Tr ue nel caso l'assembly sia stato car icato dalla GAC;


        G lo bal A ssem bly Cache (G A C)
        La car tella fisica in cui vengono depositati tutti gli assembly pubblici. Per assembly pubblico, infatti, s'intende
        ogni assembly accessibile da ogni applicazione su una deter minata macchina. Gli assembly pubblici sono,
        solitamente, tutti quelli di base del Fr amew or k .NET, ma è possibile aggiunger ne altr i con deter minati
        comandi. La GAC di Window s è di solito posizionata in C:WINDOWSassembly e contiene tutte le libr er ie base
        del Fr amew or k. Ecco per chè basta specificar e il nome dell'assembly pubblico per car icar lo (la car tella è nota a
        pr ior i).
ReflectionOnly : r estituisce Tr ue se l'assembly è stato car icato per soli scopi di analisi (r eflection);
       GetName() : r estituisce un oggetto AssemblyName associato all'assembly cor r ente;
       GetTypes() : r estituisce un ar r ay di Type che definiscono ogni tipo dichiar ato all'inter no dell'assembly.

Pr ima di ter minar e il capitolo, esaminiamo le par ticolar ità del nome dell'assembly. In gener e il no m e co m pleto di un
assembly ha questo for mato:

 [Nome Principale], Version=a.b.c.d, Culture=[Cultura], PublicKeyToken=[Token]


Il nome pr incipale è deter minato dal pr ogr ammator e e di solito indica il namespace pr incipale contenuto nell'assembly.
La ver sione è un numer o di ver sione a quattr o par ti, divise solitamente, in or dine, come segue: Major (numer o di
ver sione pr incipale) , Minor (numer o di ver sione minor e, secondar io), Revision (numer o della r evisione a cui si è giunti
per questa ver sione), Build (numer o di compilazioni eseguite per questa r evisione). Il numer o di ver sione indica di
solito la ver sione del Fr amew or k per cui l'assembly è stato scr itto: se state usando VB2005, tutte le ver sioni sar anno
uguali o infer ior i a 2.0.0.0; con VB2008 sar anno uguali o infer ior i a 3.5.0.0. Cultur e r appr esenta la cultur a in cui è
stato scr itto l'assembly: di solito è semplicmente "neutr al", neutr ale, ma nel caso in cui sia differ ente, influenza alcuni
aspetti secondar i come la r appr esentazione dei numer i (sepr ator i decimali e delle migliaia), dell'or ar io, i simboli di
valuta, ecceter a... Il token della chiave pubblica è un insieme di otto bytes che identifica univocamente la chiave
pubblica (è una sua ver sione "abbr eviata"), la quale identifica univocamente l'assembly. Viene usato il token e non tutta
la chiave per questioni di lunghezza. Ecco un esempio che ottiene questi dati:

   01. Module Module1
   02.
   03.     Sub Main()
   04.         'Carica un assembly per soli scopi di analisi.
   05.         'mscorlib è l'assembly più importante di
   06.         'tutto il Framework, da cui deriva pressochè ogni
   07.         'cosa. Data la sua importanza, non ha dipendenze,
   08.         'perciò non ci saranno problemi nel risolverle.
   09.         'Se volete caricare un altro assembly, dovrete usare
   10.         'uno dei metodi in grado di risolvere le dipendenze.
   11.         Dim Asm As Assembly = Assembly.ReflectionOnlyLoad("mscorlib")
   12.         Dim Name As AssemblyName = Asm.GetName
   13.
   14.         Console.WriteLine(Asm.FullName)
   15.         Console.WriteLine("Nome: " & Name.Name)
   16.         Console.WriteLine("Versione: " & Name.Version.ToString)
   17.         Console.WriteLine("Cultura: " & Name.CultureInfo.Name)
   18.
   19.         'Il formato X indica di scrivere un numero usando la
   20.         'codifica esadecimale. X2 impone di occupare sempre almeno
   21.         'due posti: se c'è una sola cifra, viene inserito
   22.         'uno zero.
   23.         Console.Write("Public Key: ")
   24.         For Each B As Byte In Name.GetPublicKey()
   25.              Console.Write("{0:X2}", B)
   26.         Next
   27.         Console.WriteLine()
   28.
   29.         Console.Write("Public Key token: ")
   30.         For Each B As Byte In Name.GetPublicKeyToken
   31.              Console.Write("{0:X2}", B)
   32.         Next
   33.         Console.WriteLine()
   34.
   35.         Console.WriteLine("Processore: " & _
   36.              Name.ProcessorArchitecture.ToString)
   37.
   38.         Console.ReadKey()
   39.
   40.     End Sub
   41.
   42. End Module
Con quello che abbiamo visto fin'or a si potr ebbe scr iver e una pr ocedur a che enumer i tutti gli assembly pr esenti nel
contesto cor r ente:

   01. Sub EnumerateAssemblies()
   02.     Dim Asm As Assembly
   03.     Dim Name As AssemblyName
   04.
   05.     'AppDomain è una variabile globale, oggetto singleton, da cui
   06.     'si possono trarre informazioni sull'AppDomain corrente o
   07.     'crearne degli altri.
   08.     For Each Asm In AppDomain.CurrentDomain.GetAssemblies
   09.          Name = Asm.GetName
   10.          Console.WriteLine("Nome: " & Name.Name)
   11.          Console.WriteLine("Versione: " & Name.Version.ToString)
   12.          Console.Write("Public Key Token: ")
   13.          For Each B As Byte In Name.GetPublicKeyToken
   14.               Console.Write(Hex(B))
   15.          Next
   16.          Console.WriteLine()
   17.          Console.WriteLine()
   18.     Next
   19. End Sub
A45. La Reflection - Parte II

La c lasse Sy stem.Ty pe
La classe Type è una classe davver o par ticolar e, poiché r appr esenta un tipo. Con tipo indichiamo tutte le possibili
tipologie di dato esistenti: tipi base, enumer ator i, str uttur e, classi e delegate. Per ogni tipo contemplato, esiste un
cor r ispettivo oggetto Type che lo r appr esenta: avevo detto all'inizio della guida, infatti, che ogni cosa in .NET è un
oggetto, ed i tipi non fanno eccezione. Vi sor pr ender ebbe saper e tutto ciò che può esser e r appr esentato da una classe
e fr a poco vi sveler ò un segr eto... Ma per or a concentr iamoci su Type. Questi oggetti r appr esentanti un tipo - che
possiamo chiamar e per br evità OT (non è un ter mine tecnico) - vengono cr eati dur ante la fase di inizializzazione del
pr ogr amma e ne esiste una e una sola copia per ogni singolo tipo all'inter no di un singolo AppDomain. Ciò significa che
due contesti applicativi differ enti avr anno due OT diver si per r appr esentar e lo stesso tipo, ma non analizzer emo
questa peculiar e casistica. Ci limiter emo, invece, a studiar e gli OT all'inter no di un solo dominio applicativo, coincidente
con il nostr o pr ogr amma.
Come per gli assembly, esistono molteplici modi per ottener e un OT:

       Tr amite l'oper ator e GetType(Tipo);
       Tr amite la funzione d'istanza GetType();
       Tr amite la funzione condivisa Type.GetType("nometipo").

Ecco un semplice esempio di come funzionano questi metodi:

   01. Module Module1
   02.
   03.     Sub Main()
   04.         'Ottiene un OT per il tipo double tramite
   05.         'l'operatore GetType
   06.         Dim DoubleType As Type = GetType(Double)
   07.         Console.WriteLine(DoubleType.FullName)
   08.
   09.         'Ottiene un OT per il tipo string tramite
   10.         'la funzione statica Type.GetType. Essa richiede
   11.         'come parametro il nome (possibilmente completo)
   12.         'del tipo. Nel caso il nome non corrisponda a
   13.         'nessun tipo, verrà restituito Nothing
   14.         Dim StringType As Type = Type.GetType("System.String")
   15.         Console.WriteLine(StringType.FullName)
   16.
   17.         'Ottiene un OT per il tipo ArrayList tramite
   18.         'la funzione d'istanza GetType. Da notare che,
   19.         'mentre le precedenti usavano come punto
   20.         'di partenza direttamente un tipo (o il suo nome),
   21.         'questa richiede un oggetto di quel tipo.
   22.         Dim A As New ArrayList
   23.         Dim ArrayListType As Type = A.GetType()
   24.         Console.WriteLine(ArrayListType.FullName)
   25.
   26.         Console.ReadKey()
   27.     End Sub
   28.
   29. End Module

Or a che ho esemplificato come ottener e un OT, vor r ei mostr ar e l'unicità di OT ottenuti in modi differ enti: anche se
usassimo tutti i tr e metodi sopr a menzionati per ottener e un OT per il tipo Str ing, otter r emo un r ifer imento allo
stesso oggetto, poiché il tipo Str ing è unico:

   01. Module Module1
   02.
   03.     Sub Main()
   04.
Dim Type1 As Type = GetType(String)
   05.         Dim Type2 As Type = Type.GetType("System.String")
   06.         Dim Type3 As Type = "Ciao".GetType()
   07.
   08.         Console.WriteLine(Type1 Is Type2)
   09.         '> True
   10.         Console.WriteLine(Type2 Is Type3)
   11.         '> True
   12.
   13.         'Gli OT contenuti in Type1, Type2 e Type3
   14.         'SONO lo stesso oggetto
   15.
   16.         Console.ReadKey()
   17.     End Sub
   18.
   19. End Module

Questo non vale per il tipo System.Type stesso, poiché il metodo d'istanza GetType r estituisce un oggetto RuntimeType.
Questi dettagli, tuttavia, non vi inter esser anno se non tr a un bel po' di tempo, quindi possiamo anche evitar e di
soffer mar ci e pr oceder e con la spiegazione.
Ogni oggetto Type espone una quantità inimmaginabile di membr i e penso che potr ebbe esser e la classe più ampia di
tutto il Fr amew or k. Di questa massa enor me di infor mazioni, ve ne è un sottoinsieme che per mette di saper e in che
modo il tipo è stato dichiar ato e quali sono le sue car atter istiche pr incipali. Possiamo r icavar e, ad esempio, gli
specificator i di accesso, gli eventuali modificator i, possiamo saper e se si tr atta di una classe, un enumer ator e, una
str uttur a o altr o, e, nel pr imo caso, se è astr atta o sigillata; possiamo saper e le sua classe base, le inter facce che
implementa, se si tr atta di un ar r ay o no, ecceter a... Di seguito elenco i membr i di questo sottoinsieme:

       Assembly : r estituisce l'assembly a cui il tipo appar tiene (ossia in cui è stato dichiar ato);
       AssemblyQualifiedName : r estituisce il nome dell'assembly a cui il tipo appar tiene;
       BaseType : se il tipo cor r ente er edita da una classe base, questa pr opr ietà r estituisce un oggetto Type in
       r ifer imento a tale classe;
       Declar ingMethod : se il tipo cor r ente è par ametr o di un metodo, questa pr opr ietà r estituisce un oggetto
       MethodBase che r appr esenta tale metodo;
       Declar ingType : se il tipo cor r ente è membr o di una classe, questa pr opr ietà r estituisce un oggetto Type che
       r appr esenta tale classe; questa pr opr ietà viene valor izzata, quindi, solo se il tipo è stato dichiar ato all'inter no
       di un altr o tipo (ad esempio classi nidificate);
       FullName : il nome completo del tipo cor r ente;
       IsAbstr act : deter mina se il tipo è una classe astr atta;
       IsAr r ay : deter mina se è un ar r ay;
       IsClass : deter mina se è una classe;
       IsEnum : deter mina se è un enumer ator e;
       IsInter face : deter mina se è un'inter faccia;
       IsNested : deter mina se il tipo è nidificato: questo significa che r appr esenta un membr o di classe o di str uttur a;
       di conseguenza tutte le pr opr ietà il cui nome inizia per "IsNested" ser vono a deter minar e l'ambito di visibilità
       del membr o, e quindi il suo specificator e di accesso;
       IsNestedAssembly : deter mina se il membr o è Fr iend;
       IsNestedFamily : deter mina se il membr o è Pr otected;
       IsNestedFamORAssem : deter mina se il membr o è Pr otected Fr iend;
       IsNestedPr ivate : deter mina se il membr o è Pr ivate;
       IsNestedPublic : deter mina se il membr o è Public;
       IsNotPublic : deter mina se il tipo non è Public (solo per tipi non nidificati). Vi r icor do, infatti, che all'inter no di
       un namespace, gli unici specificator i possibili sono Public e Fr iend (gli altr i si adottano solo all'inter no di una
       classe);
       IsPointer : deter mina se è un puntator e;
IsPr imitive : deter mina se è uno dei tipi pr imitivi;
       IsPublic : deter mina se il tipo è Public (solo per tipi non nidificati);
       IsSealed : deter mina se è una classe sigillata;
       IsValueType : deter mina se è un tipo value;
       Name : il nome del tipo cor r ente;
       Namespace : il namespace in cui è contenuto il tipo cor r ente.

Con questa abbondante manciata di pr opr ietà possiamo iniziar e a scr iver e un metodo di analisi un po' più
appr ofondito. Nella fattispecie, la pr ossima pr ocedur a Enumer ateTypes accetta come par ametr o il r ifer imento ad un
assembly e scr ive a scher mo tutti i tipi ivi definiti:

   01. Module Module1
   02.
   03.     Sub EnumerateTypes(ByVal Asm As Assembly)
   04.         Dim Category As String
   05.
   06.         'GetTypes restituisce un array di Type che
   07.         'indicano tutti i tipi definiti all'interno
   08.         'dell'assembly Asm
   09.         For Each T As Type In Asm.GetTypes
   10.              If T.IsClass Then
   11.                  Category = "Class"
   12.              ElseIf T.IsInterface Then
   13.                  Category = "Interface"
   14.              ElseIf T.IsEnum Then
   15.                  Category = "Enumerator"
   16.              ElseIf T.IsValueType Then
   17.                  Category = "Structure"
   18.              ElseIf T.IsPrimitive Then
   19.                  Category = "Base Type"
   20.              End If
   21.              Console.WriteLine("{0} ({1})", T.Name, Category)
   22.         Next
   23.     End Sub
   24.
   25.     Sub Main()
   26.         'Ottiene un riferimento all'assembly in esecuzione,
   27.         'quindi al programma. Non otterrete molti tipi
   28.         'usando questo codice, a meno che il resto del
   29.         'modulo non sia pieno di codice vario come nel
   30.         'mio caso XD
   31.         Dim Asm As Assembly = Assembly.GetExecutingAssembly()
   32.
   33.         EnumerateTypes(Asm)
   34.
   35.         Console.ReadKey()
   36.     End Sub
   37.
   38. End Module




Il nostro pic c olo segreto
Pr ima di pr oceder e con l'enumer azione dei membr i, vor r ei mostr ar e che in r ealtà tutti i tipi sono classi, soltanto con
r egole "speciali" di er editar ietà e di sintassi. Questo codice r intr accia tutte le classi basi di un tipo, costr uendone
l'alber o di er editar ietà fino alla r adice (che sar à ovviamente System.Object):

   01. Module Module1
   02.
   03.     'Analizza l'albero di ereditarietà di un tipo
   04.     Sub AnalyzeInheritance(ByVal T As Type)
   05.         'La proprietà BaseType restituisce la classe
   06.         'base da cui T è derivata
   07.         If T.BaseType IsNot Nothing Then
   08.              Console.WriteLine("> " & T.BaseType.FullName)
   09.
AnalyzeInheritance(T.BaseType)
   10.            End If
   11.        End Sub
   12.
   13.        Enum Status
   14.            Enabled
   15.            Disabled
   16.            Standby
   17.        End Enum
   18.
   19.        Structure Example
   20.            Dim A As Int32
   21.        End Structure
   22.
   23.        Delegate Sub Sample()
   24.
   25.        Sub Main()
   26.            Console.WriteLine("Integer:")
   27.            AnalyzeInheritance(GetType(Integer))
   28.            Console.WriteLine()
   29.
   30.             Console.WriteLine("Enum Status:")
   31.             AnalyzeInheritance(GetType(Status))
   32.             Console.WriteLine()
   33.
   34.             Console.WriteLine("Structure Example:")
   35.             AnalyzeInheritance(GetType(Example))
   36.             Console.WriteLine()
   37.
   38.             Console.WriteLine("Delegate Sample:")
   39.             AnalyzeInheritance(GetType(Sample))
   40.             Console.WriteLine()
   41.
   42.            Console.ReadKey()
   43.        End Sub
   44.
   45. End    Module

L'output mostr a che il tipo Integer e la str uttur a Ex ample der ivano entr ambi da System.ValueType, che a sua volta
der iva da Object. La definizione r igor osa di "tipo value", quindi, sar ebbe "qualsiasi tipo der ivato da System.ValueType".
Infatti, al par i dei pr imi due, anche l'enumer ator e der iva indir ettamente da tale classe, anche se mostr a un passaggio
in più, attr aver so il tipo System.Enum. Allo stesso modo, il delegate Sample der iva dalla classe DelegateMulticast, la
quale der ivata da Delegate, la quale der iva da Object. La differ enza sostanziale tr a tipi value e r efer ence, quindi,
r isiede nel fatto che i pr imi hanno almeno un passaggio di er editar ietà attr aver so la classe System.ValueType, mentr e
i secondi der ivano dir ettamente da Object.
System.Enum e System.Delegate sono classi astr atte che espongono utili metodi statici che potete ispezionar e da soli
(sono pochi e di facile compr ensione). Ma or a che sapete che tutti i tipi sono classi, potete anche esplor ar e i membr i
esposti dai tipi base.




Enumerare i membri
Fino ad or a abbiamo visto solo come analizzar e i tipi, ma ogni tipo possiede anche dei membr i (var iabili, metodi,
pr opr ietà, eventi, ecceter a...). La Reflection per mette anche di ottener e infor mazioni sui membr i di un tipo, e la
classe in cui queste infor mazioni vengono poste è Member Info, del namespace System.Reflection. Dato che ci sono
diver se categor ie di membr i, esistono altr ettante classi der ivate da Member Info che ci r accontano una stor ia tutta
diver sa a seconda di cosa stiamo guar dando: MethodInfo contiene infor mazioni su un metodo, Pr oper tyInfo su una
pr opr ietà, Par amter Info su un par ametr o, FieldInfo su un campo e via dicendo. Fr a le molteplici funzioni esposte da
Type, ce ne sono alcune che ser vono pr opr io a r eper ir e questi dati; eccone un elenco:

       GetConstr uctor s() : r estituisce un ar r ay di Constr uctor Info, ognuno dei quali r appr esenta uno dei costr uttor i
       definiti per quel tipo;
GetEvents() : r estituisce un ar r ay di EventInfo, ognuno dei quali r appr esenta uno degli eventi dichiar ati in quel
       tipo;
       GetFields() : r estituisce un ar r ay di FieldInfo, ognuno dei quali r appr esenta uno dei campi dichiar ati in quel tipo;
       GetInter faces() : r estituisce un ar r ay di Type, ognuno dei quali r appr esenta una delle inter facce implementate
       da quel tipo;
       GetMember s() : r estituisce un ar r ay di Member Info, ognuno dei quali r appr esenta uno dei membr i dichiar ati in
       quel tipo;
       GetMethods() : r estituisce un ar r ay di MethodInfo, ognuno dei quali r appr esenta uno dei metodi dichiar ati in
       quel tipo;
       GetNestedTypes() : r estituisce un ar r ay di Type, ognuno dei quali r appr esenta uno dei tipi dichiar ati in quel
       tipo;
       GetPr oper ties() : r estituisce un ar r ay di Pr oper tyInfo, ognuno dei quali r appr esenta una delle pr opr ietà
       dichiar ate in quel tipo;

La funzione GetMember s, da sola, ci for nisce una lista gener ale di tutti i membr i di quel tipo:

   01. Module Module1
   02.     Sub Main()
   03.         Dim T As Type = GetType(String)
   04.
   05.         'Elenca tutti i membri di String
   06.         For Each M As MemberInfo In T.GetMembers
   07.              'La proprietà MemberType restituisce un enumeratore che
   08.              'specifica di che tipo sia il membro, se una proprietà,
   09.              'un metodo, un costruttore, eccetera...
   10.              Console.WriteLine(M.MemberType.ToString & " " & M.Name)
   11.         Next
   12.
   13.         Console.ReadKey()
   14.     End Sub
   15. End Module

Eseguendo il codice appena pr oposto, potr ete notar e che a scher mo appaiono tutti i membr i di Str ing, ma molti sono
r ipetuti: questo si ver ifica per chè i metodi che possiedono delle var ianti in over load vengono r ipor tati tante volte
quante sono le var ianti; natur alemnte, ogni oggetto MethodInfo sar à diver so dagli altr i per le infor mazioni sulla
quantità e sul tipo di par ametr i passati a tale metodo. Accanto a questa str anezza, noter ete, poi, che per ogni
pr opr ietà ci sono due metodi definiti come get_NomePr opr ietà e set_NomePr opr ietà: questi metodi vengono cr eati
automaticamente quando il codice di una pr opr ietà viene compilato, e vengono eseguiti al momento di impostar e od
ottener e il valor e di tale pr opr ietà. Altr a str anezza è che tutti i costr uttor i si chiamano ".ctor " e non New . Stiamo
cominciando ad entr ar e nel mondo dell'Inter mediate Language, il linguaggio inter medio simil-macchina in cui vengono
conver titi i sor genti una volta compilati. Di fatto, noi stiamo eseguendo il pr ocesso inver so della compilazione, ossia la
deco m pilazio ne. Alcune infor mazioni vengono manipolate nel passaggio da codice a IL, e quando si tor na indietr o, le
si vede in altr o modo, ma tutta l'infor mazione necessar ia è ancor a contenuta lì dentr o. Non esiste, tuttavia, una classe
già scr itta che r itr aduca in codice tutto il linguaggio inter medio: ciò che il Fr amew or k ci for nisce ci consente solo di
conoscer e "a pezzi" tutta l'infor mazione ivi contenuta, ma sottolineiamo "tutta". Sar ebbe, quindi, possibile - ed infatti è
già stato fatto - r itr adur r e tutti questi dati in codice sor gente. Per or a, ci limiter emo a "r icostr uir e" la signatur e di
un metodo.
Pr ima di pr oceder e, vi for nisco un br eve elenco dei membr i significativi di ogni der ivato di Member Info:


M em ber Info

       Declar ingType : la classe che dichiar a questo membr o;
       Member Type : categor ia del membr o;
       Name : il nome del membr o;
ReflectedType : il tipo usato per ottener e un r ifer imento a questo membr o tr amite r eflection;



M etho dInfo

       GetBaseDefinition() : se il metodo è modificato tr amite polimor fismo, r estituisce la ver sione della classe base (se
       non è stato sottoposto a polimor fismo, r estituisce Nothing);
       GetCur r entMethod() : r estituisce un MethodInfo in r ifer imento al metodo in cui questa funzione viene chiamata;
       GetMethodBody() : r estituisce un oggetto MethodBody (che vedr emo in seguito) contenente infor mazioni sulle
       var iabili locali, le eccezioni e il codice IL;
       GetPar ameter s() : r estituisce un elenco di Par ameter Info r appr esentanti i par ametr i del metodo;
       IsAbstr act : deter mina se il metodo è MustOver r ide;
       IsConstr uctor : deter mina se è un costr uttor e;
       IsFinal : deter mina se è NotOver r idable;
       IsStatic : deter mina se è Shar ed;
       IsVir tual : deter mina se è Over r idable;
       Retur nPar ameter : qualor a il metodo fosse una funzione, r estituisce infor mazioni sul valor e r estituito;
       Retur nType : in una funzione, r estituisce l'oggetto Type associato al tipo r estituito. Se il metodo non è una
       funzione, r estituisce Nothing o uno speciale OT in r ifer imento al tipo System.Void.



FieldInfo

       GetRaw CostantValue() : se il campo è una costante, ne r estituisce il valor e;
       IsLiter al : deter mina se è una costante;
       IsInitOnly : deter mina se è una var iabile r eadonly;



Pr o per tyInfo

       CanRead : deter mina se si può legger e la pr opr ietà;
       CanWr ite : deter mina se si può impostar e la pr opr ietà;
       GetGetMethod() : r estituisce un MethodInfo cor r ispondente al blocco Get;
       GetSetMethod() : r estituisce un MethodInfo cor r ispondente al blocco Set;
       GetPr oper tyType() : r estituisce un oggetto Type in r ifer imento al tipo della pr opr ietà.



Ev entInfo (per ulter ior i infor mazioni, veder e i capitoli della sezione B sugli eventi)

       GetAddMethod() : r estituisce un r ifer imento al metodo usato per aggiunger e gli handler d'evento;
       GetRaiseMethod() : r estituisce un r ifer imento al metodo che viene r ichiamato quando si scatena l'evento;
       GetRemoveMethod() : r estituisce un r ifer imento al metodo usato per r imuover e gli handler d'evento;
       IsMulticast : indica se l'evento è gestito tr amite un delegate multicast.




 001. Module Module1
 002.     'Analizza il metodo rappresentato dall'oggetto MI
 003.     Sub AnalyzeMethod(ByVal MI As MethodInfo)
 004.         'Il nome
 005.         Dim Name As String
 006.         'Il nome completo, con scpecificatori di accesso,
 007.         'modificatori, signature e tipo restituito. Per
 008.         'ulteriori informazioni sul tipo StringBuilder,
 009.         'vedere il capitolo "Magie con le stringhe"
 010.
Dim CompleteName As New System.Text.StringBuilder
011.   'Lo specificatore di accesso
012.   Dim Scope As String
013.   'Gli eventuali modificatori
014.   Dim Modifier As String
015.   'La categoria: Sub o Function
016.   Dim Category As String
017.   'La signature del metodo, che andremo a costruire
018.   Dim Signature As New System.Text.StringBuilder
019.
020.   'Di solito, tutti i metodi hanno un tipo restituito,
021.   'poiché, in analogia con la sintassi del C#, una
022.   'procedura è una funzione che restituisce Void,
023.   'ossia niente. Per questo bisogna controllare anche il
024.   'nome del tipo di ReturnParameter
025.   If MI.ReturnParameter IsNot Nothing AndAlso _
026.        MI.ReturnType.FullName <> "System.Void" Then
027.        Category = "Function"
028.   Else
029.        Category = "Sub"
030.   End If
031.
032.   If MI.IsConstructor Then
033.        Name = "New"
034.   Else
035.        Name = MI.Name
036.   End If
037.
038.   If MI.IsAssembly Then
039.        Scope = "Friend"
040.   ElseIf MI.IsFamily Then
041.        Scope = "Protected"
042.   ElseIf MI.IsFamilyOrAssembly Then
043.        Scope = "Protected Friend"
044.   ElseIf MI.IsPrivate Then
045.        Scope = "Private"
046.   Else
047.        Scope = "Public"
048.   End If
049.
050.   If MI.IsFinal Then
051.       'Vorrei far notare una sottigliezza. Se il metodo è
052.       'Final, ossia NotOverridable, significa che non può
053.       'essere modificato nelle classi derivate. Ma tutti i
054.       'membri non dichiarati esplicitamente Overridable
055.       'non sono modificabili nelle classi derivate. Quindi,
056.       'definire un metodo senza modificatori polimorfici
057.       '(come quelli che seguono qua in basso), equivale a
058.       'definirlo NotOverridable. Perciò non si
059.       'aggiunge nessun modificatore in questo caso
060.   ElseIf MI.IsAbstract Then
061.       Modifier = "MustOverride"
062.   ElseIf MI.IsVirtual Then
063.       Modifier = "Overridable"
064.   ElseIf MI.GetBaseDefinition IsNot Nothing AndAlso _
065.       MI IsNot MI.GetBaseDefinition Then
066.       Modifier = "Overrides"
067.   End If
068.
069.   If MI.IsStatic Then
070.       If Modifier <> "" Then
071.            Modifier = "Shared " & Modifier
072.       Else
073.            Modifier = "Shared"
074.       End If
075.   End If
076.
077.   'Inizia la signature con una parentesi tonda aperta.
078.   'Append aggiunge una stringa a Signature
079.   Signature.Append("(")
080.   For Each P As ParameterInfo In MI.GetParameters
081.       'Se P è un parametro successivo al primo, lo separa dal
082.
'precedente con una virgola
083.       If P.Position > 0 Then
084.           Signature.Append(", ")
085.       End If
086.
087.       'Se P è passato per valore, ci vuole ByVal, altrimenti
088.       'ByRef. IsByRef è un membro di Type, ma viene
089.       'usato solo quando il tipo in questione indica il tipo
090.       'di un parametro
091.       If P.ParameterType.IsByRef Then
092.            Signature.Append("ByRef ")
093.       Else
094.            Signature.Append("ByVal ")
095.       End If
096.
097.       'Se P è opzionale, ci vuole la keyword Optional
098.       If P.IsOptional Then
099.           Signature.Append("Optional ")
100.       End If
101.
102.
103.       Signature.Append(P.Name)
104.       If P.ParameterType.IsArray Then
105.           Signature.Append("()")
106.       End If
107.       'Dato che la sintassi del nome è in stile C#, al
108.       'posto delle parentesi tonde in un array ci sono delle
109.       'quadre: rimediamo
110.       Signature.Append(" As " & P.ParameterType.Name.Replace("[]",""))
111.
112.       'Si ricordi che i parametri optional hanno un valore
113.       'di default
114.       If P.IsOptional Then
115.           Signature.Append(" = " & P.DefaultValue.ToString)
116.       End If
117.   Next
118.   Signature.Append(")")
119.
120.   If MI.ReturnParameter IsNot Nothing AndAlso _
121.       MI.ReturnType.FullName <> "System.Void" Then
122.       Signature.Append(" As " & MI.ReturnType.Name)
123.   End If
124.
125.   'Ed ecco il nome completo
126.   CompleteName.AppendFormat("{0} {1} {2} {3}{4}", Scope, Modifier, _
127.   Category, Name, Signature.ToString)
128.   Console.WriteLine(CompleteName.ToString)
129.   Console.WriteLine()
130.
131.   'Ora ci occupiamo del corpo
132.   Dim MB As MethodBody = MI.GetMethodBody
133.
134.   If MB Is Nothing Then
135.       Exit Sub
136.   End If
137.
138.   'Massima memoria occupata sullo stack
139.   Console.WriteLine("Massima memoria stack : {0} bytes", _
140.       MB.MaxStackSize)
141.   Console.WriteLine()
142.
143.   'Variabili locali (LocalVariableInfo è una variante di
144.   'FieldInfo)
145.   Console.WriteLine("Variabili locali:")
146.   For Each L As LocalVariableInfo In MB.LocalVariables
147.        'Dato che non si può ottenere il nome, ci si deve
148.        'accontentare di un indice
149.        Console.WriteLine(" Var({0}) As {1}", L.LocalIndex, _
150.            L.LocalType.Name)
151.   Next
152.   Console.WriteLine()
153.
154.
'Gestione delle eccezioni
 155.            Console.WriteLine("Gestori di eccezioni:")
 156.            For Each Ex As ExceptionHandlingClause In MB.ExceptionHandlingClauses
 157.                 'Tipo di clausola: distingue tra filtro (When),
 158.                 'clausola (Catch) o un blocco Finally
 159.                 Console.WriteLine(" Tipo : {0}", Ex.Flags.ToString)
 160.                 'Se si tratta di un blocco Catch, ne specifica la
 161.                 'natura
 162.                 If Ex.Flags = ExceptionHandlingClauseOptions.Clause Then
 163.                     Console.WriteLine("    Catch Ex As " & Ex.CatchType.Name)
 164.                 End If
 165.                 'Offset, ossia posizione in bytes nel Try, del gestore
 166.                 Console.WriteLine(" Offset : {0}", Ex.HandlerOffset)
 167.                 'Lunghezza, in bytes, del codice eseguibile del gestore
 168.                 Console.WriteLine(" Lunghezza : {0}", Ex.HandlerLength)
 169.                 Console.WriteLine()
 170.            Next
 171.        End Sub
 172.
 173.        Sub Test(ByVal Num As Int32, ByVal S As String)
 174.            Dim T As Date
 175.            Dim V As String
 176.
 177.              Try
 178.                Console.WriteLine("Prova 1, 2, 3")
 179.            Catch Ex As ArithmeticException
 180.                Console.WriteLine("Errore 1")
 181.            Catch Ex As ArgumentException
 182.                Console.WriteLine("Errore 2")
 183.            Finally
 184.                Console.WriteLine("Ciao")
 185.            End Try
 186.        End Sub
 187.
 188.        Sub Main()
 189.            Dim T As Type = GetType(Module1)
 190.            Dim Methods() As MethodInfo = T.GetMethods
 191.            Dim Index As Int16
 192.
 193.              Console.WriteLine("Inserire un numero tra i seguenti per analizzare il metodo
                      corrispondente:")
 194.              Console.WriteLine()
 195.              For I As Int16 = 0 To Methods.Length - 1
 196.                   Console.WriteLine("{0} - {1}", I, Methods(I).Name)
 197.              Next
 198.              Console.WriteLine()
 199.              Index = Console.ReadLine
 200.
 201.         If Index >= 0 And Index &rt; Methods.Length Then
 202.             AnalyzeMethod(Methods(Index))
 203.         End If
 204.
 205.         Console.ReadKey()
 206.     End Sub
 207.
 208. End Module

Analizzando il metodo Test, si otter r à questo output:

 Public Shared Sub Test(ByVal Num As Int32, ByVal S As String)

 Massima memoria stack : 2 bytes

 Variabili locali:
   Var(0) As DateTime
   Var(1) As String
   Var(2) As ArithmeticException
   Var(3) As ArgumentException
Gestori di eccezioni:
  Tipo : Clause
    Catch Ex As ArithmeticException
  Offset : 15
  Lunghezza : 26

 Tipo : Clause
   Catch Ex As ArgumentException
 Offset : 41
 Lunghezza : 26

 Tipo : Finally
 Offset : 67
 Lunghezza : 13
A46. La Reflection - Parte III

Reflec tion dei generic s
I gener ics si compor tano in modo differ ente in molti ambiti, e la Reflection r icade pr opr io fr a questi. Infatti, un Type
che r appr esenta un tipo gener ic non ha lo stesso nome di quando è stato dichiar ato nel codice, ma possiede una for ma
contr atta e diver sa. Ad esempio, ammettendo che l'assembly che stiamo analizzando contenga questa classe:

    1. Class Example(Of T, K)
    2.     '...
    3. End Class

quando tr over emo l'oggetto Type che la r appr esenta dur ante l'enumer azione dei tipi, scopr ir emo che il suo nome è
molto str ano. Sar à molto simile a questa str inga:

 Example`2


In questa par ticolar e for mattazione, il due indica che la classe ex ample lavor a su due tipi gener ics: i lor o nome
"vir tuali" non vengono r ipor tati nel nome, cosicché anche confr ontando i nomi di due OT indicanti tipi gener ics, magar i
pr ovenienti da AppDomain diver si, si capisce che in r ealtà sono pr opr io lo stesso tipo, poiché la ver a differ enza sta
solo nel nome e nella quantità di par ametr i gener ics (l'identificator e di questi ultimi, infatti, essendo solo un
segnaposto, è ininfluente). Nonostante l'assenza di dettagli, ci sono delle pr opr ietà che ci per mettono di r ecuper ar e il
nome dei tipi gener ics aper ti, ossia "T" e "K" in questo caso. In gener ale, per lavor ar e su classi o tipi genr ics, è
impor tante far e affidamento su questi membr i di Type:

       IsGener icTypeDefinition : deter mina se questo Type r appr esenta una definizione di un tipo gener ics. Fate
       attenzione ai dettagli, poiché esiste un'altr a pr opr ietà molto simile con la quale ci si può confonder e. Affinché
       questa pr oper ietà r estituisca Tr ue è necessar io (e sufficiente) che il tipo che stiamo esaminando contenga una
       definizione di uno o più tipi gener ics APERTI (e n on collegati). Ad esempio:

          01. Module Module1
          02.
          03.     'Dichiaro questa classe e la prossima variabile come
          04.     'pubblici perchè se fossero Friend bisognerebbe
          05.     'usare un overload troppo lungo di GetField e
          06.     'GetNestedTypes specificando ci cercare i membri non
          07.     'pubblici. Di default, le funzioni di ricerca operano
          08.     'solo su membri pubblici
          09.
          10.     Public Class Example(Of T)
          11.
          12.     End Class
          13.
          14.     Public E As Example(Of Int32)
          15.
          16.     Sub Main()
          17.         'Ottiene il tipo di questo modulo
          18.         Dim ModuleType As Type = GetType(Module1)
          19.
          20.         'Enumera tutti i tipi presenti nel modulo fino a
          21.         'trovare la classe Example. Ho usato un for perchè
          22.         'non si può usare GetType (in qualsiasi
          23.         'sua versione) su una classe generics senza specificare
          24.         'un tipo generics collegato, cosa che noi non
          25.         'vogliamo affatto. Per ottenere il riferimento a
          26.         'Example(Of T) bisogna per forza usare una funzione
          27.         'che restituisca tutti i tipi esistenti e poi
          28.         'cercarlo tra questi.
          29.         For Each T As Type In ModuleType.GetNestedTypes()
          30.
If T.Name.StartsWith("Example") Then
          31.                         Console.WriteLine("{0} - IsGenericTypeDefinition: {1}", _
          32.                             T.Name, T.IsGenericTypeDefinition)
          33.                     End If
          34.              Next
          35.
          36.              'Ottiene un riferimento al campo E dichiarayo sopra
          37.              Dim EField As FieldInfo = ModuleType.GetField("E")
          38.              'E ne ottiene il tipo
          39.              Dim EType As Type = EField.FieldType
          40.
          41.              Console.WriteLine("{0} - IsGenericTypeDefinition: {1}", EType.Name,
                              EType.IsGenericTypeDefinition)
          42.
          43.         Console.ReadKey()
          44.     End Sub
          45.
          46. End Module

       A scher mo appar ir à lo stesso nome due volte, ma in un caso IsGener icTypeDefinition sar à Tr ue e nell'altr o False.
       Questo per chè il tipo della var iabile E è sì dichiar ato come gener ic, ma all'atto pr atico lavor a su un solo tipo:
       Int32; per ciò non si tr atta di una defin izion e di tipo gener ic, ma di un uso di un tipo gener ic;
       IsGener icType : molto simile alla pr ecedente, ma funziona al contr ar io, ossia r estituisce Tr ue se il tipo NON è
       una definizione di tipo gener ic, ma una sua applicazione mediante tipi collegati. Nell'esempio di pr ima,
       EType.IsGener icType sar ebbe stato Tr ue;
       GetGener icAr guments() : se almeno uno tr a IsGener icTypeDefinition e IsGener icType è ver o, allor a abbiamo a
       che far e con tipi gener ics. Questa funzione r estituisce gli OT dei tipi gener ics aper ti (nel pr imo caso) o collegati
       (nel secondo caso). Tr a br eve ne vedr emo un esempio.

Ecco un esempio di come enumer ar e tutti i tipi gener ics di un assembly:

   01. Module Module1
   02.
   03.     Sub EnumerateGenerics(ByVal Asm As Assembly)
   04.         For Each T As Type In Asm.GetTypes
   05.              'Controlla se si tratta di un tipo contenente
   06.              'tipi generics aperti
   07.              If T.IsGenericTypeDefinition Then
   08.                  'Ottiene il nome semplice di quel tipo (la
   09.                  'versione completa è troppo lunga XD)
   10.                  Dim Name As String = T.Name
   11.
   12.                  'Controlla che il nome contenga l'accento tonico.
   13.                  'Infatti, possono esistere casi in cui la
   14.                  'propietà IsGeneircTypeDefinition è vera,
   15.                  'ma non ci troviamo di fronte a un tipo la cui
   16.                  'signature contenga effettivamente tipi generics.
   17.                  'Ne darò un esempio dopo...
   18.                  If Not Name.Contains("`") Then
   19.                      Continue For
   20.                  End If
   21.
   22.                  'Ottiene una stringa in cui elimina tutti i
   23.                  'caratteri a partire dall'indice del'accento
   24.                  Name = T.Name.Remove(T.Name.IndexOf("`"))
   25.                  'E poi gli aggiunge un "(Of ", per far vedere che
   26.                  'si sta iniziando una dichiarazione generic
   27.                  Name &= "(Of "
   28.                  'Quindi aggiunge tutti gli argomenti generic
   29.                  For Each GenT As Type In T.GetGenericArguments
   30.                      'Se il parametro non è il primo, lo separa dal
   31.                      'precedente con una virgola.
   32.                      If GenT.GenericParameterPosition > 0 Then
   33.                           Name &= ", "
   34.                      End If
   35.                      'Quindi vi aggiunge il nome
   36.                      Name &= GenT.Name
   37.
Next
   38.                        'E chiude la parentesi
   39.                        Name &= ")"
   40.                        Console.WriteLine(Name)
   41.                    End If
   42.             Next
   43.         End Sub
   44.
   45.         'Notate che la classe Type espone molte proprietà che
   46.         'si possono usare solo in determinati casi. Ad esempio, in
   47.         'questo codice è lecito richiamare GenericParametrPosition
   48.         'poiché sappiamo a priori che quel Type indica un tipo
   49.         'generic in una signature generic. Ma un in un qualsiasi OT
   50.         'non ha alcun senso usare tale proprietà!
   51.
   52.         Sub Main()
   53.             'Ottiene un riferimento all'assembly corrente
   54.             Dim Asm As Assembly = Assembly.GetExecutingAssembly()
   55.
   56.              EnumerateGenerics(Asm)
   57.
   58.             Console.ReadKey()
   59.         End Sub
   60.
   61. End    Module

Ecco alcuni dei miei r isultati:

 ThreadSafeObjectProvider(Of T)
 Collection(Of T)
 ComparableCollection(Of T)
 Relation(Of T1, T2)
 IsARelation(Of T, U)
 DataFilter(Of T)


Riguar do all'if posto nel ciclo enumer ativo, vor r ei far notar e che IsGener icTypeDefinition r estituisce tr ue se r intr accia
nel tipo un r ifer imento ad un tipo gener ic aper to, indipendentemente che questo sia dichiar ato nel tipo o da un'altr a
par te. Ad esempio:

    1. Class Example(Of T)
    2.     Delegate Sub DoSomething(ByVal Data As T)
    3.     '...
    4. End Class

L'enumer azione r aggiunge anche DoSomething, poiché è anch'esso un tipo, anche se nidificato, accessibile a tutti i
membr i dell'assembly (o, se pubblico, a tutti); ed anche in quel caso, la pr opr ietà IsGener icTypeDefinition è Tr ue, poiché
la sua signatur e contiene un tipo gener ic aper to (T). Tuttavia, il suo nome non contiene accenti tonici, poiché il
gener ics è stato dichiar ato a livello di classe.
Ecco un altr o esempio, ma sui tipi gener ic collegati:

   01. Module Module1
   02.
   03.     'Enumera solo i campi generic di un tipo
   04.     Sub EnumerateGenericFieldMembers(ByVal T As Type)
   05.         For Each F As FieldInfo In T.GetFields()
   06.              If F.FieldType.IsGenericType Then
   07.                  Dim Name As String = F.FieldType.Name
   08.                  Dim I As Int16 = 0
   09.
   10.                  If Not Name.Contains("`") Then
   11.                      Continue For
   12.                  End If
   13.
   14.                  Name = Name.Remove(Name.IndexOf("`"))
   15.                  Name &= "(Of "
   16.                  For Each GenP As Type In F.FieldType.GetGenericArguments
   17.
'Dato che non si stanno analizzando dei
   18.                            'parametri generic, non si può utilizzare
   19.                            'la proprietà GenericParameterPosition
   20.                            If I > 0 Then
   21.                                Name &= ", "
   22.                            End If
   23.                            Name &= GenP.Name
   24.                            I += 1
   25.                       Next
   26.                       Name &= ")"
   27.                       Console.WriteLine("Dim {0} As {1}", F.Name, Name)
   28.                   End If
   29.            Next
   30.        End Sub
   31.
   32.        Public L As New List(Of Integer)
   33.        Public I As Int32?
   34.
   35.        Sub Main()
   36.            EnumerateGenericFieldMembers(GetType(Module1))
   37.
   38.            Console.ReadKey()
   39.        End Sub
   40. End    Module




L'uso della Reflec tion
Fino ad or a non abbiamo fatto altr o che enumer ar e membr i e tipi. Devo dir lo, una cosa un po' noiosa... Tuttavia ci è
ser vita per compr ender e come far e per acceder e a cer te infor mazioni che si celano negli assembly. Anche se non
user emo quasi mai la r eflection per enumer ar e le par ti di un assembly (a meno che non decidiate di scr iver e un object
br ow ser ), or a sappiamo quali infor mazioni possiamo r aggiunger e e come pr ender le. Questo è impor tante sopr attutto
quando si lavor a con assembly che vengono car icati dinamicamente, ad esempio in un sistema di plug-ins, come
mostr er ò fr a poco. Per dar vi un assaggio della potenza della r eflection, ho scr itto un semplice codice che per mette di
acceder e a tutte le infor mazioni di un oggetto, qualsiasi esso sia, di qualunque tipo e in qualunque assembly. Per far lo,
mi è bastato ottener ne le pr opr ietà:

   01. Module Module1
   02.
   03.     'Stampa tutte le informazioni ricavabili dalle
   04.     'proprietà di un dato oggetto O. Indent è solo
   05.     'una variabile d'appoggio per la formattazione, in modo
   06.     'da indentare bene le righe nel caso i valori delle
   07.     'proprietà siano altri oggetti.
   08.     Public Sub PrintInfo(ByVal O As Object, ByVal Indent As String)
   09.         'Ottiene il tipo di O
   10.         Dim T As Type = O.GetType()
   11.
   12.         Console.WriteLine("{0}Object of type {1}", Indent, T.Name)
   13.         'Enumera tutte le proprietà
   14.         For Each Prop As PropertyInfo In T.GetProperties()
   15.              'Ottiene il tipo restituito dalla proprietà
   16.              Dim PropType As Type = Prop.PropertyType()
   17.
   18.              'Se si tratta di una proprietà parametrica,
   19.              'la salta: in questo esempio non volevo dilungarmi,
   20.              'ma potete completare il codice se desiderate.
   21.              If Prop.GetIndexParameters().Count > 0 Then
   22.                  Continue For
   23.              End If
   24.
   25.              'Se è un di tipo base o una stringa (giacché le
   26.              'stringhe non sono tipo base ma reference), ne stampa
   27.              'direttamente il valore a schermo
   28.              If (PropType.IsPrimitive) Or (PropType Is GetType(String)) Then
   29.                  Console.WriteLine("{0} {1} = {2}", _
   30.
Indent, Prop.Name, Prop.GetValue(O, Nothing))
   31.
   32.                   'Altrimenti, se si tratta di un oggetto, lo analizza a
   33.                   'sua volta
   34.                   ElseIf PropType.IsClass Then
   35.                       Console.WriteLine("{0} {1} = ", Indent, Prop.Name)
   36.                       PrintInfo(Prop.GetValue(O, Nothing), Indent & "    ")
   37.                   End If
   38.            Next
   39.            Console.WriteLine()
   40.        End Sub
   41.
   42.        Sub Main()
   43.            'Crea alcuni   oggetti vari
   44.            Dim P As New   Person("Mario", "Rossi", New Date(1982, 3, 17))
   45.            Dim T As New   Teacher("Luigi", "Bianchi", New Date(1879, 8, 21), "Storia")
   46.            Dim R As New   Relation(Of Person, Teacher)(P, T)
   47.            Dim Q As New   List(Of Int32)
   48.            Dim K As New   Text.StringBuilder()
   49.
   50.             'Ne stampa le proprietà, senza sapere nulla a priori
   51.             'sulla natura degli oggetti.
   52.             'Notate che i nomi generics rimangono con l'accento...
   53.             PrintInfo(P, "")
   54.             PrintInfo(T, "")
   55.             PrintInfo(R, "")
   56.             PrintInfo(Q, "")
   57.             PrintInfo(K, "")
   58.
   59.           Console.ReadKey()
   60.       End Sub
   61. End   Module

L'output sar à questo:

 Object of type Person
   FirstName = Mario
   LastName = Rossi
   CompleteName = Mario Rossi

 Object of type Teacher
   Subject = Storia
   LastName = Prof. Bianchi
   CompleteName = Prof. Luigi Bianchi, dottore in Storia
   FirstName = Luigi

 Object of type Relation`2
   FirstObject =
     Object of type Person
       FirstName = Mario
       LastName = Rossi
       CompleteName = Mario Rossi

   SecondObject =
     Object of type Teacher
       Subject = Storia
       LastName = Prof. Bianchi
       CompleteName = Prof. Luigi Bianchi, dottore in Storia
       FirstName = Luigi



 Object of type List`1
   Capacity = 0
   Count = 0
Object of type StringBuilder
   Capacity = 16
   MaxCapacity = 2147483647
   Length = 0


Per scr iver e questo codice mi sono basato sul metodo GetValue esposto dalla classe Pr oper tyInfo. Esso per mette di
ottener e il valor e che la pr opr ietà r appr esentata dall'oggetto Pr oper tyInfo da cui viene invocato possiede nell'oggetto
specificato come par ametr o. In gener ale, GetValue accetta due par ametr i: il pr imo è l'oggetto da cui estr ar r e il valor e
della pr opr ietà, mentr e il secondo è un ar r ay di oggetti che r appr esenta i par ametr i da passar e alla pr opr ietà. Come
avete visto, ho enumer ato solo pr opr ietà non par ametr iche e per ciò non c'er a bisogno di for nir e alcun par ametr o:
ecco per chè ho messo Nothing.
Al par i di GetValue c'è SetValue che per mette di impostar e, invece, la pr opr ietà (ma solo se non è in sola lettur a, ossia
se CanWr ite è Tr ue). Ovviamente SetValue ha un par ametr o in più, ossia il valor e da impostar e (secondo par ametr o).
Ecco un esempio:

   01. Module Module1
   02.
   03.     'Non riscrivo PrintInfo, ma considero che stia
   04.     'ancora in questo modulo
   05.
   06.     Sub Main()
   07.         Dim P As New Person("Mario", "Rossi", New Date(1982, 3, 17))
   08.         Dim T As New Teacher("Luigi", "Bianchi", New Date(1879, 8, 21), "Storia")
   09.         Dim R As New Relation(Of Person, Teacher)(P, T)
   10.         Dim Q As New List(Of Int32)
   11.         Dim K As New Text.StringBuilder()
   12.         Dim Objects() As Object = {P, T, R, Q, K}
   13.         Dim Cmd As Int32
   14.
   15.         Console.WriteLine("Oggetti nella collezione: ")
   16.         For I As Int32 = 0 To Objects.Length - 1
   17.              Console.WriteLine("{0} - Istanza di {1}", _
   18.                  I, Objects(I).GetType().Name)
   19.         Next
   20.         Console.WriteLine("Inserire il numero corrispondente all'oggetto da modificare: ")
   21.         Cmd = Console.ReadLine
   22.
   23.         If Cmd < 0 Or Cmd > Objects.Length - 1 Then
   24.              Console.WriteLine("Nessun oggetto corrispondente!")
   25.              Exit Sub
   26.         End If
   27.
   28.         Dim Selected As Object = Objects(Cmd)
   29.         Dim SelectedType As Type = Selected.GetType()
   30.         Dim Properties As New List(Of PropertyInfo)
   31.
   32.         For Each Prop As PropertyInfo In SelectedType.GetProperties()
   33.              If (Prop.PropertyType.IsPrimitive Or Prop.PropertyType Is GetType(String)) And _
   34.                 Prop.CanWrite Then
   35.                  Properties.Add(Prop)
   36.              End If
   37.         Next
   38.
   39.         Console.Clear()
   40.         Console.WriteLine("Proprietà dell'oggetto:")
   41.         For I As Int32 = 0 To Properties.Count - 1
   42.              Console.WriteLine("{0} - {1}", _
   43.                  I, Properties(I).Name)
   44.         Next
   45.         Console.WriteLine("Inserire il numero corrispondente alla proprietà da modificare:")
   46.         Cmd = Console.ReadLine
   47.
   48.         If Cmd < 0 Or Cmd > Objects.Length - 1 Then
   49.              Console.WriteLine("Nessuna proprietà corrispondente!")
   50.              Exit Sub
   51.
End If
   52.
   53.         Dim SelectedProp As PropertyInfo = Properties(Cmd)
   54.         Dim NewValue As Object
   55.
   56.         Console.Clear()
   57.         Console.WriteLine("Nuovo valore: ")
   58.         NewValue = Console.ReadLine
   59.
   60.         'Imposta il nuovo valore della proprietà. Noterete che
   61.         'si ottiene un errore di cast con tutti i tipi che
   62.         'non siano String. Questo accade poiché viene
   63.         'eseguito un matching sul tipo degli argomenti: se essi
   64.         'sono diversi, indipendentemente dal fatto che possano
   65.         'essere convertiti l'uno nell'altro (al contrario di
   66.         'quanto dice il testo dell'errore), viene sollevata
   67.         'quell'eccezione. Per aggirare il problema, si
   68.         'dovrebbe eseguire un cast esplicito controllando prima
   69.         'il tipo della proprietà:
   70.         ' If SelectedProp.PropertyType Is GetType(Int32) Then
   71.         '    NewValue = CType(NewValue, Int32)
   72.         ' ElseIf SelectedProp. ...
   73.         'È il prezzo da pagare quando si lavora con
   74.         'uno strumento così generale come la Reflection.
   75.         '[Generalmente si conosce in anticipo il tipo]
   76.         SelectedProp.SetValue(Selected, NewValue, Nothing)
   77.
   78.         Console.WriteLine("Proprietà modificata!")
   79.
   80.         PrintInfo(Selected, "")
   81.
   82.         Console.ReadKey()
   83.     End Sub
   84. End Module




Chi ha letto anche la ver sione pr ecedente della guida, avr à notato che manca il codice per l'assembly br ow ser , ossia
quel pr ogr amma che elenca tutti i tipi (e tutti i membr i di ogni tipo) pr esenti in un assembly. Mi sembr ava tr oppo
noioso e labor ioso e tr oppo poco inter essante per r ipr opor lo anche qui, ma siete liber i di dar ci un'occhiata (al r elativo
capitolo della ver sione 2).
A47. La Reflection - Parte IV

Compilazione di c odic e a runtime
Bene, or a che sappiamo scr iver e del nor male codice per una qualsiasi applicazione e che sappiamo come analizzar e
codice ester no, che ne dite di scr iver e pr ogr ammi che pr oducano pr ogr ammi? La questione è molto diver tente:
esistono delle apposite classi, in .NET, che consentono di compilar e codice che viene pr odotto dur ante l'esecuzione
dal'applicazione stessa, gener ando così nuovi assembly per gli scopi più var i. Una volta mi sono ser vito in manier a
intensiva di questa capacità del .NET per scr iver e un installer : non solo esso cr eava altr i pr ogr ammi (autoestr aenti),
ma questi a lor o volta cr eavano altr i pr ogr ammi per estr ar r e le infor mazioni memor izzate negli autoestr aenti stessi:
pr ogr ammi che scr ivono pr ogr ammi che scr ivono pr ogr ammi! Ma or a vediamo più nel dettaglio cosa usar e nello
specifico per attivar e queste inter essanti funzionalità.
Pr ima di tutto, è necessar io impor tar e un paio di namespace: System.CodeDom e System.CodeDom.Compiler . Essi
contengono le classi che fanno al caso nostr o per il mestier e. Il pr ocesso di compilazione si svolge alltr aver so queste
fasi:

        Pr ima si ottiene il codice da compilar e, che può esser e memor izzato in un file o pr odotto dir ettamente dal
        pr ogr amma sottofor ma di nor male str inga;
        Si impostano i par ametr i di compilazione: ad esempio, si può sceglier e il tipo di output (*.ex e o *.dll), i
        r ifer imenti da includer e, se mantener e i file tempor anei, se cr ear e l'assembly e salvar lo in memor ia, se
        tr attar e gli w ar ning come er r or i, ecceter a... Insomma, tutto quello che noi scegliamo tr amite l'inter faccia
        dell'ambiente di sviluppo o che ci tr oviamo già impostato gr azie all'IDE stesso;
        Si compila il codice r ichiamando un pr ovider di compilazione;
        Si leggono i r isultati della compilazione. Nel caso ci siano stati er r or i, i r isultati conter r anno tutta la lista degli
        er r or i, con r elative infor mazioni sulla lor o posizione nel codice; in caso contr ar io, l'assembly ver r à gener ato
        cor r ettamente;
        Se l'assembly conteneva codice che ser ve al pr ogr amma, si usa la Reflection per ottener ne e invocar ne i metodi.

Queste cinque fasi cor r ispondono a cinque oggetti che dovr emo usar e nel codice:

        Str ing : ovviamente, il codice memor izzato sottofor ma di str inga;
        Compiler Par ameter s : classe del namespace CodeDom.Compiler . Contiene come pr opr ietà tutte le opzioni che ho
        esemplificato nella lista pr ecedente;
        VBCodePr ovider : pr ovider di compilazione per il linguaggio Visual Basic. Esiste un pr ovider per ogni linguaggio
        .NET, anche se può non tr ovar si sempr e nello stesso namespace. Esso for nir à i metodi per avviar e la
        compilazione;
        Compiler Results : contiene tutte le infor mazioni r elative all'output della compilazione. Se si sono ver ificati
        er r or i, ne espone una lista; se la compilazion è andata a buon file, r ifer isce dove si tr ova l'assembly compilato e,
        se ci sono, dove sono posti i file tempor anei;
        Assembly : classe che abbiamo già analizzato. Per mette di car icar e in memor ia l'assembly pr odotto come output
        e r ichiamar ne il codice od usar ne le classi ivi definite.

Il pr ossimo esempio costituisce uno dei casi più evidenti di quando conviene r ivolger si alla r eflection, e sono sicur o che
potr ebbe tor nar vi utile in futur o:

  001. Module Module1
  002.
  003.     'Questa classe rappresenta una funzione matematica:
  004.     'ne ho racchiuso il nome tra parentesi quadre poiché Function
  005.     'è una keyword del linguaggio, ma in questo caso la si
  006.
'vuole usare come identificatore. In generale, si possono usare
007.   'le parentesi quadre per trasformare ogni keyword in un normale
008.   'nome.
009.   Class [Function]
010.       Private _Expression As String
011.       Private EvaluateFunction As MethodInfo
012.
013.       'Contiene l'espressione matematica che costruisce il valore
014.       'della funzione in base alla variabile x
015.       Public Property Expression() As String
016.           Get
017.               Return _Expression
018.           End Get
019.           Set(ByVal value As String)
020.               _Expression = value
021.               Me.CreateEvaluator()
022.           End Set
023.       End Property
024.
025.       'La prossima è la procedura centrale di tutto l'esempio.
026.       'Il suo compito consiste nel compilare una libreria di
027.       'classi in cui è definita una funzione che, ricevuto
028.       'come parametro un x decimale, ne restituisce il valore
029.       'f(x). Il corpo di tale funzione varia in base
030.       'all'espressione immessa dall'utente.
031.       Private Sub CreateEvaluator()
032.           'Crea il codice della libreria: una sola classe
033.           'contenente la sola funzione statica Evaluate. Gli {0}
034.           'verranno sostituti con caratteri di "a capo", mentre
035.           'in {1} verrà posta l'espressione che produce
036.           'il valore desiderato, ad esempio "x ^ 2 + 1".
037.           Dim Code As String = String.Format( _
038.           "Imports Microsoft.VisualBasic{0}" & _
039.           "Imports System{0}" & _
040.           "Imports System.Math{0}" & _
041.           "Public Class Evaluator{0}" & _
042.           " Public Shared Function Evaluate(ByVal X As Double) As Double{0}" & _
043.           "    Return {1}{0}" & _
044.           " End Function{0}" & _
045.           "End Class", Environment.NewLine, Me.Expression)
046.
047.           'Crea un nuovo oggetto CompilerParameters, per
048.           'contenere le informazioni relative alla compilazione
049.           Dim Parameters As New CompilerParameters
050.
051.           With Parameters
052.               'Indica se creare un eseguibile o una libreria di
053.               'classi: in questo caso ci interessa la seconda,
054.               'quindi impostiamo la proprietà su False
055.               .GenerateExecutable = False
056.
057.               'Gli warning vengono considerati come errori
058.               .TreatWarningsAsErrors = True
059.               'Non vogliamo tenere alcun file temporaneo: ci
060.               'interessa solo l'assembly compilato
061.               .TempFiles.KeepFiles = False
062.               'L'assembly verrà tenuto in memoria temporanea
063.               .GenerateInMemory = True
064.
065.               'I due riferimenti di cui abbiamo bisogno, che si
066.               'trovano nella GAC (quindi basta specificarne il
067.               'nome). In questo caso, si richiede anche
068.               'l'estensione (*.dll)
069.               .ReferencedAssemblies.Add("Microsoft.VisualBasic.dll")
070.               .ReferencedAssemblies.Add("System.dll")
071.           End With
072.
073.           'Crea un nuovo provider di compilazione
074.           Dim Provider As New VBCodeProvider
075.           'E compila il codice seguendo i parametri di
076.           'compilazione forniti dall'oggetto Parameters. Il
077.           'valore restituito dalla funzione
078.
'CompileAssemblyFromSource è di tipo
079.              'CompilerResults e viene salvato in CompResults
080.              Dim CompResults As CompilerResults = _
081.                  Provider.CompileAssemblyFromSource(Parameters, Code)
082.
083.           'Se ci sono errori, lancia un'eccezione
084.           If CompResults.Errors.Count > 0 Then
085.                Throw New FormatException("Espressione non valida!")
086.           Else
087.                'Altrimenti crea un riferimento all'assembly di
088.                'output. La proprietà CompiledAssembly di
089.                'CompResults contiene un riferimento diretto a
090.                'quell'assembly, quindi ci è molto comoda.
091.                Dim Asm As Reflection.Assembly = CompResults.CompiledAssembly
092.                'Dall'assembly ottiene un OT che rappresenta
093.                'l'unico tipo ivi definito, e da questo ne
094.                'estrae un MethodInfo con informazioni sul
095.                'metodo Evaluate (l'unico presente).
096.                Me.EvaluateFunction = _
097.                    Asm.GetType("Evaluator").GetMethod("Evaluate")
098.           End If
099.       End Sub
100.
101.       'Per richiamare la funzione, basta invocare il metodo
102.       'Evaluate estratto precedentemente. Al pari delle
103.       'proprietà e dei campi, che possono essere letti o
104.       'scritti dinamicamente, anche i metodi possono essere
105.       'invocati dinamicamete attraverso MethodInfo. Si usa
106.       'la funzione Invoke: il primo parametro da
107.       'passare è l'oggetto da cui richiamare il metodo, mentre
108.       'il secondo è un array di oggetti che indicano i
109.       'parametri da passare a tale metodo.
110.       'In questo caso, il primo parametro è Nothing poiché
111.       'Evaluate è una funzione statica e non ha bisgno di nessuna
112.       'istanza per essere richiamata.
113.       Public Function Apply(ByVal X As Double) As Double
114.           Return EvaluateFunction.Invoke(Nothing, New Object() {X})
115.       End Function
116.
117.   End Class
118.
119.   Sub Main()
120.       Dim F As New [Function]()
121.
122.       Do
123.              Try
124.                  Console.Clear()
125.                  Console.WriteLine("Inserisci una funzione: ")
126.                  Console.Write("f(x) = ")
127.                  F.Expression = Console.ReadLine
128.                  Exit Do
129.              Catch ex As Exception
130.                  Console.WriteLine("Espressione non valida!")
131.                  Console.ReadKey()
132.              End Try
133.       Loop
134.
135.       Dim Input As String
136.       Dim X As Double
137.
138.       Do
139.              Try
140.                    Console.Clear()
141.                    Console.WriteLine("Immettere 'stop' per terminare.")
142.                    Console.WriteLine("Il programma calcola il valore di f in X: ")
143.                    Console.Write("x = ")
144.                    Input = Console.ReadLine
145.                    If Input <> "stop" Then
146.                         X = CType(Input, Double)
147.                         Console.WriteLine("f(x) = {0}", F.Apply(X))
148.                         Console.ReadKey()
149.                    Else
150.
Exit Do
  151.                  End If
  152.              Catch Ex As Exception
  153.                  Console.WriteLine(Ex.Message)
  154.                  Console.ReadKey()
  155.              End Try
  156.         Loop
  157.     End Sub
  158. End Module

In questo esempio ho utilizzato solo alcuni dei membr i esposti dalle classi sopr a menzionate. Di seguito elenco tutti
quelli più r ilevanti, che potr ebber o ser vir vi in futur o:

        Co m piler Par am eter s
        Compiler Options : contiene sottofor ma di str inghe dei par ametr i aggiuntivi da passar e al compilator e.
        Vedr emo solo più avanti di cosa si tr atta e di come possano gener almente esser e modificati dall'IDE nell'ambito
        del nostr o pr ogetto;
        EmbeddedResour ces : una lista di str inghe, ognuna delle quali indica il per cor so su disco di un file di r isor se da
        includer e nell'assembly compilato. Di questi file par ler ò nella sezione B;
        Gener ateEx cutable : deter mina se gener ar e un eseguibile o una libr er ia di classi;
        Gener ateInMemor y : deter mina se non salvar e l'assembly gener ato su un suppor to per manente (disco fisso o
        altr e memor ie non volatili);
        IncludeDebugInfor mations : deter mina se includer e nell'eseguibile anche le infor mazioni r elative al debug. Di
        solito questo non è molto utile per ché è possibile acceder e pr ima a queste infor mazioni tr amite l'IDE facendo il
        debug del codice stesso che compila altr o codice XD;
        MainClass : imposta il nome della classe pr incipale dell'assembly. Se si sta compilando una libr er ia di classi, questa
        pr opr ietà è inutile. Se, invece, si sta compilando un pr ogr amma, questa pr opr ietà indica il nome della classe
        dove è contenuta la pr ocedur a Main, il punto di ingr esso nell'applicazione. Gener almente il compilator e r iesce ad
        individuar e da solo tale classe, ma nel caso ci siano più classi contenenti un metodo Main bisogna specificar lo
        esplicitamente. Nel caso l'applicazione da compilar e sia di tipo w indow s for m, come vedr emo nella sezione B, la
        MainClass può anche indicar e la classe che r appr esenta la finestr a iniziale;
        OutputAssembly : imposta il per cor so dell'assembly da gener ar e. Nel caso questa pr opr ietà non venga impostata
        pr ima della compilazione, sar à il pr ovider di compilazione a pr eoccupar si di cr ear e un nome casuale per
        l'assembly e di salvar lo nella stessa car tella del nostr o pr ogr amma;
        Refer encedAssemblis : anche questa è una collezione di str inghe, e contiene il nome degli assemblies da includer e
        come r ier imeneto per il codice cor r ente. Dovete sempr e includer e almeno System.dll (quello più impor tante). Gli
        altr i assemblies pubblici sono facoltativi e var iano in funzione del compito da svolger e: se doveste usar e file
        x ml, impor ter ete anche System.Xml.dll, ad esempio;
        TempFiles : collezione che contiene i per cor si dei file tempor anei. Espone qualche pr opr ietà e metodo in più
        r ispetto a una nor male collezione;
        Tr eatWar ningsAsEr r or s : tr atta gli w ar ning come se fosser o er r or i. Questo impedisce di por tar e a ter mine la
        compilazione quando ci sono degli w ar ning;
        War ningLevel : livello da cui il compilator e inter r ompe la compilazione. La documentazione su questa pr opr ietà
        non è molto chiar a e non si capisce bene cosa intenda. È pr obabile che ogni w ar ning abbia un cer to livello di
        aller ta e questo valor e dovr ebbe comunicar e al compilator e di visualizzar e solo gli w ar ning con livello maggior e
        o uguale a quello specificato... solo ipotesi, tuttavia.



        Co m piler Results
        CompiledAssembly : r estituisce un oggetto Assembly in r ifer imento all'assembly compilato;
        Er r or s : collezione di oggetti di tipo Compiler Er r or . Ognuno di questi oggetti espone delle pr opr ietà utili a
identificar e il luogo ed il motivo dell'er r or e. Alcune sono: Line e Column (linea e colonna dell'er r or e), IsWar ning
       (se è un w ar ning o un er r or e), Er r or Number (numer o identificativo dell'er r or e), Er r or Tex t (testo dell'er r or e) e
       FileName (nome del file in cui si è ver ificato);
       NativeCompiler Retur nValue : r estituisce il valor e che a sua volta il compilator e ha r estituito al pr ogr amma una
       volta ter minata l'esecuzione. Vi r icor do, infatti, che compilator e, editor di codice e debugger sono tr e
       pr ogr ammi differ enti: l'ambiente di sviluppo integr ato for nisce un'inter faccia che sembr a unir li in un solo
       applicativo, ma r imangono sempr e entità distinti. Come tali, un pr ogr amma può r estituir e al suo chiamante un
       valor e, solitamente inter o: pr opr io come si compor ta una funzione;
       PathToAssembly : il per cor so su disco dell'assembly gener ato;
       TempFiles : i file tempor aneai r imasti.

Avr ete notato che anche VBCodePr ovider espone molti metodi, ma la maggior par te di questi ser vono per la
gener azione di codice. Questo meccanismo per mette di assemblar e una collezione di oggetti ognuno dei quali
r appr esenta un'istr uzione di codice, e poi di gener ar e codice per un qualsiasi linguaggio .NET. Sebbene sia un
funzionalità potente, non la tr atter ò in questa guida.




Generazione di programmi
Il pr ossimo sor gente è un esempio che, secondo me, sar ebbe stato MOLTO più fr uttuoso se usato in un'applicazione
w indow s for ms. Tuttavia siamo nella sezione A e qui si fa solo teor ia, per ciò, pur tr oppo, dovr ete sor bir vi questo
entusiasmante esempio come applicazione console.
Ammettiamo che un'impr esa abbia un softw ar e di gestione dei suoi mater iali, e che abbastanza spesso acquisti nuove
tipologie di pr odotti o semilavor ati. Gli addetti al magazzino dovr anno intr odur r e i dati dei nuovi oggetti, ma per far
ciò, è necessar io un pr ogr amma adatto per gestir e quel tipo di oggetti: in questo modo, ogni volta, ser ve un
pr ogr amma nuovo. L'esempio che segue è una possibile soluzione a questo pr oblema: il pr ogr amma pr incipale r ichiede
di immetter e infor mazioni su un nuovo tipo di dato e cr ea un pr ogr amma apposta per la gestione di quel tipo di dato
(notate che ho scr itto cinque volte pr ogr amma sulla stessa colonna XD).

 001. Module Module1
 002.
 003.     'Classe che rappresenta il nuovo tipo di dato, ed
 004.     'espone una funzione per scriverne il codice
 005.     Class TypeCreator
 006.         Private _Fields As Dictionary(Of String, String)
 007.         Private _Name As String
 008.
 009.         'Fields è un dizionario che contiene come
 010.         'chiavi i nomi delle proprietà da definire
 011.         'nel nuovo tipo e come valori il loro tipi
 012.         Public ReadOnly Property Fields() As Dictionary(Of String, String)
 013.              Get
 014.                  Return _Fields
 015.              End Get
 016.         End Property
 017.
 018.         'Nome del nuovo tipo
 019.         Public Property Name() As String
 020.              Get
 021.                  Return _Name
 022.              End Get
 023.              Set(ByVal value As String)
 024.                  _Name = value
 025.              End Set
 026.         End Property
 027.
 028.         Public Sub New()
 029.              _Fields = New Dictionary(Of String, String)
 030.         End Sub
 031.
 032.
'Genera il codice della proprietà
033.       Public Function GenerateCode() As String
034.           Dim Code As New Text.StringBuilder()
035.
036.           Code.AppendLine("Class " & Name)
037.           For Each Field As String In Me.Fields.Keys
038.                Code.AppendFormat("Private _{0} As {1}{2}", _
039.                    Field, Me.Fields(Field), Environment.NewLine)
040.                Code.AppendFormat( _
041.                "Public Property {0} As {1}{2}" & _
042.                " Get{2}" & _
043.                "    Return _{0}{2}" & _
044.                " End Get{2}" & _
045.                " Set(ByVal value As {1}){2}" & _
046.                "    _{0} = value{2}" & _
047.                " End Set{2}" & _
048.                "End Property{2}", _
049.                Field, Me.Fields(Field), Environment.NewLine)
050.           Next
051.           Code.AppendLine("End Class")
052.
053.           Return Code.ToString()
054.       End Function
055.
056.   End Class
057.
058.   'Classe statica contenente la funzione per scrivere
059.   'e generare il nuovo programma
060.   Class ProgramCreator
061.
062.       'Accetta come input il nuovo tipo di dato
063.       'da gestire. Restituisce in output il percorso
064.       'dell'eseguibile creato
065.       Public Shared Function CreateManagingProgram(ByVal T As TypeCreator) As String
066.           Dim Code As New Text.StringBuilder()
067.
068.           Code.AppendLine("Imports System")
069.           Code.AppendLine("Imports System.Collections.Generic")
070.           Code.AppendLine("Module Module1")
071.           Code.AppendLine(T.GenerateCode())
072.
073.           Code.AppendLine("Sub Main()")
074.           Code.AppendLine(" Dim Storage As New List(Of " & T.Name & ")")
075.           Code.AppendLine(" Dim Cmd As Char")
076.           Code.AppendLine(" Do")
077.           Code.AppendLine(" Console.Clear()")
078.           Code.AppendLine(" Console.WriteLine(""Inserimento di oggetti " & T.Name & """)")
079.           Code.AppendLine(" Console.WriteLine()")
080.           Code.AppendLine(" Console.Writeline(""Scegliere un'operazione: "")")
081.           Code.AppendLine(" Console.WriteLine("" i - inserimento;"")")
082.           Code.AppendLine(" Console.WriteLine("" e - elenca;"")")
083.           Code.AppendLine(" Console.WriteLine("" u - uscita."")")
084.           Code.AppendLine(" Cmd = Console.ReadKey().KeyChar")
085.           Code.AppendLine(" Console.Clear()")
086.           Code.AppendLine(" Select Case Cmd")
087.           Code.AppendLine("     Case ""i"" ")
088.           Code.AppendLine("       Dim O As New " & T.Name & "()")
089.           Code.AppendLine("       Console.WriteLine(""Inserire i dati: "")")
090.           'Legge ogni membro del nuovo tipo. Usa la CType
091.           'per essere sicuri che tutto venga interpretato nel
092.           'modo corretto.
093.           For Each Field As String In T.Fields.Keys
094.                Code.AppendFormat("Console.Write("" {0} = ""){1}", Field,
                       Environment.NewLine)
095.                Code.AppendFormat("O.{0} = CType(Console.ReadLine(), {1}){2}", Field,
                       T.Fields(Field), Environment.NewLine)
096.           Next
097.           Code.AppendLine("       Storage.Add(O)")
098.           Code.AppendLine("       Console.WriteLine(""Inserimento completato!"")")
099.           Code.AppendLine("     Case ""e"" ")
100.           Code.AppendLine("       For I As Int32 = 0 To Storage.Count - 1")
101.           Code.AppendLine("         Console.WriteLine(""{0:000} + "", I)")
102.
'Fa scrivere una linea per ogni proprietà
103.           'dell'oggetto, mostrandone il valore
104.           For Each Field As String In T.Fields.Keys
105.                Code.AppendFormat("Console.WriteLine(""    {0} = "" & Storage(I).
                       {0}.ToString()){1}", Field, Environment.NewLine)
106.           Next
107.           Code.AppendLine("       Next")
108.           Code.AppendLine("       Console.ReadKey()")
109.           Code.AppendLine(" End Select")
110.           Code.AppendLine(" Loop Until Cmd = ""u""")
111.
112.           Code.AppendLine("End Sub")
113.           Code.AppendLine("End Module")
114.
115.           Dim Parameters As New CompilerParameters
116.
117.           With Parameters
118.               .GenerateExecutable = True
119.               .TreatWarningsAsErrors = True
120.               .TempFiles.KeepFiles = False
121.               .GenerateInMemory = False
122.               .ReferencedAssemblies.Add("Microsoft.VisualBasic.dll")
123.               .ReferencedAssemblies.Add("System.dll")
124.           End With
125.
126.           Dim Provider As New VBCodeProvider
127.           Dim CompResults As CompilerResults = _
128.               Provider.CompileAssemblyFromSource(Parameters, Code.ToString())
129.
130.           If CompResults.Errors.Count = 0 Then
131.                Return CompResults.PathToAssembly
132.           Else
133.                For Each E As CompilerError In CompResults.Errors
134.                     Stop
135.                Next
136.                Return Nothing
137.           End If
138.       End Function
139.
140.   End Class
141.
142.   Sub Main()
143.       Dim NewType As New TypeCreator()
144.       Dim I As Int16
145.       Dim Field, FieldType As String
146.
147.       Console.WriteLine("Creazione di un tipo")
148.       Console.WriteLine()
149.
150.       Console.Write("Nome del tipo = ")
151.       NewType.Name = Console.ReadLine
152.
153.       Console.WriteLine("Inserisci il nome del campo e il suo tipo:")
154.
155.       I = 1
156.       Do
157.           Console.Write("Nome campo {0}: ", I)
158.           Field = Console.ReadLine
159.
160.           If String.IsNullOrEmpty(Field) Then
161.               Exit Do
162.           End If
163.
164.           Console.Write("Tipo campo {0}: ", I)
165.           FieldType = Console.ReadLine
166.
167.           'Dovrete immettere il nome completo e con
168.           'le maiuscole al posto giusto. Ad esempio:
169.           ' System.String
170.           'e non string o system.String o STring.
171.           If Type.GetType(FieldType) Is Nothing Then
172.               Console.WriteLine("Il tipo {0} non esiste!", FieldType)
173.
Console.ReadKey()
174.              Else
175.                   NewType.Fields.Add(Field, FieldType)
176.                   I += 1
177.              End If
178.         Loop
179.
180.         Dim Path As String = ProgramCreator.CreateManagingProgram(NewType)
181.
182.         If Not String.IsNullOrEmpty(Path) Then
183.              Console.WriteLine("Programma di gestione per il tipo {0} creato!", NewType.Name)
184.              Console.WriteLine("Avviarlo ora? y/n")
185.              If Console.ReadKey().KeyChar = "y" Then
186.                   Process.Start(Path)
187.              End If
188.         End If
189.     End Sub
190. End Module
A48. Gli Attributi

Cosa sono e a c osa servono
Gli attr ibuti sono una par ticolar e categor ia di oggetti che ha come unico scopo quello di for nir e infor mazioni su altr e
entità. Tutte le infor mazioni contenute in un attr ibuto vanno sotto il nome di m etadati. Attr aver so i metadati, il
pr ogr ammator e può definir e ulter ior i dettagli su un membr o o su un tipo e sul suo compor tamento in r elazione con le
altr e par ti del codice. Gli attr ibuti, quindi, non sono str umenti di "pr ogr ammazione attiva", poiché non fan n o nulla, ma
dicon o semplicemente qualcosa; si avvicinano, per cer ti ver si, alla pr ogr ammazione dichiar ativa. Inoltr e, essi non
possono esser e usati né r intr acciati dal codice in cui sono definiti: non si tr atta di var iabili, metodi, classi, str uttur e o
altr o; gli attr ibuti, semplicemente par lando, non "esistono" se non come par te invisibile di infor mazione attaccata a
qualche altr o membr o. Sono come dei par assiti: hanno senso solo se attr ibuiti, appunto, a qualcosa. Per questo motivo,
l'unico modo per utilizzar e le infor mazioni che essi si por tano dietr o consiste nella Reflection.
La sintassi con cui si asseg na un attr ibuto a un membr o (non "si dichiar a", né "si inizializza", ma "si assegna" a
qualcosa) è questa:

    1. <Attributo(Parametri)> [Entità]

Faccio subito un esempio. Il Visual Basic per mette di usar e ar r ay con indici a base maggior e di 0: questa è una sua
peculiar ità, che non si tr ova in tutti i linguaggi .NET. Le Common Language Specifications del Fr amew or k specificano
che un qualsiasi linguaggio, per esser e qualificato come .NET, deve dar e la possibilità di gestir e ar r ay a base 0. VB fa
questo, ma espone anche quella par ticolar ità di pr ima che gli der iva dal VB classico: questa potenzialità è detta n on
CLS-Complian t, ossia che non r ispetta le specifiche del Fr amew or k. Noi siamo liber i di usar la, ad esempio in una
libr er ia, ma dobbiamo avver tir e gli altr i che, qualor a usasser o un altr o linguaggio .NET, non potr ebber o
pr obabilmente usufr uir e di quella par te di codice. L'unico modo di far e ciò consiste nell'assegnar e un attr ibuto
CLSCompliant a quel membr o che non r ispetta le specifiche:

   01. <CLSCompliant(False)> _
   02. Function CreateArray(Of T)(ByVal IndexFrom As Int32, ByVal IndexTo As Int32) As Array
   03.     'Per creare un array a estremi variabili è necessario
   04.     'usare una funzione della classe Array, ed è altrettanto
   05.     'necessario dichiarare l'array come di tipo Array, anche se
   06.     'questo è solitamente sconsigliato. Per creare il
   07.     'suddetto oggetto, bisogna passare alla funzione tre
   08.     'parametri:
   09.     ' - il tipo degli oggetti che l'array contiene;
   10.     ' - un array contenente le lunghezze di ogni rango dell'array;
   11.     ' - un array contenente gli indici iniziali di ogni rango.
   12.     Return Array.CreateInstance(GetType(T), New Int32() {IndexTo - IndexFrom}, New Int32()
              {IndexTo})
   13. End Function
   14.
   15. Sub Main()
   16.     Dim CustomArray As Array = CreateArray(Of Int32)(3, 9)
   17.
   18.     CustomArray(3) = 1
   19.     '...
   20. End Sub

Or a, se un pr ogr ammator e usasse la libr er ia in cui è posto questo codice, pr obabilmente il compilator e pr odur r ebbe
un War ning aggiuntivo indicano esplicitamente che il metodo Cr eateAr r ay non è CLS-Compliant, e per ciò non è sicur o
usar lo.
Allo stesso modo, un altr o esempio potr ebbe esser e l'attr ibuto Obsolete. Specialmente quando si lavor a su gr andi
pr ogetti di cui esistono più ver sioni e lo sviluppo è in costante evoluzione, capita che vengano scr itte nuove ver sioni di
membr i o tipi già esistenti: quelle vecchie sar anno molto pr obabilmente mantenute per assicur ar e la compatibilità con
softw ar e datati, ma sar anno comunque mar cate con l'attr ibuto obsolete. Ad esempio, con questa r iga di codice potr ete
ottener e l'indir izzo IP del mio sito:

    1. Dim IP As Net.IPHostEntry = System.Net.Dns.Resolve("www.totem.altervista.org")

Tuttavia otter r ete un War ning che r ipor ta la seguente dicitur a: 'Public Shared Fun ction Res olve(hos tName As Strin g)
As Sys tem.Net.IPHos tEn try' is obs olete: Res olve is obs oleted for this type, pleas e us e GetHos tEn try in s tead.
http://go.micros oft.com/fwlin k/?lin kid=14202 . Questo è un esempio di un metodo, esistente dalla ver sione 1.1 del
Fr amew or k, che è stato r impiazzato e quindi dichiar ato obsoleto.
Un altr o attr ibuto molto inter essante è, ad esempio, Conditional, che per mette di eseguir e o tr alasciar e del codice a
seconda che sia definita una cer ta costante di compilazione. Queste costanti sono impostabili in una finestr a di
compilazione avanzata che vedr emo solo più avanti. Tuttavia, quando l'applicazione è in modalità debug, è di default
definita la costante DEBUG.

   01.    <Conditional("DEBUG")> _
   02.    Sub WriteStatus()
   03.        'Scriva a schermo la quantità di memoria usata,
   04.        'senza aspettare la prossima garbage collection
   05.        Console.WriteLine("Memory: {0}", GC.GetTotalMemory(False))
   06.    End Sub
   07.
   08.    'Crea un nuovo oggetto
   09.    Function CreateObject(Of T As New)() As T
   10.        Dim Result As New T
   11.        'Richiama WriteStatus: questa chiamata viene IGNORATA
   12.        'in qualsiasi caso, tranne quando siamo in debug.
   13.        WriteStatus()
   14.        Return Result
   15.    End Function
   16.
   17.    Sub Main()
   18.        Dim k As Text.StringBuilder = _
   19.             CreateObject(Of Text.StringBuilder)()
   20.        '...
   21.    End Sub

Usando Cr eateObject possiamo monitor ar e la quantità di memor ia allocata dal pr ogr amma, ma solo in modalità debug,
ossia quando la costante di compilazione DEBUG è definita.
Come avr ete notato, tutti gli esempi che ho fatto comunicavano infor mazioni dir ettamente al compilator e, ed infatti
una buona par te degli attr ibuti ser ve pr opr io per definir e compor tamente che non si potr ebber o indicar e in altr o
modo. Un attr ibuto può esser e usato, quindi, nelle manier e più var ie e intr odur r ò nuovi attr ibuti molto impor tanti
nelle pr ossime sezioni. Questo capitolo non ha lo scopo di mostr ar e il funzionamento di ogni attr ibuti esistente, ma di
insegnar e a cosa esso ser va e come agisca: ecco per chè nel pr ossimo par agr afo ci cimenter emo nella scr ittur a di un
nuovo attr ibuto.




Dic hiarare nuovi attributi
For malmente, un attr ibuto non è altr o che una classe der ivata da System.Attr ibute. Ci sono alcune convenzioni
r iguar do la scr ittur a di queste classi, per ò:

         Il nome della classe deve sempr e ter minar e con la par ola "Attr ibute";
         Gli unici membr i consentiti sono: campi, pr opr ietà e costr uttor i;
         Tutte le pr opr ietà che vengono impostate nei costr uttor i devono esser e ReadOnly, e vicever sa.

Il pr imo punto è solo una convenzione, ma gli altr i sono di utilità pr atica. Dato che lo scopo dell'attr ibuto è contener e
infor mazione, è ovvio che possa contener e solo pr opr ietà, poiché non spetta a lui usar ne il valor e. Ecco un esempio
semplice con un attr ibuto senza pr opr ietà:
001. 'In questo codice, cronometreremo dei metodi, per
002. 'vedere quale è il più veloce!
003. Module Module1
004.
005.     'Questo è un nuovo attributo completamente vuoto.
006.     'L'informazione che trasporta consiste nel fatto stesso
007.     'che esso sia applicato ad un membro.
008.     'Nel metodo di cronometraggio, rintracceremo e useremo
009.     'solo i metodi a cui sia stato assegnato questo attributo.
010.     Public Class TimeAttribute
011.         Inherits Attribute
012.
013.     End Class
014.
015.     'I prossimi quattro metodi sono procedure di test. Ognuna
016.     'esegue una certa operazione 100mila o 10 milioni di volte.
017.
018.     <Time()> _
019.     Sub AppendString()
020.         Dim S As String = ""
021.         For I As Int32 = 1 To 100000
022.              S &= "a"
023.         Next
024.         S = Nothing
025.     End Sub
026.
027.     <Time()> _
028.     Sub AppendBuilder()
029.         Dim S As New Text.StringBuilder()
030.         For I As Int32 = 1 To 100000
031.              S.Append("a")
032.         Next
033.         S = Nothing
034.     End Sub
035.
036.     <Time()> _
037.     Sub SumInt32()
038.         Dim S As Int32
039.         For I As Int32 = 1 To 10000000
040.              S += 1
041.         Next
042.     End Sub
043.
044.     <Time()> _
045.     Sub SumDouble()
046.         Dim S As Double
047.         For I As Int32 = 1 To 10000000
048.              S += 1.0
049.         Next
050.     End Sub
051.
052.     'Questa procedura analizza il tipo T e ne estrae tutti
053.     'i metodi statici e senza parametri marcati con l'attributo
054.     'Time, quindi li esegue e li cronometra, poi riporta
055.     'i risultati a schermo per ognuno.
056.     'Vogliamo che i metodi siano statici e senza parametri
057.     'per evitare di raccogliere tutte le informazioni per la
058.     'funzione Invoke.
059.     Sub ReportTiming(ByVal T As Type)
060.         Dim Methods() As MethodInfo = T.GetMethods()
061.         Dim TimeType As Type = GetType(TimeAttribute)
062.         Dim TimeMethods As New List(Of MethodInfo)
063.
064.         'La funzione GetCustomAttributes accetta due parametri
065.         'nel secondo overload: il primo è il tipo di
066.         'attributo da cercare, mentre il secondo specifica se
067.         'cercare tale attributo in tutto l'albero di
068.         'ereditarietà del membro. Restituisce come
069.         'risultato un array di oggetti contenenti gli attributi
070.         'del tipo voluto. Vedremo fra poco come utilizzare
071.         'questo array: per ora limitiamoci a vedere se non è
072.
'vuoto, ossia se il metodo è stato marcato con Time
 073.              For Each M As MethodInfo In Methods
 074.                   If M.GetCustomAttributes(TimeType, False).Length > 0 And _
 075.                      M.GetParameters().Count = 0 And _
 076.                      M.IsStatic Then
 077.                       TimeMethods.Add(M)
 078.                   End If
 079.              Next
 080.
 081.              Methods = Nothing
 082.
 083.              'La classe Stopwatch rappresenta un cronometro. Start
 084.              'per farlo partire, Stop per fermarlo e Reset per
 085.              'resettarlo a 0 secondi.
 086.              Dim Crono As New Stopwatch
 087.
 088.              For Each M As MethodInfo In TimeMethods
 089.                   Crono.Reset()
 090.                   Crono.Start()
 091.                   M.Invoke(Nothing, New Object() {})
 092.                   Crono.Stop()
 093.                   Console.WriteLine("Method: {0}", M.Name)
 094.                   Console.WriteLine(" Time: {0}ms", Crono.ElapsedMilliseconds)
 095.              Next
 096.
 097.             TimeMethods.Clear()
 098.             TimeMethods = Nothing
 099.         End Sub
 100.
 101.         Sub Main()
 102.             Dim This As Type = GetType(Module1)
 103.
 104.              'Non vi allarmate se il programma non stampa nulla
 105.              'per qualche secondo. Il primo metodo è molto
 106.              'lento XD
 107.              ReportTiming(This)
 108.
 109.             Console.ReadKey()
 110.         End Sub
 111. End     Module

Ecco i r isultati del benchmar king (ter mine tecnico) sul mio por tatile:

 Method:   AppendString
   Time:   4765ms
 Method:   AppendBuilder
   Time:   2ms
 Method:   SumInt32
   Time:   27ms
 Method:   SumDouble
   Time:   34ms


Come potete osser var e, concatenar e le str inghe con & è enor memente meno efficiente r ispetto all'Append della classe
Str ingBuilder . Ecco per chè, quando si hanno molti dati testuali da elabor ar e, consiglio sempr e di usar e il secondo
metodo. Per quando r iguar da i numer i, le pr estazioni sono comunque buone, se non che i Double occupano 32 bit in più
e ci vuole più tempo anche per elabor ar li. In questo esempio avete visto che gli attr ibuti possono esser e usati solo
attr aver so la Reflection. Pr ima di pr oceder e, bisogna dir e che esiste uno speciale attr ibuto applicabile solo agli
attr ibuti che definisce quali entità possano esser e mar cate con dato attr ibuto. Esso si chiama Attr ibuteUsage. Ad
esempio, nel codice pr ecedente, Time è stato scr itto con l'intento di mar car e tutti i metodi che sar ebber o stati
sottoposti a benchmar king, ossia è "nato" per esser e applicato solo a metodi, e non a classi, enumer ator i, var iabili o
altr o. Tuttavia, per come l'abbiamo dichiar ato, un pr ogr ammator e può applicar lo a qualsiasi cosa. Per r estr inger e il
suo campo d'azione si dovr ebbe modificar e il sor gente come segue:

    1. <AttributeUsage(AttributeTargets.Method)> _
    2.
Public Class TimeAttribute
    3.     Inherits Attribute
    4.
    5. End Class

Attr ibuteTar gets è un enumer ator e codificato a bit.
Ma veniamo or a agli attr ibuti con par ametr i:

 001. Module Module1
 002.
 003.     'UserInputAttribute specifica se una certa proprietà
 004.     'debba essere valorizzata dall'utente e se sia
 005.     'obbligatoria o meno. Il costruttore impone un solo
 006.     'argomento, IsUserScope, che deve essere per forza
 007.     'specificato, altrimenti non sarebbe neanche valsa la
 008.     'pena di usare l'attributo, dato che questa è la
 009.     'sua unica funzione.
 010.     'Come specificato dalle convenzioni, la proprietà
 011.     'impostata nel costruttore è ReadOnly, mentre
 012.     'le altre (l'altra) è normale.
 013.     <AttributeUsage(AttributeTargets.Property)> _
 014.     Class UserInputAttribute
 015.         Inherits Attribute
 016.
 017.         Private _IsUserScope As Boolean
 018.         Private _IsCompulsory As Boolean = False
 019.
 020.         Public ReadOnly Property IsUserScope() As Boolean
 021.              Get
 022.                  Return _IsUserScope
 023.              End Get
 024.         End Property
 025.
 026.         Public Property IsCompulsory() As Boolean
 027.              Get
 028.                  Return _IsCompulsory
 029.              End Get
 030.              Set(ByVal value As Boolean)
 031.                  _IsCompulsory = value
 032.              End Set
 033.         End Property
 034.
 035.         Sub New(ByVal IsUserScope As Boolean)
 036.              _IsUserScope = IsUserScope
 037.         End Sub
 038.
 039.     End Class
 040.
 041.     'Cubo
 042.     Class Cube
 043.         Private _SideLength As Single
 044.         Private _Density As Single
 045.         Private _Cost As Single
 046.
 047.         'Se i parametri del costruttore vanno specificati
 048.         'tra parentesi quando si assegna l'attributo, allora
 049.         'come si fa a impostare le altre proprietà
 050.         'facoltative? Si usa un particolare operatore di
 051.         'assegnamento ":=" e si impostano esplicitamente
 052.         'i valori delle proprietà ad uno ad uno,
 053.         'separati da virgole, ma sempre nelle parentesi.
 054.         <UserInput(True, IsCompulsory:=True)> _
 055.         Public Property SideLength() As Single
 056.              Get
 057.                  Return _SideLength
 058.              End Get
 059.              Set(ByVal value As Single)
 060.                  _SideLength = value
 061.              End Set
 062.         End Property
 063.
 064.
<UserInput(True)> _
065.       Public Property Density() As Single
066.           Get
067.               Return _Density
068.           End Get
069.           Set(ByVal value As Single)
070.               _Density = value
071.           End Set
072.       End Property
073.
074.       'Cost non verrà chiesto all'utente
075.       <UserInput(False)> _
076.       Public Property Cost() As Single
077.           Get
078.               Return _Cost
079.           End Get
080.           Set(ByVal value As Single)
081.               _Cost = value
082.           End Set
083.       End Property
084.
085.   End Class
086.
087.   'Crea un oggetto di tipo T richiendendo all'utente di
088.   'impostare le proprietà marcate con UserInput
089.   'in cui IsUserScope è True.
090.   Function GetInfo(ByVal T As Type) As Object
091.       Dim O As Object = T.Assembly.CreateInstance(T.FullName)
092.
093.       For Each PI As PropertyInfo In T.GetProperties()
094.           If Not PI.CanWrite Then
095.               Continue For
096.           End If
097.
098.           Dim Attributes As Object() = PI.GetCustomAttributes(GetType(UserInputAttribute),
                  True)
099.
100.           If Attributes.Count = 0 Then
101.               Continue For
102.           End If
103.
104.           'Ottiene il primo (e l'unico) elemento dell'array,
105.           'un oggetto di tipo UserInputAttribute che rappresenta
106.           'l'attributo assegnato e contiene tutte le informazioni
107.           'passate, sottoforma di proprietà.
108.           Dim Attr As UserInputAttribute = Attributs(0)
109.
110.           'Se la proprietà non è richiesta all'utente,
111.           'allora continua il ciclo
112.           If Not Attr.IsUserScope Then
113.               Continue For
114.           End If
115.
116.           Dim Value As Object = Nothing
117.           'Se è obbligatoria, continua a richiederla
118.           'fino a che l'utente non immette un valore corretto.
119.           If Attr.IsCompulsory Then
120.                Do
121.                    Try
122.                        Console.Write("* {0} = ", PI.Name)
123.                        Value = Convert.ChangeType(Console.ReadLine, PI.PropertyType)
124.                    Catch Ex As Exception
125.                        Value = Nothing
126.                        Console.WriteLine(Ex.Message)
127.                    End Try
128.                Loop Until Value IsNot Nothing
129.           Else
130.                'Altrimenti la richiede una sola volta
131.                Try
132.                    Console.Write("{0} = ", PI.Name)
133.                    Value = Convert.ChangeType(Console.ReadLine, PI.PropertyType)
134.                Catch Ex As Exception
135.
Value = Nothing
 136.                        End Try
 137.                    End If
 138.                    If Value IsNot Nothing Then
 139.                        PI.SetValue(O, Value, Nothing)
 140.                    End If
 141.             Next
 142.
 143.            Return O
 144.        End Function
 145.
 146.        Sub Main()
 147.            Dim O As Object
 148.
 149.             Console.WriteLine("Riempire i campi (* = obbligatorio):")
 150.
 151.             O = GetInfo(GetType(Cube))
 152.
 153.             'Stampa i valori con il metodo PrintInfo scritto qualche
 154.             'capitolo fa
 155.             PrintInfo(O, "")
 156.
 157.            Console.ReadKey()
 158.        End Sub
 159. End    Module

Vi lascio immaginar e cosa faccia il metodo Conver t.ChangeType...
A49. Modificare le opzioni di compilazione

Esistono più modi di influir e sulla compilazione di un sor gente e di modificar e il compor tamento del compilator e ver so
le sue par ti. Alcuni di questi "modi" consistono nel modificar e le opzioni di compilazione, che si suddividuono in quattr o
voci pr incipali: Ex plicit, Compar e, Infer e Str ict. Per attivar e o disattivar e ognuna di esse, è possibile usar e delle
dir ettive speciali poste in testa al codice o acceder e alla finestr a dell'ambiente di sviluppo r elativa alla compilazione.
Nel secondo caso, baster à che clicchiate, dal menù pr incipale, Pr oject > [NomePr getto] Pr oper ties; dalle pr opr ietà,
scegliete la seconda scheda cliccando sull'etichetta Compile sulla sinistr a:




Da questo pannelo potr ete anche decider e il compor tamento da adottar e ver so cer te cir costanze di codice, ossia se
tr attar le come w ar ning, er r or i, o se non segnalar le neppur e.




Option Explic it
Quando Ex plicit è attiva, tutte le var iabili devono esser e esplicitamente dichiar ate pr ima del lor o uso: d'altr a par te,
questa è sempr e stata la pr assi che abbiamo adottato fin dall'inizio del cor so e non ci sono par ticolar i motivi per
combiar la. Quando l'opzione è disattivata, ogni nome sconosciuto ver r à tr attato come una nuova var iabile e cr eato al
momento. Ecco un esempio in cui disattivo Ex plicit da codice:

   01. Option Explicit Off
   02.
   03. Module Module1
   04.     Sub Main()
   05.         'La variabile Stringa non viene dichiarata, ma è
   06.         'lecito usarla e non viene comunicato alcun errore
   07.         Stringa = "Ciao"
   08.
   09.         'Stessa cosa per la variabile I, che non è stata
   10.         'dichiarata da nessuna parte
   11.         For I = 1 To 20
   12.              Console.WriteLine(Stringa)
   13.         Next
   14.
   15.
Console.ReadKey()
   16.     End Sub
   17. End Module

Le dir ettive per l'attivazione/disattivazione di un'opzione di compilazione devono tr ovar si sempr e in cima al sor gente,
anche pr ima di ogni altr a dir ettiva Impor ts, e hanno una sintassi pr essoché costante:

    1. Option [Nome] On/Off

Anche se è possibile disattivar e Ex plicit, è fortemen te s con s igliato far lo: può pr odur r e molti più danni che benefici. È
pur ver o che si usa meno codice, ma questo diventa anche meno compr ensibile, e gli er r or i di battitur a possono
condannar vi a settimane di insonnia. Ad esempio:

   01. Option Explicit Off
   02.
   03. Module Module1
   04.     Sub Main()
   05.         Stringa = "Ciao"
   06.
   07.         For I = 1 To 20
   08.              If I > 10 Then
   09.                  Strnga = I
   10.              End If
   11.              Console.WriteLine(Stringa)
   12.         Next
   13.
   14.         Console.ReadKey()
   15.     End Sub
   16. End Module

Il codice dovr ebbe, nelle vostr e intenzioni, scr iver e "Ciao" solo 10 volte, e poi stampar e 11, 12, 13, ecceter a... Tuttavia
questo non succede, per chè avete dimenticato una "i" e il compilator e non vi segnala nessun er r or e a causa della
dir ettiva Option; non r icever ete neppur e un w ar ning del tipo "Unused local var iable", per chè la r iga in cui è pr esente
Str nga consiste in un assegnamento. Er r or i stupidi possono causar e gr andi per dite di tempo.




Option Compare
Questa opzione non assume i valor i On/Off, ma Binar y e Tex t. Quando Compar e è impostata su Binar y, la
compar azione tr a due str inghe viene effettuata confr ontando il valor e dei singoli bytes che la compongono e, per ciò, il
confr onto diventa cas e-s en s itive (maiuscole e minuscole della stessa letter a sono consider ate differ enti). Se, al
contr ar io, è impostata su Tex t, viene compar ato solo il testo che le str inghe contengono, senza far e distinzioni su
letter e maiuscole o minuscole (cas e-in s en s itive). Eccone un esempio:

   01. Option Compare Text
   02. Module Module1
   03.   Sub Main()
   04.     If "CIAO" = "ciao" Then
   05.       Console.WriteLine("Option Compare Text")
   06.     Else
   07.       Console.WriteLine("Option Compare Binary")
   08.     End If
   09.     Console.ReadKey()
   10.   End Sub
   11. End Module

Scr ivendo "Binar y" al posto di "Tex t", otter r emo un messaggio differ ente a r untime!




Option Stric t
Questa opzione si occupa di r egolar e le conver sioni implicite tr a tipi di dato differ enti. Quando è attiva, tutti i cast
impliciti vengono segnalati e consider ati come er r or i: non si può passar e, ad esempio, da Double a Integer o da una
classe base a una der ivata:

   01. Option Strict On
   02. Module Module1
   03.     Sub Main()
   04.         Dim I As Int32
   05.         Dim P As Student
   06.
   07.         'Conversione implicita da Double a Int32: viene
   08.         'segnalata come errore
   09.         I = 4.0
   10.         'Conversione implicita da Person a Student: viene
   11.         'segnalata come errore
   12.         P = New Person("Mario", "Rossi", New Date(1968, 9, 12))
   13.
   14.         Console.ReadKey()
   15.     End Sub
   16. End Module

Per evitar e di r icever e le segnalazioni di er r or e, bisogna utilizzar e un oper ator e di cast esplicito come CType.
Gener almente, Str ict viene mantenuta su Off, ma se siete par ticolar mente r igor osi e volete evitar e le conver sioni
implicite, siete liber i di attivar la.




Option Infer
Questa opzione di compilazione è stata intr odotta con la ver sione 2008 del linguaggio e, se attivata, per mette di
infer ir e il tipo di una var iabile senza tipo (ossia senza clausola As) analizzando i valor i che le vengono passati. All'inizio
della guida, ho detto che una var iabile dichiar ata solo con Dim (ad esempio "Dim I") viene consider ata di tipo Object:
questo è ver o dalla ver sione 2005 in giù, e nella ver sioni 2008 e successive solo se Option Infer è disattivata. Ecco un
esempio:

   01. Option Infer Off
   02.
   03. Module Module1
   04.     Sub Main()
   05.         'Infer è disattivata: I viene considerata di
   06.         'tipo Object
   07.         Dim I = 2
   08.
   09.         'Dato che I è Object, può contenere
   10.         'qualsiasi cosa, e quindi questo codice non genera
   11.         'alcun errore
   12.         I = "ciao"
   13.
   14.     End Sub
   15. End Module

Pr ovando ad impostar e Infer su On, non otter r ete nessuna segnalazione dur ante la scr ittur a, ma appena il pr ogr amma
sar à avviato, ver r à lanciata un'eccezione di cast, poiché il tipo di I viene dedotto dal valor e assegnatole (2) e la fa
diventar e, da quel momento in poi, una var iabile Integer a tutti gli effetti, e "ciao" non è conver tibile in inter o.
A50. Comprendere e implementare un algoritmo

For se sar ebbe stato oppor tuno tr attar e questo ar gomento moooolto pr ima nella guida piuttosto che alla fine della
sezione; non per niente, la compr ensione degli algor itmi è la pr ima cosa che viene r ichiesta in un qualsiasi cor so di
infor matica. Tuttavia, è pur ver o che, per r isolver e un pr oblema, bisogna dispor r e degli str umenti giusti e,
pr efer ibilmente, conoscer e tutti gli str umenti a pr opr ia disposizione. Ecco per chè, pr ima di giunger e a questo punto,
ho voluto spiegar e tutti i concetti di base e la sintassi del linguaggio, poiché questi sono solo str umenti nelle mani del
pr ogr ammator e, la per sona che deve occupar si della stesur a del codice.
Iniziamo col dar e una classica definizione di algor itmo, mutuata da Wikipedia:


  In s ieme di is truzion i elemen tari un ivocamen te in terpretabili che, es eguite in un ordin e s tabilito, permetton o la
                                    s oluzion e di un problema in un n umero fin ito di pas s i.


Vor r ei innanzitutto por r e l'attenzione sulle pr ime par ole: "insieme di istr uzioni elementar i" (io aggiunger e "insieme
finito", per esser e r igor osi, ma mi sembr a abbastanza difficile for nir e un insieme infinito di istr uzioni :P).
Sottolineiamo elem entar i: anche se i linguaggi .NET si tr ovano ad un livello molto distante dal pr ocessor e, che è in
gr ado di eseguir e un numer o molto limitato di istr uzioni - fr a cui And, Or , Not, +, Shift, lettur a e scr ittur a - la gamma
di "comandi" disponibile è altr ettanto esigua. Dopotutto, cosa possiamo scr iver e che sia una ver a e pr opr ia istr uzione?
Assegnamenti, oper azioni matematiche e ver ifia di condizioni. Punto. I cicli? Non fanno altr o che r ipeter e istr uzioni. I
metodi? Non fanno altr o che r ichiamar e altr o codice in cui si cono istr uzioni elementar i. Dobbiamo impar ar e, quindi, a
far e il meglio possibile con ciò ci cui disponiamo. Vi sar à utile, a questo pr oposito, disegnar e dei diagr ammi di flusso
per i vostr i algor itmi.
Inoltr e, r equsitio essenziale, è che ogni istr uzione sia unvicamente inter pr etabile, ossia che non possa esser ci
ambiguità con qualsiasi altr a istr uzione. Dopotutto, questa condizione viene soddisfatta dall'elabor ator e stesso, per
come è costr uito. Altr o aspetto impor tante è l'or dine: eseguir e pr ima A e poi B o vicever sa è differ ente; molto spesso
questa inver sione può causar e er r or i anche gr avi. Infine, è necessar io che l'algor itmo r estituisca un r isultato in un
numer o finito di passi: e questo è abbastanza ovvio, ma se ne per de di vista l'impor tanza, ad esempio, nelle funzioni
r icor sive.
In gener e, il pr oblema più gr ande di un pr ogr ammator e che deve scr iver e un algor itmo è r ender si conto di come
l'uomo pensa. Ad esempio: dovete scr iver e un pr ogr amma che contr olla se due str inghe sono l'una l'anagr amma
dell'altr a. A occhio è facile pensar e a come far e: basta qualche tentativo per veder e se r iusciamo a for mar e la pr ima
con le letter e della seconda o vicever sa, ma, domanda classica, "come si tr aduce in codice"? Ossia, dobbiamo domandar ci
come abbiamo fatto a giunger e alla conclusione, scomponendo le istr uzioni che il nostr o cer vello ha eseguito. Infatti,
quasi sempr e, per istinto o abitudine, siamo por tati ad eseguir e molti passi logici alla volta, senza r ender cene conto:
questo il computer non è in gr ado di far lo, e per istr uir lo a dover e dobbiamo abbassar ci al suo livello. Ecco una
semplice lista di punti in cui espongo come passer ei dal "linguaggio umano" al "linguaggio macchina":

        Definizione di anagr amma da Wikipedia: "Un anagr amma è il r isultato della per mutazione delle letter e di una o
        più par ole compiuta in modo tale da cr ear e altr e par ole o eventualmente fr asi di senso compiuto." ;
        Bisogna contr ollar e se, spostando le letter e, è possibile for mar e l'altr a par ola;
        Ma questo è possibile solo se ogni letter a compar e lo stesso numer o di volte in entr ambe le par ole;
        Per ver ificar e quest'ultimo dato è necessar io contar e ogni letter a di entr ambe le par ole, e quindi contr ollar e
        che tutte abbiano lo stesso numer o di occor r enze;
        Ser ve per pr ima cosa memor izzar e i dati: due var iabili Str ing per le str inghe. Per gli altr i dati, bisogna
        associar e ad una letter a (Char ) un numer o (Int32), quindi una chiave ad un valor e: il tipo di dato ideale è un
Dictionar y(Of Char , Int32). Si possono usar e due dizionar i o uno solo a seconda di cosa vi viene meglio (per
       semplicità ne user ò due);
       Ovviamente, a pr ior i, se le str inghe hanno lunghezza diver sa, sicur amente non sono anagr ammi;
       Or a è necessar io analizzar e le str inghe. Per scor r er e una str inga, basta ser vir si di un ciclo For e per ottener e
       un dato car atter e a una data posizione, la pr opr ietà (di default) Char s;
       In questo ciclo, se il dizionar io contiene il car atter e, allor a questo è già stato tr ovato almeno una volta, quindi
       se ne pr ende il valor e e lo si incr ementa di uno; in caso contr ar io, si aggiunge una nuova chiave con il valor e 1;
       Una volta ottenuti i due dizionar i, per pr ima cosa, devono aver e lo stesso numer o di chiavi, altr imenti una
       str inga avr ebbe dei car atter i che non compaiono nell'altr a; poi bisogna veder e se ogni chiave del pr imo
       dizionar io esiste anche nel secondo, ed infine se il valor e ad essa associato è lo stesso. Se una sola di queste
       condizioni è falsa, allor a le str inghe NON sono anagr ammi.

Pr ima di veder e il codice, notate che è più semplice ver ificar e quando NON succede qualcosa, poiché basta un solo
(contr o)esempio per confutar e una teor ia, ma ne occor r ono infiniti per dimostr ar la:

   01. Module Module1
   02.
   03.     Sub Main()
   04.         Dim S1, S2 As String
   05.         Dim C1, C2 As Dictionary(Of Char, Int32)
   06.         Dim Result As Boolean = True
   07.
   08.         Console.Write("Stringa 1: ")
   09.         S1 = Console.ReadLine
   10.         Console.Write("Stringa 2: ")
   11.         S2 = Console.ReadLine
   12.
   13.         If S1.Length = S2.Length Then
   14.              C1 = New Dictionary(Of Char, Int32)
   15.              For I As Int16 = 0 To S1.Length - 1
   16.                   If C1.ContainsKey(S1.Chars(I)) Then
   17.                        C1(S1.Chars(I)) += 1
   18.                   Else
   19.                        C1.Add(S1.Chars(I), 1)
   20.                   End If
   21.              Next
   22.
   23.              C2 = New Dictionary(Of Char, Int32)
   24.              For I As Int16 = 0 To S2.Length - 1
   25.                   If C2.ContainsKey(S2.Chars(I)) Then
   26.                        C2(S2.Chars(I)) += 1
   27.                   Else
   28.                        C2.Add(S2.Chars(I), 1)
   29.                   End If
   30.              Next
   31.
   32.              If C1.Keys.Count = C2.Keys.Count Then
   33.                   For Each C As Char In C1.Keys
   34.                        If Not C2.ContainsKey(C) Then
   35.                            Result = False
   36.                        ElseIf C1(C) <> C2(C) Then
   37.                            Result = False
   38.                        End If
   39.                        If Not Result Then
   40.                            Exit For
   41.                        End If
   42.                   Next
   43.              Else
   44.                   Result = False
   45.              End If
   46.         Else
   47.              Result = False
   48.         End If
   49.
   50.         If Result Then
   51.              Console.WriteLine("Sono anagrammi!")
   52.
Else
53.              Console.WriteLine("Non sono anagrammi!")
54.         End If
55.
56.         Console.ReadKey()
57.     End Sub
58. End Module
A51. Il miglior codice

Il fine giustifica i mezzi... beh non sempr e. In questo caso mi sto r ifer endo allo stile in cui il codice sor gente viene
scr itto: infatti, si può ottener e un r isultato che all'occhio dell'utente del pr ogr amma sembr a buono, se non ottimo, ma
che visto da un pr ogr ammator e osser vando il codice non è per niente affidabile. Quando si pubblicano i pr opr i
pr ogr ammi open sour ce, con sor genti annessi, si dovr ebbe far e par ticolar e attenzione anche a come si scr ive,
per mettendo agli altr i pr ogr ammator i di usufr uir e del pr opr io codice in manier a veloce e intuitiva. In questo modo ne
tr ar r anno vantaggio non solo gli altr i, ma anche voi stessi, che potr este tr ovar vi a r iveder e uno stesso sor gente
molto tempo dopo la sua stesur a e non r icor dar vi più niente. A tal pr oposito, elencher ò or a alcune buone nor me da
seguir e per miglior ar e il pr opr io stile.


Commentare
È buona nor ma commentar e il sor gente nelle sue var ie fasi, per spiegar ne il funzionamento o anche solo lo scopo.
Mentr e il commento può esser e tr alasciato per oper azione str aor dinar iamente lampanti e semplici, dovr ebbe
diventar e una r egola quando scr ivete pr ocedur e funzioni o anche solo pezzi di codice più complessi o cr eati da voi ex
novo (il che li r ende sconosciuti agli occhi altr ui). Vi faccio un piccolo esempio:

     1. X = Math.Sqrt((1 - (Y ^ 2 / B ^ 2)) * A ^ 2)

Questo potr ebbe esser e qualsiasi cosa: non c'è alcuna indicazione di cosa le letter e r appr esentino, nè del per chè venga
effettuata pr opr io quell'oper azione. Ripr oviamo in questo modo, con il sor gente commentato, e vediamo se capite a
cosa la for mula si r ifer isca:

   01.    'Data l'equazione di un'ellisse:
   02.    'x^2   y^2.
   03.    '--- + --- = 1
   04.    'a^2   b^2
   05.    'Ricava x:
   06.    'x^2 / a^2 = 1 - (y^2 / b^2)
   07.    'x^2 = (1 - (y^2 / b^2)) * a^2
   08.    'x = sqrt((1 - (y^2 / b^2)) * a^2)
   09.    'Prende la soluzione positiva:
   10.    X = Math.Sqrt((1 - (Y ^ 2 / B ^ 2)) * A ^ 2)

Così è molto meglio: possiamo capir e sia lo scopo della for mula sia il pr ocedimento logico per mezzo del quale ci si è
ar r ivati.




Dare un nome
Quando si cr eano nuovi contr olli all'inter no della w indow s for m, i lor o nomi vengono gener ati automaticamente
tr amite un indice, pr eceduto dal nome della classe a cui il contr ollo appar tiene, come, ad esempio, Button1 o
TabContr ol2. Se per applicazioni veloci, che devono svolger e pochissime, semplici oper azioni e per le quali basta una
finestr a anche piccola, non c'è pr oblema a lasciar e i contr olli innominati in questo modo, quasi sempr e è utile, anzi,
molto utile, r inominar li in modo che il lor o scopo sia compr ensibile anche da codice e che il lor o nome sia molto più
facile da r icor dar e. Tr oppe volte vedo nei sor genti dei Tex tBox 3, Button34, ToolStr ipItem7 che non si sa cosa siano. A
mio par er e, invece, è necessar io adottar e uno stile ben pr eciso anche per i nomi. Dal canto mio, utilizzo sempr e come
nome una str inga composta per i pr imi tr e car atter i dalla sigla del tipo di contr ollo (ad esempio cmd o btn per i
button, lst per le liste, cmb per le combobox , tx t per le tex tbox e così via) e per i r imanenti da par ole che ne
descr ivano la funzione. Esemplificando, un pulsante che debba cr ear e un nuovo file si chiamer à btnNew File, una lista
che debba contener e degli indir izzi di posta sar à lstEmail: quest'ultima notazione è detta "notazione ungher ese". A tal
pr oposito, vi voglio sugger ir e quest'ar tico lo e vi invito caldam ente a legger lo.




V ariabili
E se il nome dei contr olli deve esser e accur ato, lo deve esser e anche quello delle var iabili. Pr ogr ammar e non è far e
algebr a, non si deve cr eder e di poter usar e solo a, c, x , m, ki ecceter a. Le var iabili dovr ebber o aver e dei nomi
significativi. Bisogna utilizzar e le stesse nor me sopr a descr itte, anche se a mio par er e il pr efisso per i tipi (obj è
object, sng single, int integer ...) si può anche tr alasciar e.




Risparmiare memoria
Rispar miar e memor ia r ender à anche le oper azioni più semplici per il computer . Spesso è bene valutar e quale sia il tipo
più adatto da utilizzar e, se Integer , Byte, Double, Object o altr i. Per ciò bisogna anche pr evedr e quali sar anno i casi che
si potr ano ver ificar e. Se steste costr uendo un pr ogr amma che r iguar di la fisica, dovr este usar e numer i in vir gola
mobile, ma quali? Più è alta la pr ecisioe da utilizzar e, più vi ser vir à spazio: se dovete r isolver e pr oblemi da liceo
user ete il tipo Decimal (28 decimali, estensione da -7,9e+28 a 7,9e+28), o al massimo Single (38 decimali, estensione da
-3,4e+38 a +3,4e+38), ma se state facendo calcoli specialistici ad esempio per un acceler ator e di par ticelle
(megalomani!) avr este bisogno di tutta la potenza di calcolo necessar ia e user este quindi Double (324 decimali,
estensione da -1,7e308 a +1,7e+308). Ricor datevi anche dell'esistenza dei tipi Unsigned, che vi per mettono di ottener e
un'estensione di numer i doppia sopr a lo zer o, così UInt16 avr à tanti numer i positivi quanti Int32.
Ricor date, inoltr e, di distr ugger e sempr e gli oggetti che utilizzate dopo il lor o ciclo di vita e di r ichiamar e il
r ispettivo distr uttor e se ne hanno uno (Dispose).




Il rasoio di Oc c am
La soluzione più semplice è quella esatta. E per questo mi r ifer isco allo scr iver e anche in ter mini di lunghezza di
codice. È inutile scr iver e funzioni lunghissime quando è possibile eguagliar le con pochissime r ighe di codice, più
compatto, incisivo ed efficace.

   01.   Public Function Fattoriale(ByVal X as byte) As UInt64
   02.     If X = 1 Then
   03.       Return 1
   04.     Else
   05.       Dim T As UInt64 = 1
   06.       For I As Byte = 1 To X
   07.          T *= I
   08.       Next
   09.       Return T
   10.     End If
   11.   End Function
   12.
   13.   'Diventa:
   14.
   15.   Public Function Fattoriale(ByVal X as byte) As UInt64
   16.     If X = 1 Then
   17.       Return 1
   18.     Else
   19.       Return X * Fattoriale(X - 1)
   20.     End If
   21.   End Function




   01. Sub Copia(ByVal Da As String, ByVal A As String)
   02.   Dim R As <font class="keyword">New</font> IO.SreamReader(Da)
   03.   Dim W As <font class="keyword">New</font> IO.StreamWriter(A)
   04.
05.       W.Write(R.ReadToEnd)
   06.
   07.      R.Close()
   08.      W.Close()
   09.    End Sub
   10.
   11.    'Diventa
   12.
   13.    IO.File.Copy(Da, A)
   14.    'Spesso anche il non conoscere tutte le possibilità
   15.    'si trasforma in uno spreco di tempo e spazio




Inc apsulamento
L'incapsulamento è uno dei tr e fondamentali del par adigma di pr ogr ammazione ad Oggetti (gli altr i due sono
polimor fismo ed er editar ietà, che abbiamo già tr attato). Come r icor der ete, all'inizio del cor so, ho scr itto che il vb.net
pr esenta tr e aspetti peculiar i e ve li ho spiegati. Tuttavia essi non costituiscono il ver o par adigma di pr ogr ammazione
ad oggetti e questo mi è stato fatto notar e da Netar r ow , che r ingr azio :P. Tr atter ò quindi, in questo momento tale
ar gomento. Nonostante il nome possa sugger ir e un concetto difficile, non è complicato. Scr iver e un pr ogr amma usando
l'incapsulamento significa str uttur ar lo in sezioni in modo tale che il cambiamento di una di esse non si r iper cuota sul
funzionamento delle altr e. Facendo lo stesso esempio che por ta Wikipedia, potr este usar e tr e var iabili x , y e z per
deter minar e un punto e poi cambiar e idea e usar e un ar r ay di tr e elementi. Se avete str uttur ato il pr ogr amma nella
manier a suddetta, dovr este modificar e legger mente solo i metodi della sezione (modulo, classe o altr o) che è
impegnata nella lor o modifica e non tutto il pr ogr amma.




Convenzioni di denominazione
Nei capitoli pr ecedenti ho spesse volte r ipor tato quali siano le "convenzioni" per la cr eazione di nomi appositi, come
quelli per le pr opr ietà o per le inter facce. Esistono anche altr i canoni, stabiliti dalla Micr osoft, che dovr ebber o r ender e
il codice miglior e in ter mini di velocità di lettur a e chiar ezza. Pr ima di elencar li, espongo una br eve ser ie di
definizioni dei tipi di no m enclatur a usati:

         Pascal Case : nella notazione Pascal, ogni par te che for ma un nome deve iniziar e con una letter a maiuscola, ad
         esempio una var iabile che conetenga il per cor so di un file sar à FileName, o una pr ocedur a che analizza un
         oggetto sar à ScanObject. Si consiglia sempr e, in nomi composti da sostantivi e ver bi, di anticipar e i ver bi e
         posticipar e i sostantivi: il metodo per eseguir e la stampa di un documento sar à Pr intDocument e non
         DocumentPr int.
         Cam el Case : nella notazione camel, la pr ima par te del nome inizia con la letter a minuscola, e tutte le
         successive con una maiuscola. Ad esempio, il titolo di un libr o sar à bookTitle, o l'indir izzo di una per sona
         addr ess (un solo nome).
         No tazio ne Ung her e se : nella notazione ungher ese, il nome del membr o viene composto come in quella Pascal,
         ma è pr eceduto da un pr efisso alfanumer ico con l'iniziale minuscola che indica il tipo di membr o. Ad esempio una
         casella di testo (Tex tBox ) che contenga il nome di una per sona sar à txtName, o una lista di oggetti lstObject.
         A ll Case : nella notazione All, tutte le letter e sono maiuscole.

Detto questo, le seguenti dir ettive specificano quando usar e quale tipo di notazione:

         Nome di un metodo : Pascal
         Campo di una classe : Pascal
         Nome di una classe : Pascal
         Membr i pubblici : Pascal
Membr i pr otected : Pascal
Campi di enumer ator i : Pascal
Membr i pr ivati : Camel
Var iabili locali : Camel
Par ametr i : Camel
Nomi di contr olli : Ungher ese
Nomi costituiti da acr onimi: All se di 2 car atter i, altr imenti Pascal
B1. IDE: Uno sguardo approfondito

Fino ad or a ci siamo ser viti dell'ambiente di sviluppo integr ato - in br eve, IDE - come di un mer o suppor to per lo
sviluppo di semplici applicazioni console: ne abbiamo fatto uso in quanto dotato di editor di testo "intelligente", un
comodo debugger e un compilator e integr ato nelle funzionalità. E non potr ete negar e che anche solo per questo non ne
avr emmo potuto far e a meno. Tuttavia, iniziando a sviluppar e applicazioni dotate di GUI (Gr aphical User Inter face) a
finestr e, l'IDE diventa uno str umento ancor a più impor tante. Le sue numer ose featur es ci per mettono di "disegnar e" le
finestr e del pr ogr amma, associar vi codice, navigar e tr a i sor genti, modificar e pr opr ietà con un click, or ganizzar e le
var ie par ti dell'applicativo, ecceter a ecceter a... Pr ima di intr odur vi all'ambito Window s For ms, far ò una r apida
panor amica dell'IDE, più appr ofondita di quella pr oposta all'inizio.




Primo impatto
Fate click su File > New Pr oject, scegliete "Window s For m Application" e, dopo aver scelto un qualsiasi nome per il
pr ogetto, confer mate la scelta. Vi appar ir à una scher mata più o meno simile a quella che segue:




Non vi allar mate se manca qualcosa, poiché i settaggi standar d dell'IDE sar anno molto pr obabilmente diver si da quelli
che uso io. L'inter faccia dell'ambiente di sviluppo, comunque, è completamente customizzabile, dato che è anch'essa
str uttur ata a finestr e: potete aggiunger e, r imuover e, fonder e o divider e finestr e semplicemente tr ascinandole con il
mouse. Ecco un esempio di come manipolar e le par ti dell'IDE in questo v ideo .




Menù princ ipale
Il menù pr incipale è costituito dalla pr ima bar r a di voci appena sotto il bor do super ior e della finestr a. Esso per mette
di acceder e ad ogni oper azione possibile all'inter no dell'IDE. Per or a ci ser vir emo di questi:

       File: il menù File per mette di cr ear e nuovi pr ogetti, salvar li, chiuder e quelli cor r enti e/o apr ir e file r ecenti;
       Edit: contiene le var ie oper azioni effettuabili all'inter no dell'editor di testo: taglia, copia, incolla, undo, r edo,
       cer ca, sostituisci, seleziona tutto ecceter a...
       View : i sottomenù consentono di nasconder e o visualizzar e finestr e:




       Code per mette di visualizzar e il codice sor gente associato a una finestr a; Designer por ta in pr imo piano l'ar ea
       r iser vata alla cr eazione delle finestr e; Database ex plor er per mette di navigar e tr a le tabelle di un database
       aper to nell'IDE; Solution Ex plor er consente di veder e le singole par ti del pr ogetto (vedi par agr afo successivo);
       Er r or List visualizza la finestr a degli er r or i e Pr oper ties Window quella delle pr opr ietà. Il sottomenù di
       Toolbar s per mette di aggiunger e o r imuover e nuove categor ie di pulsanti alla bar r a degli str umenti. Le altr e
       voci per or a non ci inter essano;
       Pr oject : espone alcune opzioni per il pr ogetto ed in par ticolar e consente di acceder e alle pr opr ietà di pr ogetto;
       Build : for nisce diver se opzioni per la compilazione del pr ogetto e/o della soluzione.

Infine, cone Tools > Options, potr ete modificar e qualsiasi opzione r iguar dante l'ambiente di sviluppo, dal color e del
testo nell'editor , agli spazi usati per l'indentazione, all'autosalvataggio, ecceter a... (non vale la pena di analizzar e tutte
le voci disponibili, per chè sono ver amente tr oppe!).
Solution Explorer
La finestr a denominata "Solution Ex plor er " per mette di navigar e all'inter no della soluzione cor r ente e veder ne le var ie
par ti. Una soluzione è l'insieme di due o più pr ogetti, o, se si tr atta di un pr ogetto singolo, coincide con esso.




Come vedete ci sono cinque pulsanti sulla bar r a super ior e: il pr imo per mette di apr ir e una finestr a delle pr opr ietà per
l'elemento selezionato; il secondo visualizza tutti i files fisicamente esistenti nella car tella della soluzione, il ter zo
aggior na il solution ex plor er (nel caso di files aggiunti dall'ester no dell'IDE), mentr e quar to e quinto per mettono di
passar e dal codice al visual designer e vicever sa. Pr emendo il secondo pulsante, potr emo ossevar e che c'è molto più di
ciò che appar e a pr ima vista:
La pr ima car tella contiene dei files che vanno a costr uir e uno dei namespace più utili in un'applicazione w indow s, M y,
di cui ci occuper emo nella sezione C. La seconda car tella mostr a l'elenco di tutti i r ifer imenti inclusi nel pr ogetto: il
numer o e il tipo di assembly impor tati var ia a seconda della ver sione dell'IDE e nella 2008 quelli elencati sono gli
elementi di default. Per i nostr i pr ogetti, solamente tr e sar anno di vitale impor tanza: System, System.Dr aw ing e
System.Window s.For ms. Potete r imuover e gli altr i senza pr eoccupazione (questo ci far à r ispar miar e anche un
megabyte di RAM). La car tella bin contiene a sua volta una o due car telle (Debug e Release) in cui tr over ete il
pr ogr amma compilato o in modalità debug o in modalità r elease. obj, invece, è dedicato ai file che contengono il codice
oggetto, una ser ie di bytes molto simili al codice compilato, ma ancor a in attesa di esser e assemblati in un unico
eseguibile.
Dopo tutti questi elementi, che per or a ci inter essano poco, tr oviamo la For m1, di default la pr ima finestr a
dell'applicazione. Possiamo notar e che esiste anche un altr o file, oltr e a For m1.vb (in cui è contenuto il codice che
scr iviamo noi): For m1.Designer .vb. Quest'ultimo sor gente è pr odotto automaticamente dal Designer e contiene
istr uzioni che ser vono a inizializzar e e costr uir e l'inter faccia gr afica; in esso sono anche dichiar ati tutti i contr olli che
abbiamo tr ascinato sulla for m. Ogni for m, quindi, è costituita da due file sor genti diver si, i quali contengono, tuttavia,
infor mazioni sulla stessa classe (For m1 in questo caso). Se r icor date, avevo detto che esiste una par ticolar e categor ia
di classi che possono esser e scr itte su file diver si: le classi par ziali. In gener e, infatti, le for ms sono classi par ziali, in
cui il codice "gr afico" viene tenuto nascosto e pr odotto dall'IDE e il codice di utilità viene scr itto dal pr ogr ammator e.




Finestra delle proprietà
Contiene un elenco di tutte le pr opr ietà dell'elemento selezionato nel designer e per mette di modificar le dir ettamente
dall'ambiente di sviluppo. Il box sottostante contiene anche una br eve descr izione della pr opr ietà selezionata.
Pr emendo il pulsante con l'icona del fulmine in cima alla finestr a, si apr ir à la finestr a degli Eventi, che contiene,
appunto, una lista di tutti gli eventi che il contr ollo possiede (vedi pr ossimo capitolo).
B2. Gli Eventi

Cosa sono
Or a che stiamo per entr ar e nel mondo della pr ogr ammazione visuale, è necessar io allontanar si da quello ster eotipo di
applicazione che ho usato fin dall'inizio della guida. In questo nuovo contesto, "non esiste" una Sub Main (o, per meglio
dir e, esiste ma possiede una semplice funzione di inizializzazione): il codice da eseguir e, quindi, non viene posto in un
singolo blocco ed eseguito dall'inizio alla fine seguendo un flusso ben definito. Piuttosto, esiste un oggetto standar d, la
For m - nome tecnico della finestr a - che viene cr eato all'avvio dell'applicazione e che l'utente può veder e e manipolar e a
suo piacimento. L'appr occio cambia: il pr ogr ammator e non vincola il flusso di esecuzione, ma dice semplicemente al
pr ogr amma "come compor tar si" in r eazione all'input dell'utente. Ad esempio, viene pr emuto un cer to pulsante: bene, al
click esegui questo codice; viene inser ito un testo in una casella di testo: quando l'utente digita un car atter e, esegui
quest'altr o codice, e così via... Il codice viene scr itto, quindi, per even ti. Volendo dar e una definizione teor ico-
concettuale di evento, potr emmo dir e che è un qualsiasi atto che modifica lo stato attuale di un oggetto. Ho di
pr oposito detto "oggetto", poiché le For ms non sono le uniche entità a posseder e eventi. Passando ad un ambito più
for male e r igor oso, infatti, un evento non è altr o che una speciale var iabile di tipo delegate (multicast). Essendo di tipo
delegate, tale var iabile può contener e r ifer imenti a uno o più metodi, i quali vengono comunemente chiamati g esto r i
d'ev ento (o ev ent's handler ). La pr ogr ammazione visuale, in sostanza, r ichiede di scr iver e tanti gestor i d'evento
quanti sono gli eventi che vogliamo gestir e e, quindi, tanti quanti possono esser e le azioni che il nostr o pr ogr amma
consente all'utente di eseguir e. Mediante queste definizioni, delineamo il compor tamento di tutta l'applicazione.




Sintassi e invoc azione degli eventi
Le facilitazioni che l'IDE mette a disposizione per la scr ittur a dei gestor i d'evento por tano spesso i pr ogr ammator i
novelli a non saper e cosa siano e come funzionino r ealmente gli eventi, anche a causa di una consider evole pr esenza di
tutor ial del tipo HOW-TO che spiegano in due o tr e passaggi come costr uir e "il tuo pr imo pr ogr amma". Inutile dir e che
queste scor ciatie fanno più male che bene. Per questo motivo ho deciso di intr odur r e l'ar gomento quanto pr ima, per
metter vi subito al cor r ente di come stanno le cose.
Iniziamo con l'intr odur r e la sintassi con cui si dichiar a un evento:

    1. Event [Nome] As [Tipo]

Dove [Nome] è il nome dell'evento e [Tipo] il suo tipo. Data la natur a di ciò che staimo definendo, il tipo sar à sempr e un
tipo delegate. Possiamo sceglier e di utilizzar e un delegate già definito nelle libr er ie standar d del Fr amew or k - come il
classico EventHandler - oppur e decider e di scr iver ne uno noi al momento. Scegliendo il secondo caso, tuttavia, si devono
r ispettar e delle convenzioni:

       Il nome del delegate deve ter minar e con la par ola "Handler ";
       Il delegate deve espor r e solo due par ametr i;
       Il pr imo par ametr o è solitamente chiamato "sender " ed è comunemente di tipo Object. Questa convenzione è
       abbastanza r estr ittiva e non è necessar io seguir la sempr e;
       Il secondo par ametr o è solitamente chiamato "e" ed il suo tipo è una classe che er edita da System.EventAr gs. Allo
       stesso modo, possiamo definir e un nuovo tipo der ivato da tale classe per il nuovo delegate, ma il nome di questo
       tipo deve ter minar e con "EventAr gs".

Come avr ete notato sono un po' fissato sulle convenzioni. Ser vono a r ender e il codice più chiar o e "standar d" (quando
non ci sono r egole da seguir e, ognuno fa come meglio cr ede: vedi, ad esempio, i compilator i C). Ad ogni modo, sender
r appr esenta l'oggetto che ha gener ato l'evento, mentr e e contiene tutte le infor mazioni r elative alle cir costanze in cui
questo evento si è ver ificato. Se e è di tipo EventAr gs, non contiene alcun membr o: il fatto che l'evento sia stato
gener ato è di per sé significativo. Ad esempio, per un ipotetico evento Click non avr emmo bisogno di conoscer e
nessun'altr a infor mazione: ci basta saper e che è stato fatto click col mouse. Invece, per l'evento KeyDow n (pr essione di
un tasto sulla tastier a) sar ebbe inter essante saper e quale tasto è stato pr emuto, il codice associato ad esso ed
eventualmente il car atter e. Ma or a passiamo a un piccolo esempio sul pr imo caso, mantenendoci ancor a per qualche
r iga in una Console Application:

 001. Module Module1
 002.
 003.     'Questa classe rappresenta una collezione generica di
 004.     'elementi che può essere ordinata con l'algoritmo
 005.     'Bubble Sort già analizzato
 006.     Public Class BubbleCollection(Of T As IComparable)
 007.         'Eredita tutti i membri pubblici e protected della classe
 008.         'a tipizzazione forte List(Of T), il che consente di
 009.         'disporre di tutti i metodi delle liste scrivendo
 010.         'solo una linea di codice
 011.         Inherits List(Of T)
 012.
 013.         'Questo campo indica il numero di millisecondi impiegati
 014.         'ad ordinare tutta la collezione
 015.         Private _TimeElapsed As Single = 0
 016.
 017.         Public ReadOnly Property TimeElapsed() As Single
 018.              Get
 019.                  Return _TimeElapsed
 020.              End Get
 021.         End Property
 022.
 023.         'Ecco gli eventi:
 024.         'Il primo viene lanciato prima che inizi la procedura di
 025.         'ordinamento, e per tale motivo è di tipo
 026.         'CancelEventHandler. Questo delegate espone come
 027.         'secondo parametro della signature una variabile "e"
 028.         'al cui intero è disponibile una proprietà
 029.         'Cancel che indica se cancellare oppure no l'operazione.
 030.         'Se si volesse cancellare l'operazione sarebbe possibile
 031.         'farlo nell'evento BeforeSorting.
 032.         'In genere, si usa il tipo CancelEventHandler o un suo
 033.         'derivato ogni volta che bisogna gestire un evento
 034.         'che inizia un'operazione annullabile.
 035.         Event BeforeSorting As System.ComponentModel.CancelEventHandler
 036.         'Il secondo viene lanciato dopo aver terminato la procedura
 037.         'di ordinamento e serve solo a notificare un'azione
 038.         'avvenuta. Il tipo è un semplicissimo EventHandler
 039.         Event AfterSorting As EventHandler
 040.
 041.         'Scambia l'elemento alla posizione Index con il suo
 042.         'successivo
 043.         Private Sub SwapInList(ByVal Index As Int32)
 044.              Dim Temp As T = Me(Index + 1)
 045.              Me.RemoveAt(Index + 1)
 046.              Me.Insert(Index, Temp)
 047.         End Sub
 048.
 049.         'In List(Of T) è già presente un metodo Sort,
 050.         'perciò bisogna oscurarlo con Shadows (in quanto non
 051.         'è sovrascrivibile con il polimorfismo)
 052.         Public Shadows Sub Sort()
 053.              Dim Occurrences As Int32
 054.              Dim J As Int32
 055.              Dim Time As New Stopwatch
 056.              'Attenzione! non bisogna confondere EventHandlers con
 057.              'EventArgs: il primo è un tipo delegate e costituisce
 058.              'il tipo dell'evento; il secondo è un normale tipo
 059.              'reference e rappresenta tutti gli argomenti opzionali
 060.              'inerenti alle operazioni svolte
 061.              Dim e As New System.ComponentModel.CancelEventArgs
 062.
 063.
'Viene generato l'evento. RaiseEvent si occupa di
  064.              'richiamare tutti i gestori d'evento memorizzati
  065.              'nell'evento BeforeSorting (che, ricordo, è un
  066.              'delegate multicast). A tutti i gestori d'evento
  067.              'vengono passati i parametri Me ed e. Al termine
  068.              'di questa operazione, se un gestore d'evento ha
  069.              'modificato una qualsiasi proprietà di e (e volendo,
  070.              'anche di quest'oggetto), possiamo sfruttare tale
  071.              'conoscenza per agire in modi diversi.
  072.              RaiseEvent BeforeSorting(Me, e)
  073.              'In questo caso, se e.Cancel = True si
  074.              'cancella l'operazione
  075.              If e.Cancel Then
  076.                  Exit Sub
  077.              End If
  078.
  079.              Time.Start()
  080.              J = 0
  081.              Do
  082.                  Occurrences = 0
  083.                  For I As Int32 = 0 To Me.Count - 1 - J
  084.                       If I = Me.Count - 1 Then
  085.                           Continue For
  086.                       End If
  087.                       If Me(I).CompareTo(Me(I + 1)) = 1 Then
  088.                           SwapInList(I)
  089.                           Occurrences += 1
  090.                       End If
  091.                  Next
  092.                  J += 1
  093.              Loop Until Occurrences = 0
  094.              Time.Stop()
  095.              _TimeElapsed = Time.ElapsedMilliseconds
  096.
  097.              'Qui genera semplicemente l'evento
  098.              RaiseEvent AfterSorting(Me, EventArgs.Empty)
  099.          End Sub
  100.
  101.     End Class
  102.
  103.     '...
  104.
  105. End Module

Questo codice mostr a anche l'uso dell'istr uzione RaiseEvent, usata per gener ar e un evento. Essa non fa altr o che
scor r er e tutta l'invocation list di tale evento ed invocar e tutti i gestor i d'evento ivi contenuti. Le invocazioni si
svolgono, di nor ma, una dopo l'altr a (sono sincr one).
Or a che abbiamo ter minato la classe, tuttavia, bisogner ebbe anche poter la usar e, ma mancano ancor a due impor tanti
infor mazioni per esser e in gr ado di gestir la cor r ettamente. Pr ima di tutto, la var iabile che user emo per contener e
l'unica istanza di BubbleCollection deve esser e dichiar ata in modo diver so dal solito. Se nor malmente potr emmo
scr iver e:

    1. Dim Bubble As New BubbleCollection(Of Int32)

in questo caso, non basta. Per poter usar e gli eventi di un oggetto, è necessar io comunicar lo esplicitamente al
compilator e usando la keyw or d WithEvents, da antepor r e (o sostituir e) a Dim:

    1. WithEvents Bubble As New BubbleCollection(Of Int32)

Infine, dobbiamo associar e dei gestor i d'evento ai due eventi che Bubble espone (NB: non è obbligator io associar e
handler a tutti gli eventi di un oggetto, ma basta far lo per quelli che ci inter essano). Per associar e un metodo a un
evento e far lo diventar e gestor e di quell'evento, si usa la clausola Handles, molto simile come sintassi alla clausola
Implements analizzata nei capitoli sulle inter facce.

    1. Sub [Nome Gestore](ByVal sender As Object, ByVal e As [Tipo]) Handles [Oggetto].[Evento]
    2.     '...
    3.
End Sub

I gestor i d'evento sono sempr e pr ocedur e e mai funzioni: questo è ovvio, poiché eseguono solo istr uzioni e nessuno
r ichiede alcun valor e in r itor no da lor o. Ecco l'esempio completo:

 001. Module Module1
 002.
 003.     Public Class BubbleCollection(Of T As IComparable)
 004.         Inherits List(Of T)
 005.
 006.         Private _TimeElapsed As Single = 0
 007.
 008.         Public ReadOnly Property TimeElapsed() As Single
 009.              Get
 010.                  Return _TimeElapsed
 011.              End Get
 012.         End Property
 013.
 014.         Event BeforeSorting As System.ComponentModel.CancelEventHandler
 015.         Event AfterSorting As EventHandler
 016.
 017.         Private Sub SwapInList(ByVal Index As Int32)
 018.              Dim Temp As T = Me(Index + 1)
 019.              Me.RemoveAt(Index + 1)
 020.              Me.Insert(Index, Temp)
 021.         End Sub
 022.
 023.         Public Shadows Sub Sort()
 024.              Dim Occurrences As Int32
 025.              Dim J As Int32
 026.              Dim Time As New Stopwatch
 027.              Dim e As New System.ComponentModel.CancelEventArgs
 028.
 029.              RaiseEvent BeforeSorting(Me, e)
 030.              If e.Cancel Then
 031.                  Exit Sub
 032.              End If
 033.
 034.              Time.Start()
 035.              J = 0
 036.              Do
 037.                  Occurrences = 0
 038.                  For I As Int32 = 0 To Me.Count - 1 - J
 039.                       If I = Me.Count - 1 Then
 040.                           Continue For
 041.                       End If
 042.                       If Me(I).CompareTo(Me(I + 1)) = 1 Then
 043.                           SwapInList(I)
 044.                           Occurrences += 1
 045.                       End If
 046.                  Next
 047.                  J += 1
 048.              Loop Until Occurrences = 0
 049.              Time.Stop()
 050.              _TimeElapsed = Time.ElapsedMilliseconds
 051.
 052.              'Qui genera semplicemente l'evento
 053.              RaiseEvent AfterSorting(Me, EventArgs.Empty)
 054.         End Sub
 055.
 056.     End Class
 057.
 058.     'Bubble è WithEvents poiché ne utilizzeremo
 059.     'gli eventi
 060.     WithEvents Bubble As New BubbleCollection(Of Int32)
 061.     Sub Main()
 062.         Dim I As Int32
 063.
 064.         Console.WriteLine("Inserire degli interi (0 per terminare):")
 065.         I = Console.ReadLine
 066.         Do While I <> 0
 067.
Bubble.Add(I)
 068.                      I = Console.ReadLine
 069.               Loop
 070.
 071.             'Il corpo di Main termina con l'esecuzione di Sort, ma
 072.             'il programma non finisce qui, poiché Sort
 073.             'scatena due eventi, BeforeSorting e AfterSorting.
 074.             'Questi comportano l'esecuzione prima del metodo
 075.             'Bubble_BeforeSorting e poi di Bubble_AfterSorting.
 076.             'Vedrete bene il risultato eseguendo il programma
 077.             Bubble.Sort()
 078.         End Sub
 079.
 080.         Private Sub Bubble_BeforeSorting(ByVal sender As Object, ByVal e As
                 System.ComponentModel.CancelEventArgs) Handles Bubble.BeforeSorting
 081.              If Bubble.Count = 0 Then
 082.                  e.Cancel = True
 083.                  Console.WriteLine("Lista vuota!")
 084.                  Console.ReadKey()
 085.              End If
 086.         End Sub
 087.
 088.         Private Sub Bubble_AfterSorting(ByVal sender As Object, ByVal e As EventArgs) Handles
                 Bubble.AfterSorting
 089.              Console.WriteLine("Lista ordinata:")
 090.              'Scrive a schermo tutti gli elementi di Bubble
 091.              'mediante un delegate generico.
 092.              Bubble.ForEach(AddressOf Console.WriteLine)
 093.              Console.WriteLine("Tempo impiegato: {0} ms", Bubble.TimeElapsed)
 094.              Console.ReadKey()
 095.         End Sub
 096.
 097.         'Handles significa "gestisce". In questo come in molti altri
 098.         'casi, il codice è molto simile al linguaggio.
 099.         'Ad esempio, traducendo in italiano si avrebbe:
 100.         ' Bubble_AfterSorting gestisce Bubble.AfterSorting
 101.         'Il VB è molto chiaro nelle keywords
 102. End     Module

Anche per i nomi dei gestor i d'evento c'è questa convenzione: "[Oggetto che gener a l'evento]_[Evento gestito]".
Ciò che abbiamo appena visto consente di eseguir e una sor ta di ear ly binding, ossia legar e l'evento a un gestor e
dur ante la scr ittur a del codice. C'è, par imenti, una tecnica par allela più simile al late binding, che consente di associar e
un gestor e ad un evento dinamicamente. La sintassi è:

    1.   'Add Handler = Aggiungi Gestore; molto intuitiva come keyword
    2.   AddHandler [Oggetto].[Evento], AddressOf [Gestore]
    3.   'E per rimuovere il gestore dall'invocation list:
    4.   RemoveHandler [Oggetto].[Evento], AddressOf [Gestore]

Il codice sopr a potr ebbe esser e stato modificato come segue:

   01. Module Module1
   02.
   03.     '...
   04.
   05.     WithEvents Bubble As New BubbleCollection(Of Int32)
   06.     Sub Main()
   07.          Dim I As Int32
   08.
   09.          AddHandler Bubble.BeforeSorting, AddressOf Bubble_BeforeSorting
   10.          AddHandler Bubble.AfterSorting, AddressOf Bubble_AfterSorting
   11.
   12.          Console.WriteLine("Inserire degli interi (0 per terminare):")
   13.          I = Console.ReadLine
   14.          Do While I <> 0
   15.               Bubble.Add(I)
   16.               I = Console.ReadLine
   17.          Loop
   18.
   19.          'Il corpo di Main termina con l'esecuzione di Sort, ma
   20.
'il programma non finisce qui, poiché Sort
   21.            'scatena due eventi, BeforeSorting e AfterSorting.
   22.            'Questi comportano l'esecuzione prima del metodo
   23.            'Bubble_BeforeSorting e poi di Bubble_AfterSorting.
   24.            'Vedrete bene il risultato eseguendo il programma
   25.            Bubble.Sort()
   26.        End Sub
   27.
   28.        Private Sub Bubble_BeforeSorting(ByVal sender As Object, ByVal e As
                 System.ComponentModel.CancelEventArgs)
   29.             If Bubble.Count = 0 Then
   30.                 e.Cancel = True
   31.                 Console.WriteLine("Lista vuota!")
   32.                 Console.ReadKey()
   33.             End If
   34.        End Sub
   35.
   36.     Private Sub Bubble_AfterSorting(ByVal sender As Object, ByVal e As EventArgs)
   37.         Console.WriteLine("Lista ordinata:")
   38.         Bubble.ForEach(AddressOf Console.WriteLine)
   39.         Console.WriteLine("Tempo impiegato: {0} ms", Bubble.TimeElapsed)
   40.         Console.ReadKey()
   41.     End Sub
   42. End Module

Ovviamente se usate questo metodo, non potr ete usar e allo stesso tempo anche Handles, o aggiunger este due volte lo
stesso gestor e!
B3. I Controlli

La base delle applic azioni W indow s Form
Se gli eventi sono il pr incipale meccanismo con cui scr iver e un'applicazione visuale, i contr olli sono i pr incipali oggetti
da usar e. For malmente, un contr ollo non è altr o che una classe der ivata da System.Window s.For ms.Contr ol. In pr atica,
esso r appr esenta un qualsiasi componente dell'inter faccia gr afica di un pr ogr amma: pulsanti, menù, caselle di testo,
liste var ie, e anche le finestr e, sono tutti contr olli. Per questa r agione, se volete cr ear e una GUI (Gr aphical User
Inter face) per il vostr o applicativo, dovr ete necessar iamente conoscer e quali contr olli le libr er ie standar d vi mettono a
disposizione (e questo avviene in tutti i linguaggi che suppor tino libr er ie visuali). Conoscer e un contr ollo significa
pr incipalmente saper e quali pr opr ietà, metodi ed eventi esso possiede e come usar li.
Una volta aper to il pr ogetto Window s For m, tr over ete che l'IDE ha cr eato per noi la pr ima For m, ossia la pr ima
finestr a dell'applicazione. Essa sar à la pr ima ad esser e aper ta quando il pr ogr amma ver r à fatto cor r er e e, per i
pr ossimi capitoli, sar à anche l'unica che user emo. L'esecuzione ter mina automaticamente quando tale finestr a viene
chiusa. Come avr ete visto, inoltr e, tr a le mer avigliose funzionalità del nostr o ambiente di sviluppo c'è anche un'ar ea
gr afica - detta Designer - che ci per mette di veder e un'antepr ima della For m e di modificar la o aggiunger ci nuovi
elementi. Per modificar e l'aspetto o il compor tamento della For m, è sufficiente modificar e le r elative pr opr ietà nella
finestr a delle pr opr ietà




Mentr e per aggiunger e elementi alla super ficie liber a della finestr a, è sufficiente tr ascinar e i contr olli desider ati dalla
toolbox nel designer . La toolbox è di solito nascosta e la si può mostr ar e soffer mandosi un secondo sulla linguetta
"Toolbox " che spunta fuor i dal lato sinistr o della scher mata dell'IDE:




La c lasse Control
La classe Contr ol è la classe base di tutti i contr olli (ma non è astr atta). Essa espone un buon numer o di metodi e
pr opr ietà che vengono er editati da tutti i suoi der ivati. Tr a questi membr i di default, sono da r icor dar e:

        Allow Dr op : specifica se il contr ollo suppor ta il Dr ag and Dr op (per ulter ior i infor mazioni su questa tecnica,
        veder e capitolo r elativo);
        Anchor : pr opr ietà enumer ata codificata a bit (vedi capitolo sugli enumer ator i) che per mette di impostar e a
        quali   lati   del for m   i   cor r ispondenti   lati   del contr ollo   r estano   "ancor ati"   dur ante   il pr ocesso   di
        r idimensionamento. Dir e che un un contr ollo è ancor ato a destr a, per esempio, significa che il suo lato destr o
        manter r à sempr e la stessa distanza dal lato destr o del suo contenitor e (il contenitor e per eccellenza è la For m
        stessa). Seguendo questa logica, ancor ando un contr ollo a tutti i lati, si otter r à come r isultato che quel contr ollo
        si ingr andir à quanto il suo contenitor e;
        BackColor : color e di sfondo;
        Backgr oundImage : immagine di sfondo;
        Contex tMenuStr ip : il menù contestuale associato al contr ollo;
        Contr ols : l'elenco dei contr olli contenuti all'inter no del contr ollo cor r ente. Un contr ollo può, infatti, far e da
        "contenitor e" per altr i contr olli. La finestr a, la For m, è un classico esempio di contenitor e, ma nel cor so delle
        lezioni vedr emo altr i contr olli specializzati e molto ver satili pensati apposta per questo compito;
        DoDr agDr op() : inizia un'oper azione di Dr ag and Dr op da questo contr ollo;
Enabled : deter mina se il contr ollo è abilitato. Quando disabilitato, esso è di color e gr igio scur o e non è possibile
       alcuna inter azone tr a l'utente e il contr ollo stesso;
       Focus() : attiva il contr ollo;
       Focused : deter mina se il contr ollo è attivo;
       Font : car atter e con cui il testo viene scr itto sul contr ollo (se è pr esente del testo);
       For eColor : color e del testo;
       Height : altezza, in pix el, del contr ollo;
       Location : posizione del contr ollo r ispetto al suo contenitor e (r estituisce un valor e di tipo Point);
       MousePosition : posizione del mouse r ispetto al contr ollo (anche questa r estituisce un Point);
       Name : il nome del contr ollo (molto spesso coincide col nome della var iabile che r appr esenta quel contr ollo nel
       for m);
       Size : dimensione del contr ollo (r estituisce un valor e di tipo Size);
       TabIndex : for se non tutti sanno che con il pulsante Tab (tabulazione) è possibile scor r er e or dinatamente i
       contr olli. Ad esempio, in una finestr a con due caselle di testo, è possibile spostar si dalla pr ima alla seconda
       pr emendo Tab. Questo accade anche nei moduli Web. La pr opr ietà TabIndex deter mina l'indice associato al
       contr ollo in questo meccanismo. Così, se una casella di testo ha TabIndex = 0 e un menù a discesa TabIndex = 1,
       una volta selezionata la casella di testo sar à possibile spostar si sul menù a discesa pr emendo Tab. L'iter azione
       può continuar e indefinitamente per un qualsiasi numer o di contr olli e, una volta r aggiunta la fine, r einizia
       daccapo;
       Tag : qualsiasi oggetto associato al contr ollo. Tag è di tipo Object ed è molto utile per immagazzinar e
       infor mazioni di var io gener e che non è possibile por r e in nessun'altr a pr opr ietà;
       Tex t : testo visualizzato sul contr ollo (se il contr ollo pr evede del testo);
       Visible : deter mina se il contr ollo è visibile;
       Width : lar ghezza, in pix el, del contr ollo.




La c lasse Form
For m è la classe che r appr esenta una finestr a. Ogni finestr a che noi usiamo nelle applicazioni è r appr esentata da una
classe der ivata da For m. Oltr e ai membr i di Contr ol, essa ne espone molti altr i. Ecco una lista molto sintetica di alcuni
membr i che potr ebber o inter essar vi ad or a:

       Allow Tr anspar ency : deter mina se il for m può esser e r eso tr aspar ente (vedi pr opr ietà Opacity);
       AutoScr oll : deter mina se sulla finestr a venga automaticamente mostr ata una bar r a di scor r imento quando i
       contr olli che essa contiene spor gono oltr e il suo bor do visibile;
       Close() : chiude la for m. Se si tr atta della pr ima for m, l'applicazione ter mina (è possibile modificar e questo
       compor tamento, come vedr emo in seguito);
       For mBor der Style : imposta il tipo di bor do della finestr a (nessuno, singolo, doppio: singolo equivale a non poter
       r idimensionar e la finestr a);
       HelpButton : deter mina se il pulsante help (?) è visualizzato nella bar r a del titolo, accanto agli altr i;
       Hide() : nasconde la for m, ossia la r ende invisibile, ma non la chiude;
       Icon : indica l'icona mostr ata nell'angolo super ior e sinistr o della finestr a, vicino al titolo. Questà pr opr ietà è di
       tipo System.Dr aw ing.Icon;
       Max imizeBox : deter mina se l'icona che per mette di ingr andir e la finestr a a scher mo inter o è visualizzata;
       Max imumSize : massima dimensione consentita;
       MinimizeBox : deter mina se il pulsante che per mette di r idur r e la finestr a a icona è visualizzato;
       MinimumSize : minima dimensione consentita;
       Opacity : imposta l'opacità della finestr a: 0 per r ender la invisibile, 1 per r ender la totalmente opaca (nor male);
Show () : visualizza la for m nel caso sia nascosta o comunque non attualmente visibile sullo scher mo;
       Show Dialog() : come Show (), ma la finestr a viene mostr ata in modalità Dialog. In questo modo, l'utente può
       inter agir e solo con essa e con nessun'altr a for m del pr ogr amma fino a che questa non sia stata chiusa,
       confer mando una scelta o annullando l'oper azione. Restituisce come r isultato un valor e enumer ato che indica che
       azione l'utente abbia compiuto;
       Show Icon : deter mina se visualizzar e l'icona nella bar r a del titolo;
       Show InTaskBar : deter mina se visualizzar e la finestr a nella bar r a delle applicazioni;
       TopMost : deter mina se la finestr a è sempr e in pr imo piano;
       Window State : indica lo stato della finestr a (nor male, massimizzata, r idotta a icona).

Questi sono solo alcuni dei molteplici membr i che la classe espone. Ho elencato sopr attutto quelli che vi per metter anno
di modificar e l'aspetto ed il compor tamento della for m, in quanto, allo stato attuale delle cose, non siete in gr ado di
gestir e e compr ender e il r esto delle funzionalità. Nel cor so di questa sezione, comunque, intr odur r ò via via nuovi
dettagli r iguar do questa classe e spiegher ò come usar li. Ma or a passiamo alla scr ittur a del pr imo pr ogr amma...




Il c ontrollo Button
Per il pr ossimo esempio, dovr emo usar e un nuovo contr ollo, che possiamo indicar e senza r emor e come il pr incipale e
più usato meccanismo di inter azione: il pulsante. Esso viene r appr esentato dal contr ollo Button. Dopo aver aper to un
nuovo pr ogetto Window s For m vuoto, tr ascinate un nuovo pulsante dalla toolbox sulla super ficie della finestr a e
posizionatelo dove più vi aggr ada. Il nome di questo contr ollo sar à btnHello, ad esempio.
Or a che abbiamo disposto l'unico elemento della GUI, bisogna cr ear e un gestor e d'evento che si occupi di eseguir e del
codice quando l'utente clicca sul pulsante. Per far e ciò, possiamo scr iver e il codice a mano o semplicemente far e doppio
click sul pulsante nel Designer e l'IDE scr iver à automaticamente il codice associato. Questo succede per chè ogni contr ollo
ha un "evento di default", ossia quell'evento che viene usato più spesso: il doppio click su un elemento dell'inter faccia
gr afica ci per mette di delegar e all'ambiente di sviluppo la stesur a del pr ototipo per la Sub che dovr emo cr ear e per tale
evento. Nel caso di Button, l'evento più usato è Click. Il codice automaticamente gener ato sar à:

    1. Private Sub btnHello_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles
          btnHello.Click
    2.
    3. End Sub

Or a, all'inter no del cor po della pr ocedur a possiamo por r e ciò che vogliamo. In questo esempio, visualizzer emo a
scher mo il messaggio "Hello, Wor ld!", ma in modo diver so dalle applicazioni console. In questo ambiente, si è soliti usar e
una par ticolar e classe che ser ve per visualizzar e finestr e di avver timento. Tale classe è MessageBox e ha un solo
metodo statico, Show :

    1. Public Class Form1
    2.
    3.     Private Sub btnHello_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
              Handles btnHello.Click
    4.          MessageBox.Show("Hello, World!", "Esempio", MessageBoxButtons.OK,
                   MessageBoxIcon.Information)
    5.     End Sub
    6.
    7. End Class

Show accetta come minimo un par ametr o, ossia il messaggio da visualizzar e. Tutti gli altr i par ametr i sono "opzionali"
(non nel ver o senso del ter mine, ma esisteono 18 ver sioni diver se dello stesso metodo Show modificate tr amite
over loading). In questo caso, il secondo indica il titolo della finestr a di avviso, il ter zo i pulsanti visualizzati (un solo
pulsante "OK") ed il quar to l'icona mostr ata in fianco al messaggio (una "I" bianca su sfondo blu, che significa
"Infor mazione").
 Vb.net
B4. Label e TextBox

In questo capitolo mi occuper ò di altr i due comunissimi contr olli: label (etichetta) e tex tbox (casella di testo). L'esempio
della lezione consiste nello scr iver e un pr ogr amma che, dato il r aggio, calcola l'ar ea del cer chio.




Label
Il contr ollo Label ser ve per visualizzar e un qualsiasi messaggio o testo sulla super ficie della w indow s for m. Per questo
pr ogetto, occor r e aggiunger e una label all'inter no del for m designer e impostar e il testo su "Intr odur r e il r aggio di un
cer chio:". Poichè questo tipo di contr ollo è utilizzatissimo, è inutile assegnar e un nome significativo a ogni sua istanza,
a meno che non la si debba modificar e dur ante l'esecuzione del pr ogr amma. Solo due pr opr ietà mer itano di esser e
menzionate:

        AutoSize : se attiva, r idimensiona la label per ader ir e alla lunghezza del testo. Il r idimensionamento avviene
        solo in lunghezza, a meno che il testo non contenga esplicitamente un car atter e "a capo". Se disattivata, invece,
        il testo della label ver r à automaticamente spostato per r ientr ar e nei limiti imposti dalla sua dimensione;
        Tex tAlign : per mette di allinear e il testo in 9 modi diver si, combinando i tr e valor i di allineamente ver ticale
        (Top, Center , Bottom) con i tr e valor i di allineamento or izzontale (Left, Center , Right). L'allineamento non è
        effettivo se AutoSize = Tr ue.

Dopo aver modificato le pr opr ietà della for m come nella lezione scor sa, l'inter faccia si pr esenter à pr essapoco così:




TextBox
Costituisce il contr ollo di input per eccellenza, il più usato in tutte quelle situazioni che r ichiedono all'utente di
immetter e dati. Le pr opr ietà r ilevanti sono:

        Max Length : massima lunghezza del testo, in car atter i;
        AutoCompleteMode : modalità di autocompletamento. Fr a i pr egi della Tex tBox vi è la possibilità di "sugger ir e"
        all'utente cosa digitar e nel caso le pr ime letter e pr emute cor r ispondano all'inizio di una delle par ole che il
        pr ogr amma ha già elabor ato. Per far e un esempio pr atico, si compor ta allo stesso modo del sistema di
        composizione T9 dei cellular i, in cui il r esto della par ola viene "sugger ita" pr ima del suo completamento.
        L'enumer ator e può assumer e quattr o valor i: None (assente), Suggest (viene sugger ita la par ola facendo
        appar ir e sotto la tex tbox un menù a discesa con tutte le possibili var ianti), Append (viene sugger ita la par ola
        accodando alle letter e digitate il pezzo mancante evidenziato il blu), AppendSuggest (un'unione di entr ambe le
        pr ecedenti opzioni);
        AutoCompleteSour ce : fonte dalla quale pr elevar e le par ole dell'autocompletamento. I valor i pr edefiniti indicano
r isor se di sistema, quali la cr onologia (Histor yList, nel caso, ad esempio, la tex tbox funga da contenitor e di
       indir izzi inter net), le car telle (FileSystemDir ector ies, ad esempio per facilitar e l'immissione di un per cor so da
       tastier a), i file (FileSystem), i files o i pr ogr ammi aper ti di r ecente (RecentlyUsedList), oppur e tutti questi
       insieme (AllSystemResour ces). Se impostato su CustomSour ce, sar à la pr opr ietà AutoCompleteCustomSour ce a
       deter minar e la fonte da cui attinger e infor mazioni;
       Char acter Casing : indica il casing delle letter e. Ci sono tr e valor i possibili: None (tutte le letter e vengono
       lasciate così come sono), Upper (tutte le letter e sono conver tite in maiuscole) o Low er (tutte in minuscole);
       Lines : r estituisce un ar r ay di str inghe r appr esentanti tutte le r ighe di testo della tex tbox , nel caso di una
       tex tbox Multiline;
       Multiline : se impostata su Tr ue, la tex tbox sar à r idimensionabile e l'utente potr à inser ir e un testo che
       compr ende più r ighe. Quando la pr opr ietà è False, il car atter e "a capo" viene r espinto;
       Passw or dChar : un valor e di tipo Char che deter mina il car atter e da visualizzar e al posto delle letter e qualor a
       la tex tbox debba contener e una passw or d. In questo modo si evita che occhi indiscr eti possano intr aveder e le
       str inghe digitate. Impostando questa pr opr ietà, si mascher a automaticamente il testo;
       ReadOnly : deter mina se l'utente può modificar e il testo della tex tbox ;
       Scr ollBar s : pr opr ietà enumer ata che specifica se le bar r e di scor r imento devono esser e pr esenti.
       L'enumer ator e accetta quattr o valor i: None (nessuna scr ollbar ), Ver tical (solo ver ticale), Hor izontal (solo
       or izzontale), Both (entr ambe);

Or a aggiungiamo una tex tbox di nome tx tRadius, appena sotto la label.




Finire il programma di c alc olo
Ultima cosa essenziale per concluder e il pr ogr amma è un pulsante che avvii il calcolo, altr imenti non si potr ebbe saper e
quando l'utente ha finito l'immissione e vuole conoscer e il r isultato. Dopo aver aggiunto il button btnAr ea, la finestr a
sar à simile a questa:




Doppio click sul pulsante per apr ir e l'editor di codice sull'evento Click di btnAr ea:

   01. Public Class Form1
   02.
   03.     Private Sub btnArea_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
              Handles btnArea.Click
   04.          Dim Radius As Single = txtRadius.Text
   05.          Dim Area As Single
   06.          Area = Radius ^ 2 * Math.PI
   07.          MessageBox.Show("L'area del cerchio è " & Area & ".", Me.Text, MessageBoxButtons.OK,
                   MessageBoxIcon.Information)
   08.     End Sub
   09.
   10. End Class

Pr ima di far cor r er e il pr ogr amma, bisogna r icor dar si che i numer i decimali immessi in input devono aver e la vir gola,
e non il punto.
Da notar e che abbiamo assegnato una str inga a un valor e single: come già detto, in VB.NET, le conver sioni implicite
vengono eseguite automaticamente quando sono possibili e Option Str ict è disattivata.
Tuttavia, se l'utente immettesse una par ola, il pr ogr amma andr ebbe in cr ash: vediamo quindi di r affinar e il codice così
da inter cettar e l'eccezione gener ata.

   01. Public Class Form1
   02.
   03.     Private Sub btnArea_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
              Handles btnArea.Click
   04.          Try
   05.              Dim Radius As Single = txtRadius.Text
   06.              Dim Area As Single
   07.              Area = Radius ^ 2 * Math.PI
   08.              MessageBox.Show("L'area del cerchio è " & Area & ".", Me.Text,
                       MessageBoxButtons.OK, MessageBoxIcon.Information)
   09.          Catch ICE As InvalidCastException
   10.              MessageBox.Show("Inserire un valore numerico valido!", Me.Text,
                       MessageBoxButtons.OK, MessageBoxIcon.Error)
   11.          End Try
   12.     End Sub
   13.
   14. End Class

Ma non basta ancor a. I numer i negativi o nulli vengono comunque accetati, ma per definizione una lunghezza non può
aver e misur a non positiva, per ciò:

   01. Public Class Form1
   02.
   03.     Private Sub btnArea_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
              Handles btnArea.Click
   04.          Try
   05.              Dim Radius As Single = txtRadius.Text
   06.
   07.              If Radius <= 0 Then
   08.                   Throw New ArgumentException()
   09.              End If
   10.
   11.              Dim Area As Single
   12.              Area = Radius ^ 2 * Math.PI
   13.              MessageBox.Show("L'area del cerchio è " & Area & ".", Me.Text,
                       MessageBoxButtons.OK, MessageBoxIcon.Information)
   14.          Catch ICE As InvalidCastException
   15.              MessageBox.Show("Inserire un valore numerico valido!", Me.Text,
                       MessageBoxButtons.OK, MessageBoxIcon.Error)
   16.          Catch AE As ArgumentException
   17.              MessageBox.Show("Il raggio non può essere negativo o nullo!", Me.Text,
                       MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
   18.          End Try
   19.     End Sub
   20.
   21. End Class
B5. Input e Output su file


Gli Stream
Le oper azioni di input e output, in .NET come in molti altr i linguaggi, hanno come tar get uno str eam, ossia un flusso di
dati. In .NET, tale flusso viene r appr esentato da una classe astr atta, System.IO.Str eam, che espone alcuni metodi per
acceder e e manipolar e i dati ivi contenuti. Dato che si tr atta di una classe astr atta, non possiamo utilizzar la
dir ettamente, poiché, appunto, r appr esenta un concetto astr atto non istanziabile. Come già spiegato nel capitolo
r elativo, classi del gener e r appr esentano un ar chetipo per diver se altr e classi der ivate. Infatti, un flusso di dati può
esser e tante cose, e pr ovenir e da molti posti diver si:

       può tr attar si di un file, come vedr emo fr a poco; allor a la classe der ivata oppor tuna sar à FileStr eam;
       può tr attar si di dati gr ezzi pr esenti in memor ia, ed avr emo, ad esempio, Memor yStr eam;
       potr ebbe tr attar si, invece, di un flusso di dati pr oveniente dal ser ver a cui siamo collegati, e ci sar à allor a, un
       Netw or kStr eam;
       e così via, per molti diver se casistiche...

Globalmente par lando, quindi, si può associar e uno str eam al flusso di dati pr oveniente da un qualsiasi dispositivo
vir tuale o fisico o da qualunque entità astr atta all'inter no della macchina: ad esempio è possibile aver e uno str eam
associato a una stampante, a uno scanner , allo scher mo, ad un file, alla memor ia tempor anea, a qualsiasi altr a cosa.
Per ognuno di questi casi, esister à un'oppor tuna classe der ivata di Str eam studiata per adempier e a quello specifico
compito.
In questo capitolo, vedr emo cinque classi del gener e, ognuna altamente specializzata: FileStr eam, Str eamReader ,
Str eamWr iter , Binar yReader e Binar yWr iter .




FileStream
Questa classe offr e funzionalità gener iche per l'accesso a un file. Il suo costr uttor e più semplice accetta due par ametr i:
il pr imo è il per cor so del file a cui acceder e ed il secondo indica le modalità di aper tur a. Quest'ultimo par ametr o è di
tipo IO.FileMode, un enumer ator e che contiene questi campi:

       Append : apr e il file e si posiziona alla fine (in questo modo, potr emo velocemente aggiun gere dati senza
       sovr ascr iver e quelli pr ecedentemente esistenti);
       Cr eate : cr ea un nuovo file con il per cor so dato nel pr imo par ametr o; se il file esiste già, sar à sovr ascr itto;
       Cr eateNew : cr ea un nuovo file con il per cor so dato nel pr imo par ametr o del costr uttor e; se il file esiste già,
       ver r à sollevata un'eccezione;
       Open : apr e il file e si posiziona all'inizio;
       OpenOr Cr eate : apr e il file, se esiste, e si posiziona all'inizio; se non esiste, cr ea il file;
       Tr uncate : apr e il file, cancella tutto il suo contenuto, e si posiziona all'inizio.

Un ter zo par ametr o opzionale può specificar e i per messi (solo lettur a, solo scr ittur a o entr ambe), ma per or a non lo
user emo.
Pr ima di veder e un esempio del suo utilizzo, è necessar io dir e che questa classe consider a i file aper ti come file binar i.
Si par la di file binar io quando esiste una cor r ispondenza biunivoca tr a i bytes esistenti in esso e i dati letti. Questa
condizione non si ver ifica con i file di testo, in cui, ad esempio, il singolo car atter e "a capo" cor r isponde a due bytes: in
questo caso non si può par lar e di file binar i, ma è comunque possibile legger li come tali, e ciò che si otter r à sar à solo
una sequenza di numer i. Ma vedr emo meglio queste differ enze nel par agr afo successivo.
Or a, ammettendo di aver e aper to il file, sia che si voglia legger e, sia che si voglia scr iver e, sar à necessar io adottar e
un buffer, ossia un ar r ay di bytes che conter r à tempor aneamente i dati letti o scr itti. Tutti i metodi di
lettur a/scr ittur a binar i del Fr amew or k, infatti, r ichiedono come minimo tr e par ametr i:

       buffer : un ar r ay di bytes in cui por r e i dati letti o da cui pr elevar e i dati da scr iver e;
       index : indice del buffer da cui iniziar e l'oper azione;
       length : numer o di bytes da pr ocessar e.

Seguendo questa logica, avr emo la funzione Read:

 Read(buffer, index, length)


che legge length bytes dallo str eam aper to e li pone in buffer (a par tir e da index ); e, par imenti, la funzione Wr ite:

 Write(buffer, index, length)


che scr ive sullo str eam length bytes pr elevati dall'ar r ay buffer (a par tir e da index ). Ecco un esempio:

   01. Module Module1
   02.
   03.     Sub Main()
   04.         Dim File As IO.FileStream
   05.         Dim FileName As String
   06.
   07.         Console.WriteLine("Inserire il percorso di un file:")
   08.         FileName = Console.ReadLine
   09.
   10.         'IO.File.Exists(path) restituisce True se il percorso
   11.         'path indica un file esistente e False in caso contrario
   12.         If Not IO.File.Exists(FileName) Then
   13.              Console.WriteLine("Questo file non esiste!")
   14.              Console.ReadKey()
   15.              Exit Sub
   16.         End If
   17.
   18.         Console.Clear()
   19.
   20.         'Apre il file specificato, posizionandosi all'inizio
   21.         File = New IO.FileStream(FileName, IO.FileMode.Open)
   22.
   23.         Dim Buffer() As Byte
   24.         Dim Number, ReadBytes As Int32
   25.
   26.         'Chiede all'utente quanti bytes vuole leggere, e
   27.         'memorizza tale numero in Number
   28.         Console.WriteLine("Quanti bytes leggere?")
   29.         Number = CType(Console.ReadLine, Int32)
   30.         'Se Number è un numero positivo e non siamo ancora
   31.         'arrivati alla fine del file, allora legge quei bytes.
   32.         'La proprietà Position restituisce la posizione
   33.         'corrente all'interno del file (a iniziare da 0), mentre
   34.         'File.Length restituisce la lunghezza del file, in bytes.
   35.         Do While (Number > 0) And (File.Position < File.Length - 1)
   36.              'Ridimensiona il buffer
   37.              ReDim Buffer(Number - 1)
   38.              'Legge Number bytes e li mette in Buffer, a partire
   39.              'dall'inizio dell'array. Read è una funzione, e
   40.              'restituisce come risultato il numero di bytes
   41.              'effettivamente letti dallo stream.
   42.              ReadBytes = File.Read(Buffer, 0, Number)
   43.
   44.              Console.WriteLine("Bytes letti:")
   45.              For I As Int32 = 0 To ReadBytes - 1
   46.                  Console.Write("{0:000} ", Buffer(I))
   47.
Next
   48.              Console.WriteLine()
   49.
   50.              'Se abbiamo letto tanti bytes quanti ne erano stati
   51.              'chiesti, allora non siamo ancora arrivati alla
   52.              'fine del file. Richiede all'utente un numero
   53.              If ReadBytes = Number Then
   54.                   Console.WriteLine("Quanti bytes leggere?")
   55.                   Number = CType(Console.ReadLine, Int32)
   56.              End If
   57.         Loop
   58.
   59.         'Controlla se si è raggiunta la fine del file.
   60.         'Infatti, il ciclo potrebbe terminare anche se l'utente
   61.         'immettesse 0.
   62.         If File.Position >= File.Length - 1 Then
   63.              Console.WriteLine("Raggiunta fine del file!")
   64.         End If
   65.
   66.         'Chiude il file
   67.         File.Close()
   68.
   69.         Console.ReadKey()
   70.     End Sub
   71.
   72. End Module

Bisogna sempr e r icor dar si di chiuder e il flusso di dati quando si è finito di utilizzar lo. FileStr eam, e in gener ale anche
Str eam, implementa l'inter faccia IDisposable e il metodo Close non è altr o che un modo indir etto per r ichiamar e
Dispose (a cui, comunque, possiamo far e r icor so). Allo stesso modo, possiamo usar e la funzione Wr ite per scr iver e dati,
oppur e Wr iteByte per scr iver e un byte alla volta.
Come avr ete notato, la classe Str eam espone anche delle pr opr ietà in sola lettur a come CanRead, CanWr ite e CanSeek.
Infatti, non tutti i flussi di dato suppor tano tutte le oper azioni di lettur a, scr ittur a e r icer ca: un esempio può esser e il
Netw or kStr eam (che analizzer emo nella sezione dedicata al Web) associato alle r ichieste http, il quale non suppor ta le
oper azioni di r icer ca e r estituisce un er r or e se si pr ova ad utilizzar e il metodo Seek. Questo metodo ser ve per
spostar si velocemente da una par te all'altr a del flusso di dati, e accetta solo due ar gomenti:

 Seek(offset, origin)


offset è un inter o che specifica la posizione a cui r ecar si, mentr e or igin è un valor e enumer ato di tipo IO.SeekOr igin
che può assumer e tr e valor i: Begin (si r ifer isce all'inizio del file), Cur r ent (si r ifer isce alla posizione cor r ente) ed End
(si r ifer isce alla fine del file). Ad esempio:

    1.    'Si sposta alla posizione 100
    2.    File.Seek(100, IO.SeekOrigin.Begin)
    3.    'Si sposta di 250 bytes indietro rispetto alla posizione corrente
    4.    File.Seek(-250, IO.SeekOrigin.Current)
    5.    'Si sposta a 100 bytes dalla fine del file
    6.    File.Seek(-100, IO.SeekOrigin.End)

Cer to che legger e e scr iver e dati un byte alla volta non è molto comodo. Vediamo, allor a, la pr ima categor ia di file: i
file testuali.




Lettura/sc rittura di file testuali
I file testuali sono così denominati per chè contengono solo testo, ossia bytes codifcabili in una delle codifiche standar d
dei car atter i (ASCII, UTF-8, ecceter a...). Alcuni par ticolar i bytes vengono intepr etati in modi diver si, come ad esempio
la tabulazione, che viene r appr esentata con uno spazio più lungo; altr i vengono tr alasciati nella visualizzazione e
sembr ano non esister e, ad esempio il NULL ter minator , che r appr esenta la fine di una str inga, oppur e l'EOF (End Of
File); altr i ancor a vengono pr esi a gr uppi, come il car atter e a capo, che in r ealtà è for mato da una sequenza di due
bytes (Car r iage Retur n e Line Feed, r ispettivamente 13 e 10). La differ enza insita in questi tipi di file r ispetto a quelli
binar i è il fatto di non poter legger e i singoli bytes per chè non ce n'è necessità: quello che impor ta è l'infor mazione che
il testo por ta al suo inter no. La classe usata per la lettur a è Str eamReader , mentr e quella per la scr ittur a
Str eamWr iter : il costr uttor e di entr ambi accetta un unico par ametr o, ossia il per cor so del file in questione; esistono
anche altr i over loads dei costr uttor i, ma il più usato e quindi il più impor tante di tutti è quello appena citato. Ecco un
piccolo esempio di come utilizzar e tali classi in una semplice applicazione console:

   01. Module Module1
   02.     Sub Main()
   03.         Dim File As String
   04.         Dim Mode As Char
   05.
   06.         Console.WriteLine("Premere R per leggere un file, W per scriverne uno.")
   07.         'Console.ReadKey restituisce un oggetto ConsoleKeyInfo,
   08.         'al cui interno ci sono tre proprietà: Key,
   09.         'enumeratore che definisce il codice del pulsante premuto;
   10.         'KeyChar, il carattere corrispondente a quel pulsante;
   11.         'Modifier, enumeratore che definisce i modificatori attivi,
   12.         'ossia Ctrl, Shift e Alt.
   13.         'Quello che serve ora è solo KeyChar
   14.         Mode = Console.ReadKey.KeyChar
   15.         'Dato che potrebbe essere attivo il Bloc Num, ci si
   16.         'assicura che Mode contenga un carattere maiuscolo
   17.         'con la funzione statica ToUpper del tipo base Char
   18.         Mode = Char.ToUpper(Mode)
   19.         'Pulisce lo schermo
   20.         Console.Clear()
   21.
   22.         Select Case Mode
   23.              Case "R"
   24.                  Console.WriteLine("Inserire il percorso del file da leggere:")
   25.                  File = Console.ReadLine
   26.
   27.                  'Cosntrolla che il file esista
   28.                  If Not IO.File.Exists(File) Then
   29.                       'Se non esiste, visualizza un messggio ed esce
   30.                       Console.WriteLine("Il file specificato non esiste!")
   31.                       Console.ReadKey()
   32.                       Exit Sub
   33.                  End If
   34.
   35.                  Dim Reader As New IO.StreamReader(File)
   36.
   37.                  'Legge ogni singola riga del file, fintanto che non
   38.                  'si è raggiunta la fine
   39.                  Do While Not Reader.EndOfStream
   40.                       'Come Console.Readline, la funzione d'istanza
   41.                       'ReadLine restituisce una linea di testo
   42.                       'dal file
   43.                       Console.WriteLine(Reader.ReadLine)
   44.                  Loop
   45.
   46.                  'Quindi chiude il file
   47.                  Reader.Close()
   48.              Case "W"
   49.                  Console.WriteLine("Inserire il percorso del file da creare:")
   50.                  File = Console.ReadLine
   51.
   52.                  Dim Writer As New IO.StreamWriter(File)
   53.                  Dim Line As String
   54.
   55.                  Console.WriteLine("Immettere il testo del file, " & _
   56.                       "premere due volte invio per terminare")
   57.                  'Fa immettere righe di testo fino a quando
   58.                  'si termina
   59.                  Do
   60.                       Line = Console.ReadLine
   61.                       'Come Console.WriteLine, la funzione d'istanza
   62.                       'WriteLine scrive una linea di testo sul file
   63.
Writer.WriteLine(Line)
   64.                 Loop While Line <> ""
   65.
   66.                 'Chiude il file
   67.                 Writer.Close()
   68.             Case Else
   69.                 Console.WriteLine("Comando non valido!")
   70.         End Select
   71.
   72.         Console.ReadKey()
   73.     End Sub
   74. End Module

Ovviamente esistono anche i metodi Read e Wr ite, che scr ivono del testo senza mandar e a capo: inoltr e, Wr ite e
Wr iteLine hanno degli over loads che accettano anche str inghe di for mato come quelle viste nei capitoli pr ecedenti.
Come si è visto, le classi analizzate (e quelle che andr emo a veder e tr a br eve) hanno metodi molti simili a quelli di
Console: questo per chè anche la console è uno str eam, capace di input e output allo stesso tempo. Per color o che
pr ovengono dal C non sar à difficile r ichiamar e questo concetto.




Lettura/sc rittura di file binari
Come già accennato nel par agr afo pr ecedente, la distinzione tr a file binar i e testuali avviene tr amite l'inter pr etazione
dei singoli bytes. Con questo tipo di file, c'è una cor r ispondenza biunivoca tr a i bytes del file e i dati letti dal
pr ogr amma: infatti, non a caso, l'I/O viene gestito attr aver so un ar r ay di byte. Binar yWr iter e Binar yReader
espongono, oltr e alle canoniche Read e Wr ite già analizzate per FileStr eam, altr e pr ocedur e di lettur a e scr ittur a, che,
di fatto, scendono a più basso livello. Ad esempio, all'inizio della guida ho illustr ato alcuni tipi di dato basilar i,
r ipor tando anche la lor o gr andezza (in bytes). Integer occupa 4 bytes, Int16 ne occupa 2, Single he occupa 4 e così via.
Valor i di tipo base vengono quindi salvati in memor ia in notazione binar ia, r ispettando quella specifica dimensione.
Or a, esistono modi ben definiti per conver tir e un numer o in base 10 in una sequenza di bit facilmente manipolabile
dall'elabor ator e: mi r ifer isco, ad esempio, alla notazione in complemento a 2 per gli inter i e al for mato in vir gola
mobile per i r eali. Potete documentar vi su queste modalità di r appr esentazione dell'infor mazione altr ove: in questo
momento ci inter essa saper e che i dati sono "pensati" dal calcolator e in manier a diver sa da come li concepiamo noi.
Binar yWr iter e Binar yReader sono classi appositamente cr eate per far da tr amite tr a ciò che capiamo noi e ciò che
capisce il computer . Pr opr io per chè sono dei "mezzi", il lor o costr uttor e deve specificar e lo str eam (già aper to) su cui
lavor ar e. Ecco un esempio:

   01. Module Module1
   02.
   03.     Sub Main()
   04.         'Apre il file "prova.dat", creandolo o sovrascrivendolo
   05.         Dim File As New IO.FileStream("prova.dat", IO.FileMode.Create)
   06.         'Writer è lo strumento che ci permette di scrivere
   07.         'sullo stream File con codifica binaria
   08.         Dim Writer As New IO.BinaryWriter(File)
   09.         Dim Number As Int32
   10.
   11.         Console.WriteLine("Inserisci 10 numeri da scrivere sul file:")
   12.         For I As Int32 = 1 To 10
   13.              Console.Write("{0}: ", I)
   14.              Number = CType(Console.ReadLine, Int32)
   15.              Writer.Write(Number)
   16.         Next
   17.         Writer.Close()
   18.
   19.         Console.ReadKey()
   20.     End Sub
   21.
   22. End Module

Io ho inser ito questi numer i: -10 -5 0 1 20 8000 19001 -345 90 22. Pr ovando ad apr ir e il file con un editor di testo
vedr ete solo car atter i str ani, in quanto questo non è un file testuale. Apr endolo, invece, con un editor esadecimale,
otter r ete questo:

 f6 ff ff ff fb ff ff ff 00 00 00 00 01 00 00 00
 14 00 00 00 40 lf 00 00 39 4a 00 00 a7 fe ff ff
 5a 00 00 00 16 00 00 00


Ogni gr uppetto di quattr o bytes r appr esenta un numer o inter o codificato in binar io. Potr emmo far e la stessa cosa con
Single, Double, Date, Boolean, Str ing e altr i tipi base per veder e cosa succede.
Binar yWr iter e Binar yReader sono molto utili quando bisogna legger e dati in codifica binar ia, ad esempio per molti
famosi for mati di file, come mp3, w av (vedi sezione FFS), zip, mpg, ecceter a...




Esempio: steganografia su immagini
La steganogr afia è l'ar te di nasconder e del testo all'inter no di un'immagine. Per i più cur iosi, mi avventur er ò nella
scr ittur a di un semplicissimo pr ogr amma di steganogr afia su immagini, nascondendo del testo al lor o inter no.
Per pr ima cosa, si costr uisca l'inter faccia gr afica, con questi contr olli:

       Una Label, Label1, Tex t = "Intr odur r e il per cor so di un'immagine:"
       Una Tex tBox , tx tPath, cone AutoCompleteMode = Suggest e AutoCompleteSour ce = FileSystem. In questo modo, la
       tex tbox sugger ir à il nome di file e car telle esistenti mentr e state digitando, r endendo più semplice
       l'indtr oduzione del per cor so;
       Una Tex tBox , tx tTex t, Scr ollBar s = Both, MultiLine = Tr ue
       Un Button, btnHide, Tex t = "Nascondi"
       Un Button, btnRead, Tex t = "Leggi"




Ed ecco il codice ampiamente commentato:

   01. Public Class Form1
   02.
   03.     Private Sub btnHide_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
              Handles btnHide.Click
   04.          If Not IO.File.Exists(txtPath.Text) Then
   05.              MessageBox.Show("File inesistente!", Me.Text, MessageBoxButtons.OK,
                       MessageBoxIcon.Error)
   06.              Exit Sub
   07.          End If
   08.
   09.          If IO.Path.GetExtension(txtPath.Text) <> ".jpg" Then
   10.              MessageBox.Show("Il file deve essere in formato JPEG!", Me.Text,
                       MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
   11.
Exit Sub
   12.              End If
   13.
   14.              Dim File As New IO.FileStream(txtPath.Text, IO.FileMode.Open)
   15.              'Converte il testo digitato in una sequenza di bytes,
   16.              'secondo gli standard della codifica UTF8
   17.              Dim TextBytes() As Byte = _
   18.                  System.Text.Encoding.UTF8.GetBytes(txtText.Text)
   19.
   20.              'Va alla fine del file
   21.              File.Seek(0, IO.SeekOrigin.End)
   22.              'Scrive i bytes
   23.              File.Write(TextBytes, 0, TextBytes.Length)
   24.              File.Close()
   25.
   26.            MessageBox.Show("Testo nascosto con successo!", Me.Text, MessageBoxButtons.OK,
                     MessageBoxIcon.Information)
   27.        End Sub
   28.
   29.        Private Sub btnRead_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
                 Handles btnRead.Click
   30.             If Not IO.File.Exists(txtPath.Text) Then
   31.                 MessageBox.Show("File inesistente!", Me.Text, MessageBoxButtons.OK,
                          MessageBoxIcon.Error)
   32.                 Exit Sub
   33.             End If
   34.
   35.              If IO.Path.GetExtension(txtPath.Text) <> ".jpg" Then
   36.                  MessageBox.Show("Il file deve essere in formato JPEG!", Me.Text,
                           MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
   37.                  Exit Sub
   38.              End If
   39.
   40.         Dim File As New IO.FileStream(txtPath.Text, IO.FileMode.Open)
   41.         Dim TextBytes() As Byte
   42.         Dim B1, B2 As Byte
   43.
   44.         'Legge un byte
   45.         B1 = File.ReadByte()
   46.         Do
   47.             'Legge un altro byte
   48.             B2 = File.ReadByte()
   49.             'Se i bytes formano la sequenza FF D9, si ferma.
   50.             'In Visual Basic, in numeri esadecimali si scrivono
   51.             'facendoli precedere da "&H"
   52.             If B1 = &HFF And B2 = &HD9 Then
   53.                 Exit Do
   54.             End If
   55.             'Passa il valore di B2 in B1
   56.             B1 = B2
   57.         Loop While (File.Position < File.Length - 1)
   58.
   59.         ReDim TextBytes(File.Length - File.Position - 1)
   60.         'Legge ciò che rimane dopo FF D9
   61.         File.Read(TextBytes, 0, TextBytes.Length)
   62.         File.Close()
   63.
   64.         txtText.Text = System.Text.Encoding.UTF8.GetString(TextBytes)
   65.     End Sub
   66. End Class

Il testo accodato può esser e r ilevato facilmente con un Hex Editor , per questo lo si dovr ebbe cr iptar e con una
passw or d: per ulter ior i infor mazioni sulla cr iptazione in .NET, veder e capitolo r ekativo.
B6. ListBox e ComboBox

Questi contr olli sono liste con stile visuale pr opr io in gr ado di contener e elementi. La gestione di tali elementi è molto
simile a quella delle List gener ic o degli Ar r ayList. L'unica differ enza sta nel fatto che in questo caso, tutte le modifiche
vengono r ese visibili sull'inter faccia e influiscono, quindi, su ciò che l'utente può veder e. Una volta aggiunte alla
w indow s for m, il lor o aspetto sar à simile a questo:




                                                            ListBo x




                                                           Co m bo Box


Le pr opr ietà più inter essanti sono:

       Solo per ListBox :
               ColumnWidth : indica la lar ghezza delle colonne in una listbox in cui MultiColumn = Tr ue. Lasciar e 0 per il
               valor e di default
               Hor izontalEx tent : indica di quanti pix el è possibile scor r er e la listbox in or izzontale, se la scr ollbar
               or izzontale è attiva
               Hor izontalScr ollbar : deter mina se attivar e la scr ollbar or izzontale. Di solito, questa pr opr ietà viene
               impostata a Tr ue quando la listbox dispone di più colonne
               MultiColumn : deter mina se la listbox è a più colonne. In questa modalità, una volta ter minata l'altezza
               della lista, gli elementi vengono posizionati di lato anzichè sotto, ed è quindi possibile visualizzar li
               spostandosi a destr a o a sinistr a. Un esempio visuale:
ListBo x M ultiCo lum n


       Scr ollAlw aysVisible : deter mina se le scr ollbar vengono visualizzate sempr e, indipendentemente dal
       numer o di elementi pr esenti. Infatti quando questa pr opr ietà è disabilitata, se gli elementi sono pochi e
       possono esser e posizionati nell'ar ea della lista senza nasconder ne nessuno, non viene visualizzata la
       scr ollbar , che appar e quando gli elementi cominciano a diventar e tr oppi. Con questa pr opr ietà attiva,
       essa è sempr e visibile e, se inutilizzata, si disabilita automaticamente
       SelectionMode : pr opr ietà enumer ata che deter mina in quale modo sia possibile selezionar e gli elementi.
       Può assumer e quattr o valor i: None (non è possibile selezionar e niente), One (un solo elemento alla volta),
       MultiSimple (più elementi selezionabili con un click), MultiEx tended (più elementi, selezionabili solo
       tenendo pr emuto Ctr l e spostando il mouse sopr a di essi)
Solo per ComboBox :
       AutoComplete... : tutte le pr opr ietà il cui nome inizia per "AutoComplete" sono uguali a quelle citate nella
       lezione pr ecedente
       Dr opDow nHeight : deter mina l'altezza, in pix el, del menù a discesa
       Dr opDow nStyle : deter mina lo stile del menù a discesa. Può assumer e tr e valor i: Simple (il menù a discesa
       è sempr e visibile, e può esser e assimilato a una listbox ), Dr opDow n (stile nor male come nell'immagine di
       esempio pr oposta a inizio capitolo, ma è possibile modificar e il testo dell'elemento selezionato scr ivendo
       entr o la casella), Dr opDow nList (stile nor male, non è possibile modificar e l'elemento selezionato in alcun
       modo, se non selezionandone un altr o). Questa è un'immagine di una combobox con Dr opDow nStyle =
       Simple:




                                             Co m bo Box Sim ple Dr o pDo w n


       FlatStyle : lo stile visuale della ComboBox . Può assumer e quattr o valor i: Flat o Popup (la combobox è
       gr igia e schiacciata, senza contor ni 3D), System o Pr ofessional (la combobox è azzur r a e r ilevata, con
       contor ni 3D)
       Max Dr opDow nItems : il numer o massimo di elementi visualizzabili nel menù a discesa
       Max Length : deter mina il massimo numer o di car atter i di testo che possono esser e inser iti come input
       nella casella della combobox . Questa pr opr ietà ha senso solo se Dr opDow nStyle non è impostata su
       Dr opDow nList, poichè tale stile impedisce di modificar e il contenuto della combobox tr amite tastier a,
       come già detto
Per entr ambe le liste:
       Dr aw Mode : deter mina la modalità con cui ogni elemento viene disegnato. Può assumer e tr e valor i:
       Nor mal, Ow ner Dr aw Fix ed e Ow ner Dr aw Var iable. Il pr imo è quello di default; il secondo ed il ter zo
       specificano che i contr olli devono esser e disegnati da una speciale pr ocedur a definita dal pr ogr ammator e
       nell'evento Dr aw Item. Per ulter ior i infor mazioni su questo pr ocedimento, veder e l'ar ticolo Fo nt e
diseg ni nelle liste nella sezione Appunti.
        For matStr ing : dato che queste liste possono contener e anche numer i e date (e altr i oggetti, ma non è
        consigliabile aggiunger e tipi diver si da quelli base), la pr opr ietà For matStr ing indica come tali valor i
        debbano esser e visualizzati. Cliccando sul pulsante con i tr e puntini nella finestr a delle pr opr ietà su
        questa voce, appar ir à una finestr a di dialogo con i seguenti for mati standar d: No For matting, Numer ic,
        DateTime e Scientific.
        For matEnabled : deter mina se è abilitata la for mattazione degli elementi tr amite For matStr ing
        Integr alHeight : quando attiva, questa pr opr ietà for za la lista ad assumer e un valor e di altezza
        (Size.Height) che sia un multiplo di ItemHeight, in modo tale che gli elementi siano sempr e visibili
        inter amente. Se disattivata, gli elementi possono anche venir e "tagliati" fuor i dalla lista. Un esempio:




                                              Lista co n Integ r alHe ig ht = False


        ItemHeight : altezza, in pix el, di un elemento
        Items : collezione a tipizzazione debole di tutti gli elementi. Gode di tutti i metodi consueti delle liste,
        quali Add, Remove, Index Of, Inser t, ecceter a...
        Sor ted : indica se gli elementi devono esser e or dinati alfabeticamente


Detto ciò, è possibile pr oceder e con un semplice esempio. Il pr ogr amma che segue per mette di aggiunger e un
qualsiasi testo ad una lista. Pr ima di iniziar e a scr iver e il codice, bisogna includer e nella w indow s for m questi
contr olli:


        Una listbox , di nome lstItems
        Un pulsante, di nome cmdAdd, con Tex t = "Aggiungi"
        Un pulsante, di nome cmdRemove, con Tex t = "Rimuovi"
Il codice:

   01. Public Class Form1
   02.     Private Sub cmdAdd_Click(ByVal sender As Object, _
   03.         ByVal e As EventArgs) Handles cmdAdd.Click
   04.         Dim S As String
   05.
   06.         'Inputbox(
   07.         '   ByVal Prompt As Object,
   08.         '   ByVal Title As String,
   09.         '   ByVal DefaultResponse As String)
   10.         'Visualizza una finestra con una label esplicativa
   11.         'il cui testo è racchiuso in Prompt, con un titolo
   12.         'Title e una textbox con un testo di default
   13.         'DeafultResponse: una volta che l'utente ha inserito
   14.         'la stringa nella textbox e cliccato OK, la funzione
   15.         'restituisce la stringa immessa
   16.         S = InputBox("Inserisci una stringa:", "Inserimento stringa", _
   17.         "[Stringa]")
   18.
   19.         'Aggiunge la stringa alla lista
   20.         lstItems.Items.Add(S)
   21.     End Sub
   22.
   23.
Private Sub cmdRemove_Click(ByVal sender As Object, _
   24.         ByVal e As EventArgs) Handles cmdRemove.Click
   25.         'Se è selezionato un elemento...
   26.         If lstItems.SelectedIndex >= 0 Then
   27.             'Lo elimina
   28.             lstItems.Items.RemoveAt(lstItems.SelectedIndex)
   29.         End If
   30.     End Sub
   31. End Class




Non solo stringhe
Nell'esempio pr ecedente, ho mostr ato che è possibile aggiunger e agli elementi della listbox delle str inghe, ed
esse ver r anno visualizzate come testo sull'inter faccia del contr ollo. Tuttavia, la pr opr ietà Items è di tipo
ObjectCollection, quindi può contener e un qualsiasi tipo di oggetto e non necessar iamente solo str inghe. Quello
che ci pr eoccupa, in questo caso, è ciò che viene mostr ato all'utente qualor a noi inser issimo un oggetto nella
listbox : quale testo sar à visualizzato per l'elemento? Ecco un esempio (un for m con una listbox e un pulsante):

   01. Class Form1
   02.
   03.     Class Item
   04.          Private Shared IDCounter As Int32 = 0
   05.
   06.          Private _ID As Int32
   07.          Private _Description As String
   08.
   09.          Public ReadOnly Property ID() As Int32
   10.              Get
   11.                  Return _ID
   12.              End Get
   13.          End Property
   14.
   15.          Public Property Description() As String
   16.              Get
   17.                  Return _Description
   18.              End Get
   19.              Set(ByVal value As String)
   20.                  _Description = value
   21.              End Set
   22.          End Property
   23.
   24.          Sub New()
   25.              _ID = IDCounter
   26.              IDCounter += 1
   27.          End Sub
   28.
   29.     End Class
   30.
   31.     Private Sub btnDoSomething_Click(ByVal sender As Object, ByVal e As EventArgs)
              Handles btnDoSomething.Click
   32.          lstItems.Items.Add(New Item() With {.Description = "Asus Eee PC 900"})
   33.          lstItems.Items.Add(New Item() With {.Description = "Hp Pavillion Dv6000"})
   34.     End Sub
   35.
   36. End Class

Una volta pr emuto btnDoSomething, nella lista ver r anno aggiunti due oggetti, tuttavia la GUI della listbox
visualizzer à questi due elementi:

 WindowsApplication4.Form1+Item
 WindowsApplication4.Form1+Item


Questo nel mio caso, poiché il pr ogetto (e quindi il namespace pr incipale) si chiama Window sApplication4. Da ciò
si può capir e che, in assenza d'altr o, la listbox tenta di conver tir e l'oggetto in una str inga, ossia un dato
intellegibile all'uomo: l'unico modo per poter avviar e questa conver sione consiste nell'utilizzar e il metodo
ToStr ing, il quale, tuttavia, non è stato r idefinito dalla classe Item e pr ovoca l'uso del suo omonimo der ivante
dalla classe base Object. Quest'ultimo, infatti, r estituisce il tipo dell'oggetto, che in questo caso è pr opr io
Window sApplication4.For m+Item. Per modificar e il compor tamento del contr ollo, dobbiamo aggiunger e alla
classe un metodo ToStr ing, ad esempio così:

    1. Class Item
    2.     '...
    3.
    4.     Public Overrides Function ToString() As String
    5.          Return Me.Description
    6.     End Function
    7. End Class

Avviando di nuovo l'applicazione, gli elementi visualizzati sulla lista sar anno:

 Asus Eee PC 900
 Hp Pavillion Dv6000


Esiste, tuttavia, un altr o modo per ottener e lo stesso r isultato senza dover r idefinir e il metodo ToStr ing.
Questa seconda alter nativa si dimostr a par ticolar mente utile quando non possiamo acceder e o modificar e il
codice della classe di cui stiamo usando istanze: ad esempio per chè si tr atta di una classe definita in un assembly
diver so. Possiamo specificar e nella pr opr ietà ListBox .DisplayMember il nome della pr opr ietà che deve ser vir e a
visualizzar e l'elemento nella lista. Nel nostr o caso, vogliamo visualizzar e la descr izione, quindi user emo questo
codice:

    1. lstItems.DisplayMember = "Description"
    2. lstItems.Items.Add(New Item() With {.Description = "Asus Eee PC 900"})
    3. lstItems.Items.Add(New Item() With {.Description = "Hp Pavillion Dv6000"})

Ed otter r emo lo stesso r isultato.
Par allelamente, c'è anche la pr opr ietà ValueMember , che per mette di specificar e quale pr opr ietà dell'oggetto
deve esser e r estituita quando si r ichiede il valor e di un elemento selezionato mediante la pr opr ietà
SelectedValue.
B7. CheckBox e RadioButton


Mentr e ListBox e ComboBox mir avano a r ender e visuale un insieme di elementi, questi due contr olli r appr esentano
una valor e Booleano: infatti possono esser e spuntati oppur e no.




Chec kBox
La CheckBox è la classica casella di spunta, che si può segnar e con un segno di spunta (tick). Le pr opr ietà
car atter istiche sono poche:

       Appear ance : pr opr ietà enumer ata che deter mina come la checkbox viene visualizzata. Il pr imo valor e, Nor mal,
       specifica che deve esser ci una casellina di spunta con il testo a fianco; il secondo valor e, Button, specifica che
       deve esser e r ender izzata come un contr ollo Button. In questo secondo caso, se Checked è Tr ue il pulsante appar e
       pr emuto, altr imenti no
       AutoCheck : deter mina se la checkbox cambia automaticamente stato (ossia da spuntata a non spuntata) quando
       viene cliccata. Se questa pr opr ietà è False, l'unico modo per cambiar e la spunta è tr amite codice
       AutoEllipsis : se Appear ance = Button, questa pr opr ietà deter mina se il contr ollo si debba automaticamente
       r idimensionar e sulla base del pr opr io testo
       CheckAlign : se Appear ance = Nor mal, deter mina in quale posizione della checkbox si tr ovi la casellina di spunta
       Checked : indica se la checkbox è spuntata oppur e no
       CheckState : per le checkbox a tr e stati, indica lo stato cor r ente
       FlatStyle : deter mina lo stile visuale del testo attr aver so un enumer ator e a quattr o valor i, come nelle
       combobox
       Tex tAlign : allineamento del testo
       Tex tImageRelation : deter mina la r elazione testo-immagine, qualor a la pr opr ietà Image sia impostata. Può
       assumer e diver si valor i che specificano se il testo sia sotto, sopr a, a destr a o a sinistr a dell'immagine
       Thr eeState : deter mina se la checkbox suppor ta i tr e stati. In questo caso, le combinazioni possibili sono tr e,
       ossia: spuntato, senza spunta e indeter minato. Può esser e utile per offr ir e una gamma di scelta più ampia o per
       implementar e visualmente la logica booleana a tr e valor i

Ecco un esempio di tutte le possibili combinazioni di checkbox :




In definitiva, la CheckBox r ende visuale il legame Or tr a più condizioni.




RadioButton
A differ enza di CheckBox , RadioButton può assumer e solo due valor i, che non sono sempr e accessibili. La spiegazione di
questo sta nel fatto che solo un RadioButton può esser e spuntato allo stesso tempo in un dato contenitor e. Ad esempio,
in una finestr a che contenga tr e di questi contr olli, spuntando il pr imo, il secondo ed il ter zo sar anno depennati;
spuntando il secondo lo sar anno il pr imo ed il ter zo e così via. Tale meccanismo è del tutto automatico e aiuta
moltissimo nel caso si debbano pr opor r e all'utente scelte non sovr apponibili.
Gode di tutte le pr opr ietà di CheckBox , tr anne ovviamente Thr eeState e CheckState, e r appr esenta visualmente il
legame Xor tr a più condizioni.
GroupBox
Par lando di contenitor i, non si può non far e menzione al Gr oupBox . Tr a tutti i contenitor i disponibili, Gr oupBox è il più
semplice dotato di inter faccia gr afica pr opr ia. La sua funzione consiste unicamente nel r aggr uppar e in uno spazio solo
più contr olli uniti da un qualche legame logico, ad esempio tutti quelli iner enti alla for mattazione del testo. Oltr e a
r ender e la str uttur a della finestr a più or dinata, dà un tocco di stile all'applicazione e, cosa più impor tante, può
condizionar e lo stato di tutti i suoi membr i (o contr olli figli). Dato che gode solamente delle pr opr ietà comuni a tutte le
classi der ivate da Contr ol, la modifica di una di esse si r iper cuoter à su tutti i contr olli in esso contenuti. Di solito si
sfr utta questa peculiar ità per disabilitar e o r ender e invisibile un gr uppo di elementi.
L'inter faccia si pr esenta in questo modo:
B8. NumericUpDown e DomainUpDown


Numeric UpDow n
Questo contr ollo tor na utile quando si vuole pr opor r e all'utente una scelta di un numer o, inter o o decimale, compr eso
tr a un minimo e un massimo. Ad esempio, il semplice pr ogr amma che andr ò a illustr ar e in questo capitolo chiede di
indovinar e un numer o casuale da 0 a 100 gener ato dal computer . Con l'uso di una tex tbox , l'utente potr ebbe
commetter e un er r or e di battitur a e inser ir e in input car atter i non validi, mandando così in cr ash il pr ogr amma: la
soluzione potr ebbe esser e usar e un Tr y, ma si spr echer ebbe spazio, o un contr ollo Masked Tex tBox , ma in altr i casi
potr ebbe r isultar e limitativo o pedante, dato che r ichiede un pr eciso numer o di car atter i immessi. Usando invece una
combobox o una listbox si dovr ebber o aggiunger e manualmente tutti i numer i con un for , spr ecando spazio nel codice.
La soluzione ideale sar ebbe far e uso di Numer icUpDow n. Le pr opr ietà car atter istiche:

       DecimalPlaces : i posti decimali dopo la vir gola. Se impostata a 0, sar à possibile immetter e solo numer i inter i
       Hex adecimal : deter mina se visualizzar e il numer o in notazione esadecimale (solo per numer i inter i positivi)
       Incr ement : il fattor e di incr emento/decr emento automaticamente aggiunto/sottr atto quando l'utente clicca
       sulle fr ecce del contr ollo
       Inter ceptAr r ow Key : deter mina se il contr ollo debba inter cettar e e inter pr etar e la pr essione delle fr ecce
       dir ezionali su/giù da testier a
       Max imum : massimo valor e numer ico
       Minimum : minimo valor e numer ico
       ThousandSepar ator : indica se visualizzar e il separ ator e delle migliaia
       Value : il valor e indicato
       UpDow nAlign : la posizione delle fr ecce sul contr ollo

Dopo aver posizionato questi contr olli:

       Una Label Label1, Tex t = "Clicca Gener a per gener ar e un numer o casuale, quindi pr ova a indovinar e!"
       Un pulsante cmdGener ate, Tex t = "Gener a"
       Un pulsante cmdTr y, Tex t = "Pr ova"
       Un Numer icUpDow n nudValue, con le pr opr ietà standar d
       Una Label lblNumber , Tex t = "***", Font = Micr osoft Sans Ser if Gr assetto 16pt, AutoSize = False, Tex tAlign =
       MiddleCenter

Disponeteli in modo simile a questo:




Ed ecco il codice:

   01. Class Form1
   02.     'Il numero da indovinare
   03.     Private Number As Byte
   04.     'Determina se l'utente ha già dato la sua risposta
   05.     Private Tried As Boolean
   06.     'Crea un nuovo oggetto Random in grado di generare numeri
   07.     'casuali
   08.     Dim Rnd As New Random()
   09.
   10.     Private Sub cmdGenerate_Click(ByVal sender As System.Object, _
   11.         ByVal e As System.EventArgs) Handles cmdGenerate.Click
   12.         'Genera un numero aleatorio tra 0 e 99 e lo deposita in
   13.
'Number
   14.            Number = Rnd.Next(0, 100)
   15.            'Imposta Tried su False
   16.            Tried = False
   17.            'Nasconde la soluzione
   18.            lblNumber.Text = "***"
   19.        End Sub
   20.
   21.        Private Sub cmdTry_Click(ByVal sender As System.Object, _
   22.            ByVal e As System.EventArgs) Handles cmdTry.Click
   23.            'Se si è già provato, esce dalla procedura
   24.            If Tried Then
   25.                MessageBox.Show("Hai già fatto un tentativo! Premi " & _
   26.                "Genera e prova con un altro numero!", "Numeri a caso", _
   27.                MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
   28.                Exit Sub
   29.            End If
   30.
   31.              'Se NumericUpDown corrisponde al numero generato,
   32.              'l'utente vince
   33.              If nudValue.Value = Number Then
   34.                   MessageBox.Show("Complimenti, hai vinto!", "Numeri a caso", _
   35.                   MessageBoxButtons.OK, MessageBoxIcon.Information)
   36.              Else
   37.                   MessageBox.Show("Risposta sbagliata!", "Numeri a caso", _
   38.                   MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
   39.              End If
   40.
   41.            'Ormai l'utente ha fatto la sua scelta
   42.            Tried = True
   43.            'Fa vedere la soluzione
   44.            lblNumber.Text = Number
   45.        End Sub
   46. End    Class




DomainUpDow n
Questo contr ollo è molto simile come stile gr afico a quello appena analizzato solo che, anzichè visualizzar e numer i in
successione, visualizza semplici elementi testuali come le liste dei capitoli pr ecedenti. È una specie di incr ocio fr a
questi tipi di contr ollo: gode delle pr opr ietà Minimum e Max imum, ma anche della pr opr ietà Items, che stabilisce la
lista or dinata di elementi da cui pr elevar e le str inghe.
B9. PictureBox e ProgressBar


Pic tureBox
La Pictur eBox è uno di quei contr olli visibili solamente nel designer , poichè i suoi contor ni, di default, sono invisibili.
L'unica car atter istica che la r ende visibile a r untime è la sua pr opr ietà fondamentale, Image. Infatti, questo contr ollo
può contener e un'immagine: di solito viene usata per posizionar e loghi, banner o scr itte all'inter no dell'inter faccia di un
pr ogr amma. Le pr opr ietà più impor tanti sono:

       Er r or Image : l'immagine visualizzata qualor a non sia possibile car icar e un'immagine con la pr opr ietà Image
       Image : l'immagine visualizzata
       InitialImage : l'immagine visualizzata all'inizio, pr ima che sia impostata qualsiasi altr a immagine con la
       pr opr ietà Image
       SizeMode : modalità di r idimensionamento dell'immagine. Può assumer e cinque valor i: Nor mal (l'immagine
       r imane delle dimensioni nor mali, e ignor a ogni r idimensionamento della pictur ebox : per questo può anche
       venir e tagliata), Str etchImage (l'immagine si r idimensiona a seconda della pictur ebox , assumendone le stesse
       dimensioni), AutoSize (la pictur ebox         si r idimensiona sulla base dell'immagine contenuta), Center Image
       (l'immagine viene sempr e posta al centr o della pictur ebox , ma mantiene le pr opr ie dimensioni iniziali), Zoom
       (l'immagine si r idimensiona sulla base della pictur ebox , ma mantiene sempr e lo stesso r appor to tr a lar ghezza e
       altezza)

Er r or Image, Image e InitialImage sono pr opr ietà di tipo Image: quest'ultima è una classe astr atta e quindi non esiste
mai ver amente, anche se espone comunque dei metodi statici per il car icamento delle immagini. La classe che
r appr esenta ver amente e mater ialmente l'immagine è System.Dr aw ing.Bitmap, o solo Bitmap per gli amici.
Nonostante il nome sugger isca diver samente, essa fa da w r apper a un numer o elevato di for mati di immagini, tr a cui
bmp, gif, jpg, png, ex if, emf, tiff e w mf. In questo capitolo user ò tale classe in modo molto par ticolar e, quindi è meglio
pr ima analizzar ne i membr i:

       Classe astr atta Image
              Fr omFile(File) : car ica un'immagine da File e ne r estituisce un'istanza
              Fr omStr eam(Str eam) : car ica un'immagine dallo str eam Str eam e ne r estituisce un'istanza (per ulter ior i
              infor mazioni sugli str eam, veder e capitolo 56)
              Fr omHbitmap : car ica un'immagine a par tir e da un puntator e che punta al suo indir izzo in memor ia
              Hor izontalResolution : r isoluzione sull'asse x , in pix els al pollice (=2.54cm)
              Pix elsFor mat : r estituisce il for mato dell'immagine, sottofor ma di enumer ator e
              Raw For mat : r estituisce il for mato dell'immagine, in un oggetto ImageFor mat
              RotateFlip(F) : r uota e/o inver te l'immagine secondo il for mato F, esposto da un enumer ator e codificato
              a bit
              Save(File) : salva l'immagine sul file File: l'estensione del file influenzer à il metodo di scr ittur a
              dell'immagine
              Size : dimensione dell'immagine
              Ver ticalResolution : r isoluzione sull'asse y, in pix els al pollice
       Classe der ivata Bitmap
              GetPix el(X, Y) : r estituisce il color e del pix el alle coor dinate (X, Y), r ifer ite al mar gine super ior e sinistr o
              MakeTr anspar ent(C) : r ende il color e C tr aspar ente su tutta l'immagine
              SetPix el(X, Y, C) : imposta il color e del pix el alle coor dinate (X, Y) a C
SetResolution(x R, yR) : imposta la r isoluzione or izzontale su x R e quella ver ticale su yR, entr ambe
                  misur ate in punti al pollice




ProgressBar
La Pr ogr essBar è la classica bar r a di car icamento, usata per visualizzar e sull'inter faccia lo stato di un'oper azione. Le
pr opr ietà pr incipali sono poche:

        Max imum : il valor e massimo r appr esentabile dal contr ollo
        Minimum : il valor e minimo r appr esentabile dal contr ollo
        Step : valor e che definisce il valor e di incr emento quando viene r ichiamata il metodo Per for mStep
        Style : pr opr ietà enumer ata che indica lo stile della bar r a. Può assumer e tr a valor i: Block (a blocchi), Continuos
        (i blocchi possono venir e tagliati, a seconda delle per centuale) e Mar quee (un blocchetto che si muove da sinistr a
        a destr a, che r appr esenta quindi un'oper azione in cor so della quale non si sa lo stato)
        Value : il valor e r appr esentato




Esempio: Bianc o e nero
L'esempio di questa lezione è un pr ogr amma capace di car icar e un'immagine, conver tir la in bianco e ner o, e poi
r isalvar la sullo stesso o su un altr o file. I contr olli da usar e sono:

        Una Pictur eBox , imgPr eview , ancor ata a tutti i bor di, con SizeMode = Str ecthImage
        Un Button, cmdLoad, Tex t = "Car ica", Anchor = Left Or Bottom
        Un Button, cmdSave, Tex t = "Salva", Anchor = Bottom
        Un Button, cmdConver t, Tex t = "Conver ti", Anchor = Right Or Bottom
        Una Pr ogr essBar , pr gConver t, Style = Continuos

Disposti come in figur a:




Ecco il codice:

   01. Class Form1
   02.     'Funzione che converte un colore in scala di grigio
   03.     Private Function ToGreyScale(ByVal C As Color) As Color
   04.         'Per convertire un colore in scala di grigio è sufficiente
   05.         'prendere le sue componenti di rosso, verde e blu (red,
   06.         'green e blue), farne la media aritmetica e quindi
   07.         'assegnare tale valore alle nuove coordinate RGB del
   08.         'colore risultante
   09.
   10.         'Ottiene le componenti (coordinate RGB)
   11.         Dim Red As Int32 = C.R
   12.         Dim Green As Int32 = C.G
   13.         Dim Blue As Int32 = C.B
   14.         'Fa la media
   15.         Dim Grey As Int32 = (Red + Green + Blue) / 3
   16.
   17.         'Quindi crea un nuovo colore, mettendo tutte le
   18.         'componenti uguali alla media ottenuta
   19.         Return Color.FromArgb(Grey, Grey, Grey)
   20.     End Function
   21.
   22.     Private Sub cmdLoad_Click(ByVal sender As System.Object, _
   23.         ByVal e As System.EventArgs) Handles cmdLoad.Click
   24.         'Per ulteriori informazioni sui controlli OpenFileDialog e
   25.
'SaveFileDialog vedere capitolo relativo
26.           Dim Open As New OpenFileDialog
27.           Open.Filter = "File immagine|*.jpg;*.jpeg;*.gif;*.png;*.bmp;" & _
28.               "*.tif;*.tiff;*.emf;*.exif;*.wmf"
29.
30.           If Open.ShowDialog = Windows.Forms.DialogResult.OK Then
31.               'Apre l'immagine, caricandola dal file selezionato
32.               'nella finestra di dialogo tramite la funzione
33.               'statica FromFile
34.               imgPreview.Image = Image.FromFile(Open.FileName)
35.           End If
36.       End Sub
37.
38.       Private Sub cmdSave_Click(ByVal sender As System.Object, _
39.           ByVal e As System.EventArgs) Handles cmdSave.Click
40.           'Se c'è un'immagine da salvare, la salva
41.           If imgPreview.Image IsNot Nothing Then
42.               Dim Save As New SaveFileDialog
43.               Save.Filter = "File Jpeg|*.jpeg;*.jpg|File Bitmap|*.bmp|" & _
44.                    "File Png|*.png|File Gif|*.gif|File Tif|*.tif;" & _
45.                    "*.tiff|File Wmf|*.wmf|File Emf|*.emf"
46.
47.               If Save.ShowDialog = Windows.Forms.DialogResult.OK Then
48.                   'Dato che la proprietà Image è di tipo Image, usa
49.                   'il metodo statico Save per salvare l'immagine
50.                   imgPreview.Image.Save(Save.FileName)
51.               End If
52.           End If
53.       End Sub
54.
55.       Private Sub cmdConvert_Click(ByVal sender As System.Object, _
56.           ByVal e As System.EventArgs) Handles cmdConvert.Click
57.           'Prima si converte l'immagine in Bitmap, dato che Image
58.           'è una classe astratta
59.           Dim Image As Bitmap = imgPreview.Image
60.           'Variabile ausiliaria per i calcoli
61.           Dim TempColor As Color
62.
63.           'Attenzione!
64.           'Alcuni formati non supportano SetPixel, come il formato
65.           'Gif. Controllare di passare immagini di formato adeguato
66.
67.           'Itera su ogni pixel, e lo cambia di colore
68.           'Scorre le righe di pixel una alla volta
69.           For X As Int32 = 0 To Image.Width - 1
70.                'Quindi ogni pixel nella riga
71.                For Y As Int32 = 0 To Image.Height - 1
72.                     'Converte il colore
73.                     TempColor = Image.GetPixel(X, Y)
74.                     TempColor = ToGreyScale(TempColor)
75.                     Image.SetPixel(X, Y, TempColor)
76.                Next
77.                'Imposta il valore della progressbar su una percentuale
78.                'che esprime il numero di righe analizzate
79.                prgConvert.Value = X * 100 / Image.Width
80.                'Evita di bloccare il programma. Per ulteriori
81.                'informazioni su Application e il namespace My,
82.                'vedere capitolo relativo
83.                Application.DoEvents()
84.           Next
85.
86.           'Reimposta l'immagine finale
87.           imgPreview.Image = Image
88.       End Sub
89. End   Class
B10. Un semplice editor di testi


Per r ealizzar e un editor di testi bisogna pr ima di tutto saper e come per metter e all'utente di sceglier e quale file
apr ir e e in quale file salvar e ciò che ver r à scr itto. Queste semplici inter azioni vengono amministr ate da due contr olli:
OpenFileDialog e SaveFileDialog.
In questo br eve capitolo esemplificher ò il caso di un semplicissimo editor di testi, con le funzionalità base di aper tur a e
salvataggio dei file *.tx t. Pr ima di pr oceder e, ecco una lista delle pr opr ietà più significative dei contr olli in questione:

       AddEx tension : se il nome del file da apr ir e/salvar e non ha un estensione, il contr ollo l'aggiunge
       automaticamente sulla base della pr opr ietà DefaultEx t o Filter
       CheckFileEx ists : contr olla se il file selezionato esista
       CheckPathEx ists : contr olla se la car tella selezionata esista
       DefaultEx t : l'estenzione pr edefinita su cui si basa la pr opr ietà AddEx tension
       FileName : il nome del file visualizzato di default nella tex tbox del contr ollo, e modificato dopo l'inter azione con
       l'utente
       Filter : la pr opr ietà più impor tante dopo FileName. Ser ve a definir e quali tipi di file siano visualizzati dal
       contr ollo. Nella finestr a di dialogo, infatti, come mostr a l'immagin sopr a r ipor tata, poco sotto alla tex tbox
       contenente il nome del file, c'è una combobox che per mette di selezionar e il "filtr o", per l'appunto, ossia quali
       estensioni pr ender e in consider azione (nell'esempio "File di testo", con estensione *.tx t, quella che si pr ender à in
       esame nell'esempio). Ci sono delle r egole standar d per la costr uzione della str inga che deve esser e passata a
       questa pr opr ietà. Il for mato cor r etto è:

            1. Descrizione file|*.estensione1;*.estensione2|Descrizione file|...

       Se, quindi, si volesser o visualizzar e solo file multimediali, divisi in musica e video, questo sar ebbe il valor e di
       Filter : "Musica|*.mp3;*.w av;*.w ma;*.ogg;*.mid|Video|*.mpg;*.mp4;*.w mv;*.avi". Per            i file di testo "File di
       testo|*.tx t" e per tutti i file "Tutti i file|*.*"
       InitialDir ector y: la car tella iniziale pr edefinita
       MultiSelect: se ver o, si potr anno selezionar e più file (cr eando un r iquadr o col puntator e o selezionandoli
       manualmente uno ad uno tenendo pr emuto Ctr l)
       Title: il titolo della finestr a di dialogo
       ValidatesName: contr olla che i nomi dei file non contengano car atter i vietati
       Over Wr itePr ompt: (solo per SaveFileDialog) contr olla se il file selezionato ne sovr ascr ive un altr o e chiede se
       pr oceder e o no




Esempio: Editor di testi
Dopo aver analizzato le pr opr ietà impor tanti, si può pr oceder e alla stesur a del codice, ma pr ima una pr ecisazione.
Non avendo inter faccia gr afica sulla finestr a, ma costituendo w indow s for ms a sè stante, i contr olli OpenFileDialog e
SaveFileDialog possono esser e inser iti nel designer oppur e inizializzati da codice indiffer entemente (per quanto
r iguar da lo scopo). La diver sità nell'usar e un metodo piuttosto che un altr o sta nel fatto che il pr imo utilizza sempr e lo
stesso contr ollo, che potr ebbe dar e dei FileName er r ati in casi speciali, mentr e il secondo ne inizializza uno nuovo ad
ogni evento, costando di più in ter mini di memor ia. Nell'esempio seguente utilizzo il pr imo metodo, ma potr à capitar e
che sfr utti anche il secondo in diver se altr e occasioni.
Or a si aggiungano i contr olli necessar i:
Button : Name = cmdOpen, Tex t = "Apr i", Anchor = Bottom Or Left
         Button : Name = cmdSave, Tex t = "Salva", Anchor = Bottom Or Right
         Button : Name = cmdClose, Tex t = "Chiudi", Anchor = Bottom
         Tex tBox : Name = tx tFile, Multiline = Tr ue, Anchor = Top Or Right Or Bottom Or Left
         OpenFileDialog : Name = FOpen, Filter = "File di testo|*.tx t", FileName = "Testo"
         SaveFileDialog : Name = FSave, Filter = "File di testo|*.tx t", DefaultEx t = "tx t"




   01.    Private Sub cmdOpen_Click(ByVal sender As Object, ByVal e As EventArgs)_
   02.        Handles cmdOpen.Click
   03.        'La funzione ShowDialog visualizza la finestra di dialogo e
   04.        'restituisce quale pulsante è stato premuto
   05.        'Se il pulsante corrisponde con OK, procediamo
   06.        If FOpen.ShowDialog = Windows.Forms.DialogResult.OK Then
   07.            'Apre un file in lettura
   08.            'Usa la proprietà FileName di FOpen, che restituisce il
   09.            'path del file selezionato: è sicuro che il file esista
   10.            'perchè l'utente ha premuto Ok e non ha chiuso la
   11.            'finestra di dialogo
   12.            Dim R As New IO.StreamReader(FOpen.FileName)
   13.
   14.               'Legge tutto il testo del file e lo deposita nella textbox
   15.               txtFile.Text = R.ReadToEnd
   16.
   17.            'Chiude il file
   18.            R.Close()
   19.        End If
   20.    End Sub
   21.
   22.    Private Sub cmdSve_Click(ByVal sender As Object, ByVal e As EventArgs) _
   23.        Handles cmdSave.Click
   24.        'Viene visualizzata la finestra di dialogo
   25.        If FSave.ShowDialog = Windows.Forms.DialogResult.OK Then
   26.            'Apre un file in scrittura, di ci si assicura che
   27.            'l'utente acconsenta alla sovrascrittura se già esistente
   28.            'mediante la proprietà OverwritePrompt
   29.            Dim W As New IO.StreamWriter(FSave.FileName)
   30.
   31.               'Scrive tutto il contenuto della textbox nel file
   32.               W.Write(txtFile.Text)
   33.
   34.            'Chiude il file
   35.            W.Close()
   36.        End If
   37.    End Sub
   38.
   39.    Private Sub cmdClose_Click(ByVal sender As Object, ByVal e As EventArgs) _
   40.        Handles cmdClose.Click
   41.        If txtFile.Text <> "" And _
   42.           FSave.ShowDialog = Windows.Forms.DialogResult.OK Then
   43.            Dim W As New IO.StreamWriter(FSave.FileName)
   44.
   45.               W.Write(txtFile.Text)
   46.
   47.            W.Close()
   48.        End If
   49.    End Sub

Il sor gente può esser e r eso ancor a più br eve usando i metodi IO.File.Wr iteAllTex t e IO.File.ReadAllTex t.
B11. Scrivere un INI Reader - Parte I


I file INI
Dato che l'esempio di questo capitolo consiste nel r ealizzar e un lettor e di file *.ini, è bene spiegar e pr ima, per chi non
li conoscesse, come sono fatti e quale è lo scopo di questo tipo di file.
Sono file di sistema contr addistinti dalla dicitur a "Impostazioni di Configur azione", poichè tale è la lor o funzione:
ser vono a definir e il valor e delle opzioni di un pr ogr amma. Nelle applicazioni .NET ci sono altr i modo molto più
efficienti per r aggiunger e lo stesso r isultato ma li vedr emo in seguito. La str uttur a di un file ini è composta
sostanzialmente da due nuclei: cam pi e v alor i. I campi sono r aggr uppamenti concettuali atti a divider e
funzionalmente più valor i di ambito diver so e sono delimitati da una coppia di par entesi quadr e. I valor i costituiscono
qualcosa di simile alle pr opr ietà delle classi .NET e possono esser e assegnati con l'oper ator e di assegnamento =. Un
ter zo tipo di elemento è costituito dai commenti, che, come ben si sa, non influiscono sul r isultato: questi sono
pr eceduti da un punto e vir gola e possono esser e sia su una linea inter a che sulla stessa linea di un valor e. Ecco un
esempio:

 ;Ipotetico INI di un gioco
 [General Info]
 Name = ProofGame
 Version = 1.1.0.2
 Company = FG Corporation
 Year = 2006

 [Run Info]
 Diffucult = easy ;difficoltà
 Lives = 10 ;numero di vite
 Health = 90 ;salute
 Level = 20 ;livello


Il pr ogr amma di esempio analizzer à il file, r appr esentando campi e valor i in un gr afico ad alber o simile a quello che
w indow s usa per r appr esentar e la str uttur a ger ar chica delle car telle.




MenuStrip
È il classico menù di w indow s. Una volta aggiunto al for m designer , viene cr eato uno spazio apposito sotto
all'antepr ima del for m, nel quale appar e l'icona cor r ispondente; inoltr e viene visualizzata una str iscia gr igia sul lato
super ior e della finestr a, ossia l'inter faccia gr afica che MenuStr ip pr esenter à a r un-time. Per aggiunger e una voce,
basta far e click su "Type her e" e digitar e il testo associato; è possibile cancellar ne uno pr emendo Canc o modificar lo
cliccandoci sopr a due volte lentamente. Ogni sottovoce dispone di eventuali altr i sotto-menù per sonalizzabili all'infinito.
Si può aggiunger e un separ ator e, ossia una linea or izzontale, semplicemente inser endo "-" al posto del testo. Ogni
elemento così cr eato è un oggetto ToolStr ipMenuItem, inser ito nella pr opr ietà Dr opDow nItems del menù. Ecco alcune
pr opr ietà inter essanti:

       M enuStr ip
               Allow ItemReor der : deter mina se consentir e il r ior dinamento dei menù da par te dell'utente; quest'ultimo
               potr ebbe, tenendo pr emuto ALT e tr ascinando gli header , cambiar e la posizione delle sezioni sulla bar r a
               del MenuStr ip
               Items : collezione di oggetti der ivati da MenuItem che costituiscono le sezioni pr incipali del menu'
Render Mode : pr opr ietà enumer ata che definisce lo stile gr afico del contr ollo. Può assumer e tr e valor i:
               System (dipende dal sistema oper ativo), Pr ofessional o Manager Render Mode (stile simile a Micr osoft
               Office)
               Show ItemToolTips : deter mina se visualizzar e i sugger imenti (tool tip) di ogni elemento
               Tex tDir ection : dir ezione del testo, or izzontale, ver ticale a 90? o a 270?
       To o lStr ipM enuItem
               AutoToolTip : deter mina se usar e la pr opr ietà Tex t (Tr ue) o ToolTipTex t (False) per visualizzar e i tool tip
               Checked : deter mina se il contr ollo ha la spunta
               CheckOnClick : specifica sa sia possibile spuntar e il contr ollo con un click
               CheckState : uno dei tr e stati di spunta
               DisplayStyle : specifica cosa visualizzar e, se solo il testo, solo l'immagine, entr ambi o nessuno
               Dr opDow nItems : uguale alla pr opr ietà Items di MenuStr ip
               Shor tcutKeyDisplayStr ing : la str inga che deter mina quale sia la scor ciatoia da tastier a per il contr ollo,
               che ver r à visualizzata a destr a del testo (ad esempio "CTRL + D")
               Shor tcutKeys : deter mina la combinazione di tasti usata come scor ciator ia
               Show Shor tcutKeys : deter mina se visualizzar e la scor ciatoia da tastier a di fianco al testo
               Tex tImageRelation : r elazione di posizione tr a immagine e testo
               TooltipTex t : testo dell'eventuale tool tip

Dopo aver inser ito un MenuStr ip str MainMenu, una sezione str File e tr e sottosezioni, str Open, Separ ator e e str Ex it,
la scher mata appar ir à così:




StatusStrip
La bar r a di stato, sul lato basso del for m, che indica le infor mazioni aggiuntive o lo stato dell'applicazione. È un
contenitor e che può includer e altr i contr olli, come label, pr ogr essbar , dr opdow nitem, ecceter a. Per or a basta inser ir e
una label, di nome lblStatus, con testo impostato su "In attesa...". Dato che le pr opr ietà sono quasi identiche a quelle di
MenuStr ip, ecco subito un'antepr ima del for m con questi due contr olli posizionati:
ContextMenuStrip
È il menù contestuale, ossia quel menù che appar e ogniqualvolta viene pr emuto il pulsante destr o del mouse su un
deter minato contr ollo. Per far sì che esso appaia bisogna pr ima cr ear e un legame tr a questo e il contr ollo associato,
impostando la r elativa pr opr ietà Contex tMenuStr ip, comune a tutte le classi der ivate da Contr ol. La fase di cr eazione
avviene in modo identico a quanto è già stato analizzato per MenuStr ip, e anche l'inser imento delle sottovoci è simile.
Non dovr este quindi aver e pr oblemi a cr ear ne uno e inser ir e una voce str Clear View , Tex t = "Pulisci lista".




TreeV iew
Ecco il contr ollo clou della lezione, che per mette di visualizzar e dati in una str uttur a ad alber o. Le pr opr ietà più
impor tanti sono:

       CheckBox es: deter mina se ogni elemento debba aver e alla pr opr ia sinistr a una checkbox
       FullRow Select: deter mina se, quando un elemento viene selezionato, sia evidenziato solo il nome o tutta la r iga
       su cui sta il nome
       ImageList: specifica quale ImageList è associata al contr ollo; un'imagelist è una lista di immagini or dinata,
       ognuna delle quali è accessibile attr aver so un indice, come se fosse un ar r aylist
       ImageIndex : pr opr ietà che deter mina l'indice di default di ogni elemento, da pr elevar e dall'imagelist associata;
       nel caso la pr opr ietà sia r ifer ita a un elemento, indica quale immagine bisogna visualizzar e a fianco
       dell'elemento
       Nodes: la pr opr ietà più impor tante: al par i di Items delle listbox e delle combobox . Contiene una collezione di
       Tr eeNode (ossia "nodi d'alber o"): ogni elemento Node ha molteplici pr opr ietà e costituisce un'unità dalla quale
       possono dipar tir si altr e unità. Cosa impor tante, ogni nodo gode di una pr opr ietà Nodes equivalente, la quale
       implementa la str uttur a suddetta
       SelectedNode: r estituisce il nodo selezionato
       Show Lindes: indica se visualizzar e le linee che congiungono i nodi
       Show PlusMinus: indica se visualizzar e i '+' per espander e i nodi contenuti in un elemento e i '-' per eseguir e
       l'oper azione opposta
       Show RootLindes: deter mina se visualizzar e le linee che congiungono i nodi che non dipendono da niente, ossia le
       unità dalle quali si dipar tono gli altr i elementi
In una Tr eeView , ogni elemento è detto appunto no do ed è r appr esentato dalla classe Tr eeNode: ogni nodo può a sua
volta dipar tir si in più sotto-elementi, ulter ior i nodi, in un ciclo lungo a piacer e. Gli elementi che non der ivano da nulla
se non dal contr ollo stesso sono detti r oots, r adici. Allo stesso modo delle car telle e dei file del computer , ogni nodo può
esser e indicato con un per cor so di for mato simile, dove i nome dei nodi sono separ ati da "". La pr opr ietà di Tr eeNode
non sono niente di speciale o innovativo: sono già state tutte analizzate, o der ivate da Contr ol. Ecco come appar e
l'inter faccia, dopo aver aggiunto una Tr eeView tr w Ini con Dock = Fill e un Contex tMenuStr ip cntTr eeView ad essa
associato:
B12. Scrivere un INI Reader - Parte II


Dopo aver spiegato e posizionato i var i contr olli con le pr opr ietà adatte, si deve stender e il codice che per mette al
pr ogr amma di legger e i file e visualizar li cor r ettamente. Ecco il sor gente commentato:

   01. Class Form1
   02.     Private Sub ReadFile(ByVal File As String)
   03.         'Lo stream da cui leggere il file
   04.         Dim Reader As New IO.StreamReader(File)
   05.         'Una stringa che rappresenta ogni singola riga del file
   06.         Dim Line As String
   07.         'L'indice associato al numero di campi letti. Dato che ogni
   08.         'campo costituirà una radice del grafico, bisogna sapere da
   09.         'dove far derivare i relativi valori.
   10.         'Questa variabile è opzionale, in quanto è possibile usare
   11.         'la proprietà trwIni.Nodes.Count-1, poichè si aggiungono
   12.         'valori sempre soltanto all'ultimo campo aperto
   13.         Dim FieldCount As Int16 = -1
   14.
   15.         'Imposta il testo della label di stato
   16.         lblStatus.Text = "Apertura del file in corso..."
   17.
   18.         'Finchè non si raggiunge la fine del file si continua
   19.         'a leggere
   20.         While Not Reader.EndOfStream
   21.             'Leggiamo una linea di file (S)
   22.             Line = Reader.ReadLine
   23.             'Se la linea è diversa da una riga vuota
   24.             If Line <> Nothing Then
   25.                  'Se la linea inizia per "[" (significa che è
   26.                  'un campo)
   27.                  If Line.StartsWith("[") Then
   28.                       'Si aumenta FieldCount, che indica quanti campi
   29.                       'si sono già letti (in base 0)
   30.                       FieldCount += 1
   31.                       'Rimuove il primo carattere, ossia "["
   32.                       Line = Line.Remove(0, 1)
   33.                       'Rimuove dalla linea l'ultimo carattere,
   34.                       'ossia "]"
   35.                       Line = Line.Remove(Line.Length - 1, 1)
   36.                       'Aggiunge una radice alla TreeView
   37.                       trwIni.Nodes.Add(Line)
   38.                  Else
   39.                       'Altrimenti, se la linea non inzia per ";",
   40.                       'ossia non è un commento
   41.                       If Not Line.StartsWith(";") Then
   42.                           'Aggiunge la linea come sotto-nodo
   43.                           'dell'ultimo campo inserito. La linea
   44.                           'conterrà il valore in forma
   45.                           ' [nome]=[contenuto]
   46.                           'Attenzione! Possono esserci commenti in
   47.                           'riga, quindi si deve prima controllare
   48.                           'di eliminarli
   49.                           'Se l'indice del carattere ";" nella riga
   50.                           'è positivo...
   51.                           If Line.IndexOf(";") > 0 Then
   52.                               'Rimuove tutto quello che viene dopo
   53.                               'il commento
   54.                               Line = Line.Remove(Line.IndexOf(";"))
   55.                           End If
   56.                           trwIni.Nodes(FieldCount).Nodes.Add(Line)
   57.                       End If
   58.                  End If
   59.             End If
   60.         End While
   61.
'Chiude il file
   62.              Reader.Close()
   63.
   64.            lblStatus.Text = "File aperto"
   65.        End Sub
   66.
   67.        Private Sub strOpen_Click(ByVal sender As Object, _
   68.            ByVal e As EventArgs) Handles strOpen.Click
   69.            'Ecco un esempio di OpenFileDialog da codice
   70.            Dim FOpen As New OpenFileDialog
   71.            FOpen.Filter = "Impostazioni di configurazione|*.ini"
   72.            If FOpen.ShowDialog = Windows.Forms.DialogResult.OK Then
   73.                ReadFile(FOpen.FileName)
   74.            End If
   75.        End Sub
   76.
   77.        Private Sub strExit_Click(ByVal sender As Object, _
   78.            ByVal e As EventArgs) Handles strExit.Click
   79.            'Esce dal programma, chiudendo il form corrente
   80.            Me.Close()
   81.        End Sub
   82.
   83.        Private Sub strClearList_Click(ByVal sender As Object, _
   84.            ByVal e As EventArgs) Handles strClearList.Click
   85.            'Mostra un messaggio di conferma prima di procedere
   86.            If MessageBox.Show("Eliminare tutti gli elementi dela lista?", _
   87.                "INI Reader", MessageBoxButtons.YesNo, MessageBoxIcon.Question) = _
   88.                Windows.Forms.DialogResult.No Then
   89.                'Se si risponde di no, esce dalla procedura
   90.                Exit Sub
   91.            End If
   92.
   93.            'Elimina tutti i nodi
   94.            trwIni.Nodes.Clear()
   95.        End Sub
   96. End    Class

Il codice degli eventi è molto semplice, mentr e più inter essante è quello della pr ocedur a ReadFile. Per aver e una
panor amica delle oper azioni sulle str inghe usate, veder e capitolo r elativo. Per quanto r iguar da la logica del sor gente,
ecco una br eve spiegazione: viene letto il file r iga per r iga e, sulla base delle condizioni che si incontr ano man mano,
vengono eseguite istr uzioni diver se:

       La linea è vuota : può capitar e che si lascino linee di testo vuote per separ ar e ulter ior mente i campi o valor i
       dell'inter no dello stesso campo; in questo caso, poichè non c'è niente da legger e, semplicemente si passa oltr e
       La linea inizia per "[" : come già detto, in un file ini, i campi sono r acchiusi tr a par entesi quadr e, per ciò la linea
       costituisce il nome di un campo. Dopo aver eliminato le par entesi con oppor tune funzioni, si usa il r isultato per
       aggiunger e alla Tr eeView una r oot mediante Nodes.Add. Questo metodo accetta, tr a i var i over loads, un
       par ametr o str inga che costituisce il testo del nodo
       La linea inizia per ";" : è un commento e semplicemente viene omesso. Potr este comunque includer lo come nodo
       ausiliar lo e color ar lo con un color e differ ente
       La linea non ha nessun delle car atter istiche indicate : è un valor e. Quindi si aggiunge il suo contenuto come
       sotto-nodo all'ultimo nodo r oot aggiunto, con l'accor tezza di contr ollar e pr ima se ci sono dei commenti cosiddetti
       in-line e di eliminar li

Ecco uno scr eenshot di come si pr eseta il pr ogr amma finito con un file ini car icato:




Ed ecco uno scr eenshot di come potr este far lo diventar e:
 Vb.net
B13. DateTimePicker - Lavorare con le date


Il tipo di dato standar d che il .NET Fr amew or k mette a disposizione per lavor ar e cone le date e gli or ar i è Date,
facente par te del Namespace System. Per             compatibilità con il vecchio Visual Basic 6, è pr esenta anche
System.DateTime, che r appr esenta la stessa identica entità. Con questo semplice tipo è possibile far e di tutto e per ciò
non è necessar io definir e manualmente alcun metodo nuovo quando si lavor a con le date. Ecco un elenco dei metodi e
delle pr opr ietà più impor tanti:

       Add(t): aggiunge alla data un fattor e t di tipo TimeSpan contenente una dur ata di tempo
       AddYear s, AddMonths, AddDays, AddHour s, AddMinutes, AddSeconds, AddMilliseconds: aggiungono un fattor e t di
       anni, mesi, gior ni, or e, minuti, secondi, millisecondi alla data, specificata come unico par ametr o
       Year , Month, Day, Hour , Minute, Second, Millisecond: r estituiscono l'anno, il mese, il gior no, l'or a, i minuti, i
       secondi o i millisecondi della data contenuta nella var iabile
       DayOfWeek: r estituisce un enumer ator e che r appr esenta il gior no della settimana contenuto nella data della
       var iabile
       DayOfYear : r estituisce un numer o che indica il numer o del gior no in tutto l'anno
       DaysInMonth(y, m): r estituisce il numer o di gior ni del mese m dell'anno y
       Now : pr opr ietà shar ed che r estituisce la data cor r ente (Date.Now )
       Par se(s): funzione shar ed che conver te la str inga s in una data; utile per quando si deve salvar e una data su file
       Subtr act(d): sottr ae alla data della var iabile la data d, r estituendo un valor e di tipo TimeSpan (ossia 'tempo
       tr ascor so')
       ToLongDateStr ing: conver te la data in una str inga, espandendo la data in questo for mato: [gior no della
       settimana] [gior no del mese] [mese] [anno] (esempio: vener dì 30 giugno 2006)
       ToLongTimeStr ing: conver te l'or a della data in una str inga, espandendola in questo for mato: [or e].[minuti].
       [secondi] (esempio: 13.13.07)
       ToShor tDateStr ing: conver te la data in una str inga, contr aendola in questo for mato: [gior no del mese][mese]
       [anno] (esempio: 30/6/2006)
       ToShor tTimeStr ing: conver te l'or a della data in una str inga, contr aendola in questo for mato: [or e].[minuti]
       (esempio: 13.13)
       ToFileTime : funzione cur iosa, che r estituisce la data in for mato file, ossia come multiplo di inter valli di 100
       nanosecondi tr ascor si dal pr imo gennaio 1601 alle or e 12.00 di mattina
       Tr yPar se(s, r ): tenta di conver tir e la str inga s in una data: se ci r iesce, r assume il valor e della data (r è
       passata per indir izzo) e r estituisce Tr ue; se non ci r iece, r estituisce False

Par allelamente, viene definito anche il tipo TimeSpan ("tempo tr ascor so") che r appr esenta un lasso di tempo e si
ottiene con la differ enza di due valor i Date. Ha le stesse pr opr ietà sopr a elencate, fatta eccezione per alcune che
possono r ivelar si inter essanti, come Fr omDays, Fr omHour s, Fr omSeconds, Fr omMinutes, Fr omMilliseconds: funzioni
shar ed che cr eano un valor e di tipo TimeSpan a par tir e da un ammontar e di gior ni, or e, minuti, secondi o millisecondi.




Esempio: A long, long life
Ecco un esempio molto semplice e diver tito che applica i concetti sopr a esposti. Lo scopo del pr ogr amma è di calcolar e
con una buona pr ecisione la dur ata della nostr a vita, avendo immesso pr ecedentemente la data di nascita. Il contr ollo
usato è DateTimePicker , le cui pr opr ietà sono autoesplicative. Per or a pr ender ò in analisi solo le pr opr ietà For mat e
CustomFor mat. La pr ima per mette di definir e il for mato del contr ollo: è r appr esentata da un enumer ator e che può
assumer e quattr o valor i, Long (data in for mato esteso, come la r estituisce la funzione Date.ToLongDateStr ing), Shor t
(data in for mato br eve, come la r estituisce la funzione Date.ToShor tdateStr ing), Time (or a in for mato esteso) e
Custom (per sonalizzato). Se viene scelta l'ultima opzione, si deve impostar e la str inga CustomFor mat in modo da
r ipr odur r e il valor e in confor mità ai pr opr i bisogni. Nella str inga possono pr esenziar e queste sequenze di car atter i:

       d : gior no del mese, con una o due cifr e a seconda dei casi
       dd : gior no del mese, sempr e con due cifr e (vengono aggiunti zer i sulla sinistr a nel caso manchino posti)
       ddd : gior no della settimana, abbr eviato a tr e car atter i secondo la cultur a cor r ente
       dddd : gior no della settimana, con nome completo
       M : mese, con una o due cifr e a seconda dei casi
       M M : mese, sempr e con due cifr e
       M M M : nome del mese, abbr eviato a tr e car atter i secondo la cultur a cor r ente
       M M M M : nome completo del mese
       y : anno, con una o due cifr e a seconda dei casi
       yy : anno, sempr e con due cifr e
       yyyy : anno, a quattr o cifr e
       H : or a, in for mato 24 or e con una o due cifr e
       HH : or a, in for mato 24 or e con due cifr e
       h : or a, in for mato 12 or e, con una o due cifr e
       hh : or a, in for mato 12 or e, con due cifr e
       m : minuti, con una o due cifr e
       m m : minuti, con due cifr e
       s : secondi, con una o due cifr e
       ss : secondi, con due cifr e
       f : fr azioni di secondo (un numer o qualsiasi da uno a sette di "f" consecutive cor r isponde ad altr ettanti decimali)

Dato che il contr ollo dovr à espor r e il valor e in for mato:

 [nome giorno] [giorno] [nome mese] [anno], ore [ora]:[minuti]


La str inga di for mato da inser ir e sar à:

 dddd d MMMM yyyy, ore HH:mm


Gli stessi patter n valgono anche se posti come ar gomento della funzione Date.ToStr ing("For mato"). I contr olli da
aggiunger e sono un DateTimePicker (dtpBir thday), con una label di spiegazione a fianco, una label che visualizzi i
r isultati (lblAge) e un timer (tmr Refr esh) per aggior nar e il r isultato a ogni secondo che passa. Or a non r esta che
scr iver e il codice, per altr o molto semplice. Il sogente fa uso di un contr ollo Timer , che una volta abilitato
(Timer .Enabled=Tr ue o Timer .Star t()), lancia un evento Tick ogni Timer .Inter val millisecondi cir ca (il valor e è molto
var iabile, a seconda della velocità del computer su cui viene fatto cor r er e).

   01. Class Form1
   02.     Private Sub tmrRefresh_Tick(ByVal sender As Object, _
   03.         ByVal e As EventArgs) Handles tmrRefresh.Tick
   04.         'Ottiene la differenza tra le due date
   05.         Dim Age As TimeSpan = (Date.Now - dtpBirthDay.Value)
   06.         'La trasforma in secondi
   07.         Dim Seconds As Double = Age.TotalSeconds
   08.         'Variabile temporanea che serve alla costruzione
   09.         Dim AgeStr As New System.Text.StringBuilder
   10.
   11.         With AgeStr
   12.             .AppendLine("Hai vissuto")
   13.
   14.
'Calcola i giorni secondo il modo già visto nelle prime
   15.                   'lezioni sulle classi e le proprietà
   16.                   .AppendFormat("{0} giorni{1}", Seconds  (60 * 60 * 24), vbCrLf)
   17.                   Seconds -= (Seconds  (60 * 60 * 24)) * (60 * 60 * 24)
   18.
   19.                   'E così anche ore, minuti e secondi
   20.                   .AppendFormat("{0} ore{1}", Seconds  3600, vbCrLf)
   21.                   Seconds -= (Seconds  3600) * 3600
   22.
   23.                   .AppendFormat("{0} minuti{1}", Seconds  60, vbCrLf)
   24.                   Seconds -= (Seconds  60) * 60
   25.
   26.                   .AppendFormat("{0:n0} secondi", Seconds)
   27.
   28.                   'Quindi mette il risultato come testo della label
   29.                   lblAge.Text = .ToString
   30.         End       With
   31.     End Sub
   32. End Class

Per il mio caso, il r isultato è questo:
B14. ImageList


In fase di pr ogettazione, se si vogliono aggiunger e immagini a contr olli come Button, Label, SplitButton, ToolBox et
similia è sufficiente selezionar e la pr opr ietà Image (o Backgr oundImage), apr ir e la finestr a di dialogo mediante
pr essione sul pulsante che appar e, sceglier e quindi un file immagine dall'Har d Disk o dalle r isor se del pr ogetto, e
confer mar e la scelta per ottener e un effetto ottimo. Tuttavia, ciò non è sempr e possibile, ad esempio se a r un-time si
vogliono associar e deter minate icone a elementi di una lista che non è possibile pr eveder e dur ante la stesur a del
codice. In situazioni simili, il contr ollo che viene in aiuto del pr ogr ammator e si chiama ImageList. Esso costituisce una
lista, or dinata secondo indici e chiavi, che contiene immagini pr ecedentemente car icate dallo sviluppator e: tutte
queste vengono r idimensionate secondo una dimensione fissata dalle pr opr ietà del contr ollo e hanno una limitazione di
pr ofondità di color e, sempr e pr edeter minata, da 8 a 32 bit. Per ottener e effetti di gr ande impatto, è consigliabile
utilizzar e for mati ad ampio spettr o di color e e con tr aspar enza come il Por table Netw or k Gr aphics (*.png), oppur e il
JPEG (*.jpg) se si vuole r ispar miar e spazio pur conser vando una discr eta qualita'; il for mato ideale è 32x 32 pix el per le
icone gr andi e 22x 22 o 16x 16 in quelle piccole come nei menù a discesa o nelle ListView a dettagli.
Il meccanismo che per mette ai contr olli di fr uir e delle r isor se messe a disposizione da un'ImageList è lo stesso usato dal
Contex tMenuStr ip. Ogni contr ollo con inter faccia che suppor ti questo pr ocesso, dispone di una pr opr ietà ImageList, che
deve esser e impostata di conseguenza a seconda della lista di immagini che si vuole quel contr ollo possa utilizzar e.
Successivamente, i singoli elementi al suo inter no sono dotati delle pr opr ietà ImageIndex e ImageKey, che per mettono
di associar vi un'immagine pr elevandola mediante l'indice o la chiave impostata. Ecco alcuni esempi di come potr ebber o
pr esentar si contr olli di questo tipo:




                                                    Im ag eList su ListVie w




                                                   Im ag eList su Tr eeView



                                                  Im ag eList su TabCo ntr o l




Reperire le ic one
Indubbiamente questo contr ollo offr e moltissime possibilità di per sonalizzar e la veste gr afica dell'applicazione a piacer e
del pr ogr ammator e, tuttavia se non si dispone di mater iale adatto, il suo gr ande aiuto viene meno. Per questo
motivo, dar ò alcuni sugger imenti su come r eper ir e un buon numer o di icone fr eew ar e o al limite sotto licenza lgpl (il
che le r ende disponibili per l'uso da par te di softw ar e commer ciali). Come pr ima r isor sa, c'è il pr ogr amma AllEx Icon,
scr itto da Maur o Rossi in Visual Basic 6, che potete tr ovar e a questo indir izzo . Dopo aver lo avviato, basta impostar e
la dir ector y di r icer ca su C:WINDOWSSystem32 (per sistemi oper ativi Window s XP) e il filtr o su "*.dll". Ver r anno
estr atte moltissime belle icone, con la possibilità di salvar le in for mato bitmap una alla volta o in massa. Dato il lor o
for mato, anche conver tite in JPEG, r imar r à un color e di sfondo, che può venir e par zialmente eliminato impostando la
pr opr ietà ImageTr anspar ency del for m su Tr anspar ent o su White, r endendo quindi tr aspar ente il lor o sfondo. Come
seconda possibilità ci sono alcuni pacchetti di icone r eper ibili dal w eb. Il pr imo che consiglio è "Nuvola", lo stesso che uso
per le mie applicazioni, distr ibuito sotto licenza LGPL su questo sito; il secondo è "500.000 Icone!", una collezione di
cir ca 8000 icone, divise in *.ico e *.png, messe insieme da svar iate fonti del w eb: ogni r isor sa è stata r esa pubblica dal
suo cr eator e e non ci sono limitazioni al lor o uso. Il pacchetto può esser e tr ovato solo attr aver so eMule. La ter za
possibilità consiste nel cer car e sulla r ete insiemi di immagini messe liber amente a disposizione di tutti da qualche
volenter oso designer , ad esempio su questa pagina di Wikipedia, dove, navigando tr a le var ie categor ie, è possibile
ottener e svar iate centinaia di icone.
B15. ListView


La ListView è un contr ollo complesso e di gr ande impatto visivo. È lo stesso tipo di lista usato dall'ex plor er di w indow s
per visualizzar e files e car telle. Le sue pr opr ietà per mettono di per sonalizzar ne la visualizzazione in cinque stili
diver si: i più impor tanti di questi sono Lar ge Icone (Icone gr andi), Small Icon (Icone piccole) e Details (Dettagli). Ci sono
poi anche Tile e List, ma vengono usati meno spesso. Ecco alcuni esempi:




                                                          Lar g e Icon




                                                          Sm all Icon




                                                              Details




ListV iew
Come al solito, ecco la compilation delle pr opr ietà più inter essanti:

       CheckBox es : indica se la listview debba visualizzar e delle CheckBox vicino ad ogni elemento
       Columns : collezione delle colonne disponibili. Ogni colonna è contr addistinta da un testo (Tex t), un indice
       d'immagine (ImageIndex ) e un indice di visualizzazione (DisplayIndex ) che specifica la sua posizione or dinale nella
       visualizzazione. Le colonne sono visibili sono con View = Details
       FullRow Select : indica se evidenziar e tutta la r iga o solo il pr imo elemento, quando View = Details
       Gr idLines : indica su visualizzar e le r ighe della gr iglia, quando View = Details
       Gr oups : collezione dei gr uppi disponibili
       Header Style : specifica se le intestazioni delle colonne possano esser e cliccate o meno
       HideSelection : specifica se la listview debba nasconder e la selezione quando per de il Focus, ossia quando un altr o
       contr ollo diventa il contr ollo attivo
       HotTr acking : abilita gli elementi ad appar ir e come collegamenti iper testuali quando il mouse ci passa sopr a
       Hover Selection : se impostata su Tr ue, sar à possibile selezionar e un elemento semplicemente sostandoci sopr a
       con il mouse
       Items : collezione degli elementi della listview
       LabelEdit : specifica se sia possibile modificar e il testo dei SubItems da par te dell'utente, quando View = Details
       Lar geImageList : ImageList per View = Lar ge Icon / Tile / List
       MultiSelect : indica se si possano selezionar e più elementi contempor aneamente
       Ow ner Dr aw : indica se gli elementi debbano esser e disegnati dal contr ollo o dal codice del pr ogr ammator e. Vedi
       ar ticolo r elativo
       Show Gr oups : deter mina se visualizzar e i gr uppi
       Show ItemToolTips : deter mina se visualizzar e i ToolTips dei r ispettivi elementi
       SmallIconList : ImageList per View = Small Icon / Details
       Sor ting : il tipo di or dinamento, se alfabetico ascendente o discendente.
ListV iew Item
Ogni elemento della ListView è contr addistinto da un oggetto ListView Item, che, a differ enza di quanto avveniva cone
le nor mali liste come ListBox e ComboBox , non costituisce una semplice str inga (o un tipo base facilmente
r appr esentabile) ma un nucleo a sè stante, del quale si possono per sonalizzar e tutte le car atter istiche visive e
stilistiche. Poichè la ListView è compatibile con l'ImageList, tutti i membr i della collezione Items sono in gr ado di
impostar e l'indice d'immagine associato, come si è analizzato nella lezione scor sa. Inoltr e, sempr e manipolando le
pr opr ietà, si può attr ibuir e ad ogni elemento un testo, un font, un color e diver so a seconda delle necessità. Nella
visualizzazione a dettagli si possono impostar e tutti i valor i cor r ispettivi ad ogni colonna mediante la pr opr ietà
SubItems, la quale contiene una collezione di oggetti ListView SubItem: di questi è possibile modificar e il font e il color e,
oltr e che il testo.




ListV iew Group
Un gr uppo è un insieme di elementi r aggr uppati sotto la stessa etichetta. I gr uppi vengono aggiunti alla lista
utilizzando l'apposita pr opr ietà Gr oups e ogni elemento può esser e assegnato ad un gr uppo con la stessa pr opr ietà
(ListView Item.Gr oup). Oggetti di questo tipo godono di poche car atter istiche: solo il testo ed il nome sono modificabili.
Pr ima di pr oceder e con ogni oper azione, per ò, bisogna assicur ar si che la pr opr ietà Show Gr oups della ListView sia
impostata a Tr ue, altr imenti... beh, niente festa.
Ecco un esempio di codice:

   01.   'Nuovo gruppo 'Testo esplicativo'
   02.   Dim G As New ListViewGroup("Testo esplicativo")
   03.   'Nuovo elemento 'Elemento'
   04.   Dim L As New ListViewItem("Elemento")
   05.   'Aggiunge il gruppo alla listview
   06.   ListView1.Groups.Add(G)
   07.   'Setta il gruppo a cui L apparterrà
   08.   L.Group = G
   09.   'Aggiunge l'elemento alla lista
   10.   ListView1.Items.Add(L)




ListV iew c on V iew = Details
La ListView a dettagli è la ver sione più complessa di questo contr ollo, ed è contr addistinta dalla classica visualizzazione
a colonne. Ogni colonna viene deter minata in fase di sviluppo o a r un-time agendo sulla collezione Columns nelle
pr opr ietà della lista e bisogna r icor dar e l'or dine in cui le colonne vengono inser ite poichè questo influisce in manier a
significativa sul compor tamento dei SubItems. Infatti il pr imo SubItem di un ListView Item andr à sotto la pr ima colonna,
quella con indice 0, il secondo sotto la seconda e così via. Un er r or e di or dine potr ebbe pr odur r e quindi, r isultati
sgr adevoli. Nell'esempio che segue, scr iver ò un br eve pr ogr amma per calcolar e la spesa totale conoscendo i singoli
pr odotti e le singole quantità di una lista della spesa. Ecco un'antepr ima di come dovr ebbe appar ir e la finestr a:




I contr olli usati sono deducibili dal nome e dall'utilizzo. Ecco quindi il codice:

   01. Class Form1
   02.     Private Sub cmdAdd_Click(ByVal sender As System.Object, _
   03.         ByVal e As System.EventArgs) Handles cmdAdd.Click
   04.         'Nuovo elemento
   05.         Dim Item As ListViewItem
   06.         'Array dei valori che andranno a rappresentare i campi di
   07.         'ogni singola colonna
   08.
Dim Values() As String = _
   09.                 {txtProduct.Text, nudPrice.Value, nudQuantity.Value}
   10.
   11.            'Inizializza Item sulla base dei valori dati
   12.            Item = New ListViewItem(Values)
   13.            'E lo aggiunge alla lista
   14.            lstProducts.Items.Add(Item)
   15.        End Sub
   16.
   17.        Private Sub cmdDelSelected_Click(ByVal sender As System.Object, _
   18.            ByVal e As System.EventArgs) Handles cmdDelSelected.Click
   19.            'Analizza tutti gli elementi selezionati
   20.            For Each SelItem As ListViewItem In lstProducts.SelectedItems
   21.                 'E li rimuove dalla lista
   22.                 lstProducts.Items.Remove(SelItem)
   23.            Next
   24.        End Sub
   25.
   26.        Private Sub cmdCalculate_Click(ByVal sender As System.Object, _
   27.            ByVal e As System.EventArgs) Handles cmdCalculate.Click
   28.            'Totale spesa
   29.            Dim Total As Single = 0
   30.            'Prezzo unitario
   31.            Dim Price As Single
   32.            'Quantit?
   33.            Dim Quantity As Int32
   34.
   35.             For Each Item As ListViewItem In lstProducts.Items
   36.                  'Ottiene i valori da ogni elemento
   37.                  Price = CSng(Item.SubItems(1).Text)
   38.                  Quantity = CInt(Item.SubItems(2).Text)
   39.                  Total += Price * Quantity
   40.             Next
   41.
   42.            MessageBox.Show("Totale della spesa: " & Total & "€.", _
   43.                "Spesa", MessageBoxButtons.OK, MessageBoxIcon.Information)
   44.        End Sub
   45. End    Class

Per i più cur iosi, mi addentr er ò ancor a un pò di più nel par ticolar e, nella fattispecie su una car atter istica molto
appr ezzata in una ListView a dettagli, ossia la possibilità di or dinar e tutti gli elementi con un click. In questo caso,
facendo click sull'intestazione (header ) di una colonna, sar ebbe possibile or dinar e gli elementi sulla base della qualità che
quella colonna espone: per nome, per pr ezzo, per quantità. Dato che il metodo Sor t non accetta alcun over load che
consenta di usar e un Compar er , bisogner à implementar e un algor itmo che compar i tutti gli elementi e li or dini. In
questo esempio si fa r icor so a due classi che implementano l'inter faccia gener ica ICompar er (Of ListView Item): il pr imo
compar a il nome del pr odotto, mentr e il secondo il pr ezzo e la quantità. Ecco il codice da aggiunger e:

   01. Class Form1
   02.     'Queste classi saranno i comparer usati per ordinare
   03.     'le righe della ListView, al pari di come si è imparato nelle
   04.     'lezioni sulle interfacce
   05.     Private Class ProductComparer
   06.         Implements IComparer(Of ListViewItem)
   07.
   08.         Public Function Compare(ByVal x As ListViewItem, _
   09.             ByVal y As ListViewItem) As Integer _
   10.             Implements IComparer(Of ListViewItem).Compare
   11.             'Gli oggetti da comparare sono ListViewItem, mentre la proprietà
   12.             'che bisogna confrontare è il nome del prodotto, ossia
   13.             'il primo sotto-elemento
   14.             Dim Name1 As String = x.SubItems(0).Text
   15.             Dim Name2 As String = y.SubItems(0).Text
   16.             Return Name1.CompareTo(Name2)
   17.         End Function
   18.     End Class
   19.
   20.     Private Class NumberComparer
   21.         Implements IComparer(Of ListViewItem)
   22.
   23.
Private Index As Int32
24.
25.           'Price è True se ci si riferisce al Prezzo (elemento 1)
26.           'oppure False se ci si riferisce alla quantità (elemento 2)
27.           Sub New(ByVal Price As Boolean)
28.               If Price Then
29.                    Index = 1
30.               Else
31.                    Index = 2
32.               End If
33.           End Sub
34.
35.           Public Function Compare(ByVal x As ListViewItem, _
36.               ByVal y As ListViewItem) As Integer _
37.               Implements IComparer(Of ListViewItem).Compare
38.               'Qui bisogna ottenere il prezzo o la quantità: ci si basa
39.               'sul parametro passato al costruttore
40.               Dim Val1 As Single = x.SubItems(Index).Text
41.               Dim Val2 As Single = y.SubItems(Index).Text
42.               Return Val1.CompareTo(Val2)
43.           End Function
44.       End Class
45.
46.       'Scambia due elementi in una lista: dato che ListViewItem sono
47.       'variabili reference, bisognerebbe clonarli per spostarne il valore,
48.       'come si faceva con i valori value, in questo modo:
49.       'Dim Temp As ListViewItem = L1.Clone()
50.       'L1 = L2.Clone()
51.       'L2 = Temp
52.       'Ma si userebbe troppa memoria. Perciò la via più facile è
53.       'usare i metodi della lista per scambiare gli elementi
54.       Private Sub SwapInList(ByVal List As ListView, ByVal Index As Int32)
55.           Dim Temp As ListViewItem = List.Items(Index + 1)
56.           List.Items.RemoveAt(Index + 1)
57.           List.Items.Insert(Index, Temp)
58.       End Sub
59.
60.       'Ordina gli elementi con l'algoritmo Bubble Sort già
61.       'descritto nell'ultima lezione teorica
62.       Private Sub SortListViewItems(ByVal List As ListView, _
63.           ByVal Comparer As IComparer(Of ListViewItem))
64.           Dim Occurrences As Int32 = 0
65.
66.           Do
67.               Occurrences = 0
68.               For I As Int32 = 0 To List.Items.Count - 1
69.                    If I = List.Items.Count - 1 Then
70.                        Continue For
71.                    End If
72.                    If Comparer.Compare(List.Items(I), List.Items(I + 1)) = 1 Then
73.                        SwapInList(List, I)
74.                        Occurrences += 1
75.                    End If
76.               Next
77.           Loop Until Occurrences = 0
78.       End Sub
79.
80.       Private Sub lstProducts_ColumnClick(ByVal sender As System.Object, _
81.           ByVal e As ColumnClickEventArgs) Handles lstProducts.ColumnClick
82.           Select Case e.Column
83.               Case 0
84.                    'Nome
85.                    Me.SortListViewItems(lstProducts, New ProductComparer())
86.               Case 1
87.                    'Prezzo
88.                    Me.SortListViewItems(lstProducts, New NumberComparer(True))
89.               Case 2
90.                    'Quantità
91.                    Me.SortListViewItems(lstProducts, New NumberComparer(False))
92.           End Select
93.       End Sub
94. End   Class
 Vb.net
B16. ToolStrip e TabControl


ToolStrip
Tutti conoscono benissimo l'inter faccia di Micr osoft Wor d, dove sopr a lo spazio in cui si scr ive ci sono mir idai di icone,
ognuna con la pr opr ia funzione (Salva, Apr i, Nuovo, Copia, Incolla ecc...): la bar r a degli str umenti, così chiamata, dove
sono collocate quelle icone è una ToolStr ip. Ecco osser var e un esempio di toolstr ip cr eata con vb.net:




Le pr opr ietà pr incipali di una toolstr ip:

        ImageScalingSize: molto impor tante, deter mina di che dimensione sar anno le immagini della toolstr ip; per
        impostar le della dimensione di quelle di Wor d si lasci pur e 16;16 (16x 16), mentr e per far la appar ir e con la
        stessa dimensione di quelle in immagine un 24;24 è accettabile (consiglier ei di non andar e tr oppo oltr e)
        Items: l'insieme degli elementi della toolstr ip; ciò che si può metter e nella toolstr ip è un piccolo gr uppo di
        contr olli nor malissimi, già analizzati, ossia: Button (un nor male pulsante: nell'immagine, Testi e Cr onologia sono
        Button); Dr opDow nItems (menù a discesa, identico a MenuStr ip: nell'immagine Documenti è un dr opdow nitem);
        SplitButton (una fusione tr a button e dr opdow nitems, poichè gode di un evento click pur essendo una lista a
        discesa); Label (una nor malissima etichetta di testo: nell'immagine Nome è una label); Tex tBox (casella di testo:
        nell'immagine il testo "Nicolo'" contenuto in una tex tbox ); ComboBox (lista a cascata: nell'immagine è il pr imo
        contr ollo della seconda r iga); Pr ogr essBar (ultimo contr ollo); Separ ator (un separ ator e, ossia una bar r a
        ver ticale che separ a gli elementi: nell'immagine è pr esente fr a Documenti e Nome)
        Tex tDir ection: dir ezione del testo

Per r ender e più aggr aziata la veste gr afica del pr ogr amma, una toolstr ip è molto utile.
Un'ultima cosa: facendo click col pulsante destr o sulla toolstr ip in fase di pr ogettazione, si dispor r à di var ie opzioni, fr a
cui quelle di Aggiunger e uno Str umento, Conver tir e un contr ollo in altr o tipo e Aggiunger e alla bar r a le icone standar d
di lavor o (ossia Apr i, Nuovo, Salva, Copia, Incolla e Taglia, già cor r edate di icona).




TabControl
TabContr ol è un contr ollo for mato da più schede sovr apposte, ognuna delle quali contiene al pr opr io inter no una
inter faccia diver sa. Questo contr ollo, infatti, insieme a Gr oupBox , SplitPanel e pochi altr i, è un contenitor e cr eato
appositamente per or dinar e più contr olli, in questo caso c'è la possibilità di stipar e una enor me quantità di layout in
poco spazio: ogni scheda (Tab) è accessibile con un click e cambia l'inter faccia visualizzata.




A seconda della pr opr ietà Appear ance, TabContr ol può pr esentar si sotto tr e vesti gr afiche differ enti, come mostr ato in
figur a: in or dine dall'alto al basso sono Nor mal, Buttons e FlatButtons. Per inser ir e uno o più contr olli all'inter no di una
scheda è sufficiente tr ascinar li con il mouse oppur e aggiunger li da codice facendo r ifer imento alla pr opr ietà TabPages.
Ecco la lista delle pr opr ietà più r ilevanti:

        Appear ance : lo stile di visualizzazione
        ItemSize : la dimensione dell'intestazione delle schede
        Multiline : se impostato su Tr ue, qualor a le schede fosser o tr oppe, ver r anno visualizzate diver se file di header
        una sopr a all'altr a; altr imenti appar ir anno due pulsantini a mò di scr ollbar che per metter anno di scor r er le
or izzontalmente. Nel pr imo caso, la pr opr ietà in sola lettur a Row Count r estituisce il numer o di r ighe
       visualizzate
       TabPages : collezione di tutte le schede disponibili sotto for ma di oggetti TabPage

Per por tar e in pr imo piano una scheda è possibile r ichiamar e da codice il metodo TabContr ol.SelectTab(Index ),
passando come unico par ametr o l'indice della scheda da r ilevar e, o, in alter nativa, tutto l'oggetto TabPage.
B17. NotifyIcon e SplitContainer


Notify Ic on
La par te infer ior e destr a della bar r a delle applicazioni di Window s è denominata System Tr ay e r aggr uppa tutte le
icone dei pr ogr ammi cor r entemente in esecuzione sul sistema oper ativo, ovviamente solo se questi ne r ichiedono una.
Ecco uno scr eenshot:




Per aggiunger e un'icona al pr ogetto, che ver r à automaticamente visualizzata in questo spazio dopo l'avvio
dell'applicazione, è sufficiente aggiunger e al designer un contr ollo NotifyIcon, che non ha inter faccia gr afica in ambiente
di sviluppo. Le pr opr ietà inter essanti sono queste:

         BaloonTipIcon : deter mina l'icona da visualizzar e a sinistr a del titolo del fumetto (si può sceglier e tr a er r or , info
         e w ar ning)
         BaloonTipTex t : testo del fumetto
         BaloonTipTitle : titolo del fumetto
         Icon : icona visualizzata nella system tr ay (si possono sceglier e solo file icona *.ico)
         Show BaloonTip(x ) : visualizza il fumetto dell'icona per x millisecondi (2000 è una buona media)
         Tex t : descr izione visualizzata quando il mouse sosta per qualche secondo sull'icona

Come molti altr i contr olli, anche questo suppor ta un menù contestuale gr azie al quale si possono eseguir e molte
oper azioni anche in assenza dell'inter faccia utente completa. Inoltr e vengono r egistr ati anche eventi come il Click o il
doppio Click del mouse sull'icona e mediante questi si può r idur r e il for m in modo che non appaia nella bar r a delle
applicazioni ma che pr esenzi solamente l'icona nella System Tr ay. Il codice da usar e in casi simili è molto semplice:

    1.    'Nasconde il form dalla barra delle applicazioni
    2.    Me.ShowInTaskBar = False
    3.    'Rende il form invisibile
    4.    Me.Visible = False
    5.    'Se l'icona non è già visibile, la rende visibile
    6.    Me.nftIcon.Visible = True

Per r ipor tar e tutto allo stato pr ecedente è sufficiente inver tir e i valor i booleani.



                                                              Fum etto




SplitContainer
Anche lo SplitContainer è un contenitor e, e può r ivelar si davver o molto utile. La sua peculiar ità consiste nel poter
r idimensionar e con il mouse, spostando quello che viene chiamato splitter , le due par ti del contr ollo. Ogni par te è una
super ficie contenitor e a sè stante e viene r appr esentata da un oggetto Panel. Ecco le pr opr ietà più significative:

         Bor der Style : pr opr ietà enumer ata che descr ive lo stile dei bor di: assenti (None), a linea singola (Single) o 3D
         (Fix ed3D)
         Fix edPanel : specifica quale dei         due pannelli     debba r estar e di     dimensioni     fisse dur ante l'atto di
         r idimensionamento
         IsSplitter Fix ed : deter mina se lo splitter è fisso o può muover si
Or ientation : indica l'or ientamento dei pannelli, se ver ticale o or izzontale
Panel1 : r ifer imento al pannello 1; gli Splitter Panel non hanno alcuna pr opr ietà differ ente da Contr ol, e per ciò
non vale la pena di soffer mar si altr o tempo su questi
Panel1Collapsed : deter mina se all'inizio il Panello 1 sia collssato, ossia pr ivo di dimensione, il che implica che solo
il Pannello 2 sia visibile
Panel1MinSize : la dimensione minima del Pannello 1; si r ifer isce alla lar ghezza se Or ientation = Ver tical,
altr imenti all'altezza
Panel2... : le stesse di Panel 1
Splitter Distance : la distanza dello splitter dall'angolo super ior e sinistr o, in pix el
Splitter Incr ement : l'incr emento della posizione splitter quando viene mosso dal mouse, in pix el
Splitter Width : la lar ghezza dello splitter , in pix el
B18. RichTextBox e Syntax Highlightning


La RichTex tBox è un contr ollo molto potente e dallo stile simile ai fogli di micr osoft w or d, che mantiene, tuttavia, un
layout w indow s 98. Costituisce un potenziamento della tex tbox nor male poichè è in gr ado di visualizzar e dei testi
for mattati, ossia contenenti tag che ne definiscono lo stile: gr assetto, sottolineato, bar r ato, cor sivo, color e,
gr andezza, font ecc... Come sugger isce il nome, in questi contr olli il più delle volte viene car icato un file con estensione
.r tf (r ich tex t for mat). Un esempio gr afico di come potr ebbe appar ir e un testo in una r ichtex tbox :




La pr opr ietà e i metodi più impor tanti di una r ichtex tbox sono:

       AppendTex t(t): aggiunge la str inga t al testo della r ichtex tbox
       CanRedo / CanUndo: pr opr ietà che deter minano qualor a sia possibile r ifar e o annullar e dei cambiamenti
       appor tati al testo
       CaseSensitive: deter mina se la r ix htex tbox faccia differ enza tr a le maiuscole o le minuscole o consider i
       solamente il testo (vedi Opzioni di Compilazione->Compar e)
       Clear : cancella tutto il testo della r ichtex tbox
       Clear Undo: cancella la lista che r ipor ta tutti i cambiamenti effettuati, così che non sia più possibile r ichiamar e la
       pr ocedur a Undo
       Copy / Cut / Paste: copia, taglia e incolla il testo selezionato dalla o nella clipboar d
       DefaultFont / DefaultFor eColor / DefaultBackColor : deter minano r ispettivamente il font, il color e del testo e il
       color e di sfondo pr eimpostati nella r ichtex tbox
       DeselectAll: deseleziona tutto (equivale a por r e SelectionLength = 0)
       DetectUr ls: deter mina qualor a tutti gli indir izzi ur l siano for mattati secondo il calssico stile blu sottlineato dei
       collegamenti iper testuali
       Find: impor tantissima funzione che per mette di tr ovar e qualsiasi str inga all'inter no del testo. Ne esistono 4
       ver sioni (in r ealtà 7, ma le altr e non sono impor tanti per or a) modificate tr amite over loading: la pr ima chiede
       di specificar e solo la str inga, la seconda anche le opzioni di r icer ca, la ter za anche l'indice da cui iniziar e la
       r icer ca e la quar ta anche l'indice a cui ter minar e la r icer ca. Gli indici r ifer iscono una posizione nel testo
       basandosi sul numer o di car atter i (r icor date, per ò, che gli indici in vb.net sono sempr e a base 0, quindi il pr imo
       car atter e avr à indice uguale a 0, il secondo a 1 e così via). Le opzioni di r icer ca sono 5, deter minate da un
       enumer ator e: MatchCase indica se pr ender e in consider azione anche la maiuscole e le minuscole; NoHighlight
       indica di non evidenziar e il testo tr ovato; None specifica di non far niente; Rever se specifica che bisogna
       tr ovar e la str inga al contr ar io; WholeWor d, invece, pr ecisa che la str inga deve esser e una par ola a sè stante,
       quindi, nalla maggior par te dei casi, separ ata da spazi o da punteggiatur a dalle altr e
       GetChar Fr omPosition(p) / GetChar Index Fr omPosition(p): funzioni che r estituiscono il car atter e (o il suo indice)
       che si tr ova in un punto pr eciso specificato come par ametr o p
       GetChar Index Fr omLine(n) / GetChar Index OfCur r entLine: funzioni che r estituiscono r ispettivamente l'indice del
       pr imo car atter e della linea n e l'indice del pr imo della linea cor r ente, ossia quella su cui è fer mo il cur sor e
       Lines: r estituisce un ar r ay di str inghe r appr esentanti il testo di ogni r iga della r ichtex tbox
       LoadFile(f): car ica il file f nella r ix htex tbox : f può esser e anche un nor male file di testo
       Rtf: r estituisce il testo della r ichtex tbox , includendo tutti i tag r tf
       SaveFile(f): salva il testo for mattato in un file
       Select(i, l) / SelectAll: la pr ima pr ocedur a seleziona un testo lungo l a par tir e dall'indice i, mentr e la seconda
seleziona tutto
       SelectedRtf / SelectedTex t: imposta o r estituisce il testo selezionato, sia in modo r tf (con i tag) che in modo
       nor male (solo testo)
       Selection...: tutte le pr opr ietà che iniziano con 'Selection' impostano o r estituiscono le opzioni del testo
       selezionato, come il font, il color e, l'indentazione, l'allineamento ecc... SelectionStar t indica l'indice a cui inizia la
       selezione, mentr e SelectionLength la sua lunghezza: impostar e questi due par ametr i equivale a r ichiamar e la
       funzione Select
       Undo / Redo: annulla l'ultima azione o la r ipete. Le pr opr ietà UndoActionName e RedoActionName r estituiscono il
       nome di quell'azione
       ZoomFactor : imposta o r estituisce il fattor e di ingr andimento della r ichtex tbox

Si è visto che le oper azioni che si possono eseguir e su questo contr ollo sono numer osissime, una più utile dell'altr a, ma
non è finita qui. Oltr e a esser e anche utilissima per contener e testo for mattato, la r ichtex tbox offr e anche str umenti
per modificar lo: uno di questi è il Syntax Highlighting, ossia l'evidenziator e di sintassi, pr esente in quasi ogni IDE per
linguaggi.




Sy ntax Highlighting
Questa tecnica consente di evidenziar e deter minate par ole chiave nel testo del contr ollo con un color e o uno stile
diver so dal r esto. È il caso delle par ole r iser vate. Sia con Visual Basic Ex pr ess che con Shar pDevelop o Visual Studio, le
keyw or d vengono evidenziate con un color e differ ente, di solito in blu. È possibile r ipr odur r e lo stesso compor tamento
nella Rix hTex tBox . Ho impiegato del tempo a tr ovar e un codice già fatto r iguar do questo ar gomento e, dopo aver
cer cato molto, ci sono r iuscito: sono giunto alla conclusione che questo sia il miglior e della r ete, anche se si può
sempr e appor tar e qualche cor r ezione.
Si apr a un nuovo pr ogetto Libr er ia di Classi, e s'incolli tutto il codice nella classe Syntax RTB, dopodichè si clicchi
Build->Build [Nome pr ogetto] per gener ar e il contr ollo. Nonostante non si sia specificato che la classe r appr esenti un
contr ollo, il fatto che essa der ivi da RichTex tBox l'ha implicitamente sugger ito al compilator e. Syntax RTB non è altr o
che una RichTex tBox con dei metodi in più per il syntax highlighting. Si tr ascini il contr ollo sul for m nor malmente come
una tex tbox .
Ecco la classe commentata e r ior dinata:

 001. Public Class SyntaxRTB
 002.     Inherits System.Windows.Forms.RichTextBox
 003.
 004.     'La funzione SendMessage serve per inviare dati messaggi
 005.     'a una finestra o un dispositivo allo scopo di ottenere
 006.     'dati valori od eseguire dati compiti
 007.     Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
 008.        (ByVal hWnd As IntPtr, ByVal wMsg As Integer, _
 009.        ByVal wParam As Integer, ByVal lParam As Integer) As Integer
 010.
 011.     'Blocca il Refresh della finestra
 012.     Private Declare Function LockWindowUpdate Lib "user32" _
 013.         (ByVal hWnd As Integer) As Integer
 014.
 015.     'Campo privato che specifica se il meccanismo di syntax
 016.     'highlighting è case sensitive oppure no
 017.     Private _SyntaxHighlight_CaseSensitive As Boolean = False
 018.     'La tabella delle parole
 019.     Private Words As New DataTable
 020.
 021.     Public Property CaseSensitive() As Boolean
 022.         Get
 023.              Return _SyntaxHighlight_CaseSensitive
 024.         End Get
 025.         Set(ByVal Value As Boolean)
 026.              _SyntaxHighlight_CaseSensitive = Value
 027.
End Set
028.   End Property
029.
030.   'Contiene costanti usate nell'inviare messaggi all'API
031.   'di windows
032.   Private Enum EditMessages
033.       LineIndex = 187
034.       LineFromChar = 201
035.       GetFirstVisibleLine = 206
036.       CharFromPos = 215
037.       PosFromChar = 1062
038.   End Enum
039.
040.   'OnTextChanged è una procedura privata che ha il compito
041.   'di generare l'evento TextChanged: prima di farlo, colora il
042.   'testo, ma in questo caso l'evento non viene più generato
043.   Protected Overrides Sub OnTextChanged(ByVal e As EventArgs)
044.       ColorVisibleLines()
045.   End Sub
046.
047.   'Colora tutta la RichTextBox
048.   Public Sub ColorRtb()
049.       Dim FirstVisibleChar As Integer
050.       Dim i As Integer = 0
051.
052.       While i < Me.Lines.Length
053.           FirstVisibleChar = GetCharFromLineIndex(i)
054.           ColorLineNumber(i, FirstVisibleChar)
055.           i += 1
056.       End While
057.   End Sub
058.
059.   'Colora solo le linee visibili
060.   Public Sub ColorVisibleLines()
061.       Dim FirstLine As Integer = FirstVisibleLine()
062.       Dim LastLine As Integer = LastVisibleLine()
063.       Dim FirstVisibleChar As Integer
064.
065.       If (FirstLine = 0) And (LastLine = 0) Then
066.            'Non c'è testo
067.            Exit Sub
068.       Else
069.            While FirstLine < LastLine
070.                FirstVisibleChar = GetCharFromLineIndex(FirstLine)
071.                ColorLineNumber(FirstLine, FirstVisibleChar)
072.                FirstLine += 1
073.            End While
074.       End If
075.
076.   End Sub
077.
078.   'Colora una linea all'indice LineIndex, a partire dal carattere
079.   'lStart
080.   Public Sub ColorLineNumber(ByVal LineIndex As Integer, _
081.       ByVal lStart As Integer)
082.       Dim i As Integer = 0
083.       Dim SelectionAt As Integer = Me.SelectionStart
084.       Dim MyRow As DataRow
085.       Dim Line() As String, MyI As Integer, MyStr As String
086.
087.       'Blocca il refresh
088.       LockWindowUpdate(Me.Handle.ToInt32)
089.
090.       MyI = lStart
091.
092.       If CaseSensitive Then
093.            Line = Split(Me.Lines(LineIndex).ToString, " ")
094.       Else
095.            Line = Split(Me.Lines(LineIndex).ToLower, " ")
096.       End If
097.
098.       For Each MyStr In Line
099.
'Seleziona i primi MyStr.Length caratteri della linea,
100.              'ossia la prima parola
101.              Me.SelectionStart = MyI
102.              Me.SelectionLength = MyStr.Length
103.
104.              'Se la parola è contenuta in una delle righe
105.              If Words.Rows.Contains(MyStr) Then
106.                   'Seleziona la riga
107.                   MyRow = Words.Rows.Find(MyStr)
108.                   'Quindi colora la parola prelevando il colore da
109.                   'tale riga
110.                   If (Not CaseSensitive) Or _
111.                       (CaseSensitive And MyRow("Word") = MyStr) Then
112.                       Me.SelectionColor = Color.FromName(MyRow("Color"))
113.                   End If
114.              Else
115.                   'Altrimenti lascia il testo in nero
116.                   Me.SelectionColor = Color.Black
117.              End If
118.
119.              'Aumenta l'indice di un fattore pari alla lunghezza
120.              'della parola più uno (uno spazio)
121.              MyI += MyStr.Length + 1
122.       Next
123.
124.       'Ripristina la selezione
125.       Me.SelectionStart = SelectionAt
126.       Me.SelectionLength = 0
127.       'E il colore
128.       Me.SelectionColor = Color.Black
129.
130.       'Riprende il refresh
131.       LockWindowUpdate(0)
132.   End Sub
133.
134.   'Ottiene il primo carattere della linea LineIndex
135.   Public Function GetCharFromLineIndex(ByVal LineIndex As Integer) _
136.       As Integer
137.       Return SendMessage(Me.Handle, EditMessages.LineIndex, LineIndex, 0)
138.   End Function
139.
140.   'Ottiene la prima linea visibile
141.   Public Function FirstVisibleLine() As Integer
142.       Return SendMessage(Me.Handle, EditMessages.GetFirstVisibleLine, 0, 0)
143.   End Function
144.
145.   'Ottiene l'ultima linea visibile
146.   Public Function LastVisibleLine() As Integer
147.       Dim LastLine As Integer = FirstVisibleLine() + _
148.           (Me.Height / Me.Font.Height)
149.
150.       If LastLine > Me.Lines.Length Or LastLine = 0 Then
151.           LastLine = Me.Lines.Length
152.       End If
153.
154.       Return LastLine
155.   End Function
156.
157.   Public Sub New()
158.       Dim MyRow As DataRow
159.       Dim arrKeyWords() As String, strKW As String
160.
161.       Me.AcceptsTab = True
162.
163.       'Carica la colonna Word e Color
164.       Words.Columns.Add("Word")
165.       Words.PrimaryKey = New DataColumn() {Words.Columns(0)}
166.       Words.Columns.Add("Color")
167.
168.       'Aggiunge le keywords del linguaggio SQL all'array
169.       arrKeyWords = New String() {"select", "insert", "delete", _
170.          "truncate", "from", "where", "into", "inner", "update", _
171.
"outer", "on", "is", "declare", "set", "use", "values", "as", _
 172.             "order", "by", "drop", "view", "go", "trigger", "cube", _
 173.             "binary", "varbinary", "image", "char", "varchar", "text", _
 174.             "datetime", "smalldatetime", "decimal", "numeric", "float", _
 175.             "real", "bigint", "int", "smallint", "tinyint", "money", _
 176.             "smallmoney", "bit", "cursor", "timestamp", "uniqueidentifier", _
 177.             "sql_variant", "table", "nchar", "nvarchar", "ntext", "left", _
 178.             "right", "like", "and", "all", "in", "null", "join", "not", "or"}
 179.
 180.         'Quindi le aggiunge una alla volta alla tabella con
 181.         'colore rosso
 182.         For Each strKW In arrKeyWords
 183.              MyRow = Words.NewRow()
 184.              MyRow("Word") = strKW
 185.              MyRow("Color") = Color.LightCoral.Name
 186.              Words.Rows.Add(MyRow)
 187.         Next
 188.     End Sub
 189. End Class

Il costr uttor e New ha il compito di inizializzar e tutte le infor mazioni iner enti alle par ole ed al lor o color e. La
str uttur a della classe utilizza una DataTable in cui ci sono due colonne: Wor d, la par ola da evidenziar e, e Color , il color e
da usar e per l'evidenziazione. Ogni r iga contiene quindi queste due infor mazioni, e ci sono tante r ighe quante sono le
keyw or ds del linguaggio che si desider a. Color LineNumber è invece commentata nel sor gente.
Questi metodi, per ò, sebbene funzionino con il linguaggio di r ifer imento (SQL), per dono di ogni validità con l'HTML, dove
le par ola chiave sono attaccate le une alle altr e, ad esempio in:

 <a href='http://totem.altervista.org'>Link</a>


a viene subito dopo la par entesi angolar e, mentr e hr ef pr ima di un uguale. Nonostante il modo più pr eciso in assoluto
per scovar e le keyw or ds sia usar e le espr essioni r egolar i, non ancor a anlizzate, per or a si far à in altr o modo. Ecco la
classe r iscr itta da me, in modo da adeguar e il funzionamento all'HTML e miglior ando le pr estazioni:

 001. Public Class SHRichTextBox
 002.     Inherits System.Windows.Forms.RichTextBox
 003.
 004.     Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
 005.        (ByVal hWnd As IntPtr, ByVal wMsg As Integer, _
 006.        ByVal wParam As Integer, ByVal lParam As Integer) As Integer
 007.
 008.     Private Declare Function LockWindowUpdate Lib "user32" _
 009.         (ByVal hWnd As Integer) As Integer
 010.
 011.     Private Enum EditMessages
 012.         LineIndex = 187
 013.         LineFromChar = 201
 014.         GetFirstVisibleLine = 206
 015.         CharFromPos = 215
 016.         PosFromChar = 1062
 017.     End Enum
 018.
 019.     Protected Overrides Sub OnTextChanged(ByVal e As EventArgs)
 020.         'Non colora tutte le linee visibili, bensì solo la riga
 021.         'dove si trova il cursorse: in questo modo l'applicazione
 022.         'risulta più veloce. L'unico caso in cui questo
 023.         'approccio non funzione è quando si copia un testo
 024.         'all'interno della richtextbox. In quel caso ci sarà
 025.         'un pulsante apposito
 026.         Dim LineIndex As Int32 = Me.GetLineFromCharIndex(Me.SelectionStart)
 027.         Me.ColorLineNumber(LineIndex)
 028.     End Sub
 029.
 030.     'Colora tutta la RichTextBox
 031.     Public Sub ColorRtb()
 032.         For I As Int32 = 0 To Me.Lines.Length - 1
 033.              ColorLineNumber(I)
 034.         Next
 035.
End Sub
036.
037.   'Colora solo le linee visibili
038.   Public Sub ColorVisibleLines()
039.       Dim FirstLine As Integer = FirstVisibleLine()
040.       Dim LastLine As Integer = LastVisibleLine()
041.
042.       If (FirstLine = 0) And (LastLine = 0) Then
043.            'Non c'è testo
044.            Exit Sub
045.       Else
046.            While FirstLine < LastLine
047.                ColorLineNumber(FirstLine)
048.                FirstLine += 1
049.            End While
050.       End If
051.   End Sub
052.
053.   'Questa è la nuova versione: nelle stesse condizioni sopra
054.   'citate, impiega 50ms, quasi la metà! L'algoritmo vecchio
055.   'per SQL ne impiegava 10, ma non era in grado di supportare tag
056.   'vicini come quelli dell'HTML
057.   Public Sub ColorLineNumber(ByVal LineIndex As Int32)
058.       Try
059.           If Me.Lines(LineIndex).Length = 0 Then
060.                Exit Sub
061.           End If
062.       Catch Ex As Exception
063.           Exit Sub
064.       End Try
065.
066.       'Indice del primo carattere della linea
067.       Dim FirstCharIndex As Int32 = _
068.           Me.GetFirstCharIndexFromLine(LineIndex)
069.       'Tiene traccia del cursore
070.       Dim SelectionAt As Integer = Me.SelectionStart
071.
072.       'Blocca il refresh
073.       LockWindowUpdate(Me.Handle.ToInt32)
074.
075.       'Tiene traccia se ci siano tag aperti
076.       Dim TagOpened As Boolean = False
077.       'Indica se il tag ha degli attributi
078.       Dim Attribute As Boolean = False
079.       'Indica se un attributo è stato assegnato
080.       Dim Assigned As Boolean = False
081.       'Indica, per gli attributi come [readonly], se le parentesi
082.       'sono state aperte
083.       Dim AttributeOpened As Boolean = False
084.       'Variabili locali che rappresentano Me.SelectionStart e
085.       'Me.SelectionLength: usando la variable enregistration si
086.       'guadagna qualche millisecondo
087.       Dim Start, Length As Int32
088.       Dim Max As Int32 = _
089.           (FirstCharIndex + Me.Lines(LineIndex).Length) - 1
090.
091.       Me.Select(FirstCharIndex, Max + 1)
092.
093.       For Index As Int32 = FirstCharIndex To Max
094.           If Char.IsLetterOrDigit(Me.Text(Index)) Then
095.               Continue For
096.           End If
097.           'Viene aperto un tag, inizia a selezionare
098.           'Es.: <a
099.           If Me.Text(Index) = "<" Then
100.               Start = Index
101.               TagOpened = True
102.               Attribute = False
103.               Assigned = False
104.           ElseIf Me.Text(Index) = ">" Then
105.               'Viene chiuso un tag: se sono stati definiti
106.               'attributi, evidenzia solo la parentesi angolare,
107.
'Es.: <a href='www.example.com'>
108.                  'altrimenti tutta la stringa da "<" a ">"
109.                  'Es.: <div>
110.                  If Not Attribute Then
111.                       Length = Index - Start
112.                       Me.Select(Start, Length)
113.                       Me.SelectionColor = Color.Blue
114.                  End If
115.                  Me.Select(Index, 1)
116.                  Me.SelectionColor = Color.Blue
117.                  Me.DeselectAll()
118.                  TagOpened = False
119.                  Attribute = False
120.                  Assigned = False
121.              ElseIf TagOpened AndAlso Me.Text(Index) = " " Then
122.                  'Uno spazio: se un attributo è già stato impostato,
123.                  'si tratta di uno spazio che separa due attributi,
124.                  'quindi passa oltre, definendo solo
125.                  'Assigned = False;
126.                  'Es.: <div id='1' class='prova'>
127.                  'altrimenti è uno spazio che precede qualsiasi
128.                  'attributo, che quindi viene dopo la dichiarazione
129.                  'del tag, che viene colorato in blu
130.                  'Es.: <div id='1'>
131.                  If Assigned Then
132.                       Assigned = False
133.                  Else
134.                       Length = Index - Start
135.                       Me.Select(Start, Length)
136.                       Me.SelectionColor = Color.Blue
137.                  End If
138.                  Me.DeselectAll()
139.                  Start = Index + 1
140.              ElseIf TagOpened AndAlso Me.Text(Index) = "=" Then
141.                  'Un uguale: a un attributo viene assegnato un
142.                  'valore, perciò evidenzia l'attributo,
143.                  'dallo spazio precedente fino a = non compreso,
144.                  'e lo colore in rosso
145.                  'Es.: <table width='100'>
146.                  Length = Index - Start
147.                  Me.Select(Start, Length)
148.                  Me.SelectionColor = Color.Red
149.                  Me.DeselectAll()
150.                  Attribute = True
151.                  Assigned = True
152.              ElseIf Me.Text(Index) = "[" Then
153.                  'Apre un attributo
154.                  Start = Index
155.                  AttributeOpened = True
156.              ElseIf Me.Text(Index) = "]" And AttributeOpened Then
157.                  'Chiude un attributo
158.                  'Es.: <input type='text' [readonly]>
159.                  Length = Index - Start
160.                  Me.Select(Start, Length)
161.                  Me.SelectionColor = Color.Red
162.                  Me.DeselectAll()
163.                  AttributeOpened = False
164.              End If
165.       Next
166.
167.       'Ripristina la selezione
168.       Me.SelectionStart = SelectionAt
169.       Me.SelectionLength = 0
170.       'E il colore
171.       Me.SelectionColor = Color.Black
172.
173.       'Riprende il refresh
174.       LockWindowUpdate(0)
175.   End Sub
176.
177.   'Ottiene la prima linea visibile
178.   Public Function FirstVisibleLine() As Integer
179.
Return SendMessage(Me.Handle, EditMessages.GetFirstVisibleLine, 0, 0)
 180.         End Function
 181.
 182.         'Ottiene l'ultima linea visibile
 183.         Public Function LastVisibleLine() As Integer
 184.             Dim LastLine As Integer = FirstVisibleLine() + _
 185.                 (Me.Height / Me.Font.Height)
 186.
 187.              If LastLine > Me.Lines.Length Or LastLine = 0 Then
 188.                  LastLine = Me.Lines.Length
 189.              End If
 190.
 191.             Return LastLine
 192.         End Function
 193. End     Class

In questa ver sione modificate ci sono par ecchie diver genze:

       Non viene utilizzata una tabella dei color i: il motivo è semplice; viene eseguito un contr ollo un car atter e alla
       volta e, quale che sia il nome del tag e dell'attr ibuto specificato, viene comunque color ato. Questa car atter istica
       ha dei pr egi e dei difetti. Non evidenzia gli er r or i, ma in questo caso si può sempr e r ipr istinar e la tabella
       per dendo un po' di velocità. Tuttavia evidenzia anche i tag nuovi che vengono usati dai css: ad esempio, questa
       pagina usava dei tag "<k>", che non esistono nell'HTML ma sono pur sempr e tag, e vengono usati per definir e le
       keyw or ds e per color ar e il listato. Se si consider a la pr ima ipotesi, sar ebbe meglio utilizzar e una collezione a
       dizionar io a tipizzazione for te, per spr ecar e meno memor ia.
       Non divide la str inga: analizza semplicemente un car atter e per volta dall'inizio alla fine. Questo pr ocedimento è
       assai più r apido e ovviamente non funzioner ebbe con uno split, dato che i tag sono attaccati l'uno all'altr o
       Non utilizza Color Rtb su OnTex tChanged: dato che il contr ollo è pr ogettato per aiutar e nella scr ittur a, si
       suppone che chi immetta il codice stia scr ivendo, quindi color a soltanto la linea su cui si sta oper ando e non
       tutte le linee visibili. Questo contr ibuisce a velocizzar e il meccanismo

Per chi avesse letto la ver sione pr ecedente della guida, si sar à cer tamente notato il cambiamento r adicale di algor itmo
utilizzato, r ispetto a quello più r udimentale:

   01. For Each Word As String In Words
   02.      I = FirstCharIndex
   03.      Do
   04.          I = Me.Find(Word, I, I + Me.Lines(LineIndex).Length, _
   05.              RichTextBoxFinds.None)
   06.          If I >= 0 Then
   07.              Me.SelectionStart = I
   08.              Me.SelectionLength = Word.Length
   09.              'Qui utilizo un dictionary
   10.              Me.SelectionColor = Words(Word)
   11.             I += Word.Length
   12.          End If
   13.      Loop While I >= 0
   14. Next

Quest'ultimo color a solo le par ole indicate, ma esegue almeno (almeno!) un centinaio di contr olli ogni volta, ossia uno
per ogni par ola data: se poi queste appaiono nella r iga, il conto r addoppia! Questo appr occio, per far e un esempio, su
una linea di 37 car atter i con cinque o sei par ole r iser vate, impiega cir ca 90ms per color ar e, ed il tempo aumenta
ver tiginosamente di 10/20ms per ogni car atter e in più. Nel nuovo algor itmo, il tempo è r idotto a cir ca 50ms, con un
aumento di 2/3ms per ogni car atter e in più. L'algor itmo iniziale, invece, dovendo analizzar e solo il numer o di par ole
della str inga, impiegava, sempr e nelle stesse condizioni, cir ca 10ms, con un aumento di 1/2ms ogni par o la in più.
(Bisogna per ò r icor dar e che il pr imo pr oposto color ava tutte le linee visibili ad ogni modifica). Si può capir e quindi
come sia vantaggioso quello iniziale in ter mini di tempo, e quanto svantaggioso in ter mini di pr estazioni.
Esem pio di Syntax Hig hlig hting
B19. PropertyGrid


Questo contr ollo è davver o molto complesso: r appr esenta una gr iglia delle pr opr ietà, esattamente la stessa che lo
sviluppator e usa per modificar e le car atter istiche dei var i contr olli nel for m designer . La sua enor me potenza sta nel
fatto che, attr aver so la r eflection, r iesce a gestir e qualsiasi oggetto con facilità. Le si può associar e un contr ollo del
for m, su cui l'utente può agir e a pr opr io piacimento, ma anche una classe, ad esempio le opzioni del pr ogr amma, con
cui sar à quindi possibile inter agir e molto semplicemente da un'unica inter faccia. Le pr opr ietà e i metodi impor tanti
sono:

        CollapseAllGr idItems : r iduce al minimo tutte le categor ie
        Ex pandAllGr idItems : espande al massimo tutto le categor ie
        Pr oper tySor t : pr opr ietà enumer ata che indica come debbano esser e or dinati gli elementi, se alfabeticamente,
        per categor ie, per categor ie e alfabeticamente oppur e senza alcun or dinamento
        Pr oper tyTabs : collezione di tutte le possibili schede della Pr oper tyGr id. Una scheda, ad esempio, è costituita dal
        pulsante "Or dina alfabeticamente", oppur e, nell'ambiente di sviluppo, dal pulsante "Mostr a eventi" (quello con
        l'icona del fulmine). Aggiunger ne una significa aggiunger e un pulsante che possa modificar e il modo in cui il
        contr ollo legge i dati dell'oggetto. Ecco un esempio pr eso da un ar ticolo sull'ar gomento r eper ibile su The Co de
        Pr o ject:



        SelectedGr idItem : r estituisce l'elemento selezionato, un oggetto Gr idItem che gode di queste pr opr ietà:
                Ex pandable : indica se l'elemento è espandibile. Sono espandibili tutte quelle pr opr ietà il cui tipo sia un
                tipo r efer ence: in par ole pover e, essa deve espor r e al pr opr io inter no altr e pr opr ietà (non sono
                soggetti a questo compor tamento le str uttur e, in quanto tipi value, a meno che esse non espongano a
                lor o volta delle pr opr ieta'). Per i tipi definiti dal pr ogr ammator e, la Pr oper tyGr id non è in gr ado di
                for nir e una r appr esentazione che possa esser e espansa a r un-time: a questo si può supplir e in modo
                semplice facendo uso di cer ti attr ibuti come si vedr à fr a poco
                Ex panded : indica se l'elemento è cor r entemente espanso (sono visibili tutti i suoi membr i)
                Gr idItems : se Ex pandable = Tr ue, questa pr opr ietà r estituisce una collezione di oggetti Gr idItem che
                r appr esentano tutte le pr opr ietà inter ne a quella cor r ente
                Gr idItemType : pr opr ietà enumer ata in sola lettur a che specifica il tipo di elemento. Può assumer e
                quattr o valor i: Ar r ayValue (un oggetto ar r ay o a una collezione in gener e), Categor y (una categor ia),
                Pr oper ty (una qualsiasi pr opr ieta') e Root (una pr opr ietà di pr imo livello, ossia che non possiede alcun
                livello ger ar chico al di sopr a di se stessa)
                Label : il testo dell'elemento
                Par ent : se la pr opr ietà è un membr o d'istanza di un'altr a pr opr ietà, r estituisce quest'ultima (ossia quella
                che sta al livello ger ar chico super ior e)
                Pr oper tyDescr iptor : r estituisce un oggetto che indica come si compor ta la pr opr ietà nella gr iglia, quale
                sia il suo testo, la descr izione, se sia modificabile o meno (a r un-time o solo dur ante la scr ittur a del
                pr ogr amma), se sia visualizzata nella gr iglia, quale sia il delegate da invocar e nel momento in cui questa
                viene modificata e infine, il più impor tante, l'oggetto usato per conver tir e tutta la pr opr ietà in un
                valor e sintetico di tipo str inga. Tutti questi attr ibuti sono specificati dur ante la scr ittur a di una
                pr opr ietà che suppor ti la visualizzazione in una Pr oper tyGr id, come si vedr à in seguito
                Value : r estituisce il valor e della pr opr ieta'
SelectedObject : la pr opr ietà più impor tante. Imposta l'oggetto che Pr oper tyGr id gestisce: ogni modifica
          dell'utente sul contr ollo si r iper cuoter à in manier a identica sull'oggetto, esattamente come avviene nell'ambiente
          di sviluppo; vengono anche inter cettati tutti gli er r or i di casting e gestiti automaticamente
          SelectedObjects : è anche possibile far sì che vengano gestiti più oggetti contempor aneamente. Se questi sono
          dello stesso tipo, ogni modifica si r iper cuoter à su ognuno nella stessa manier a. Se sono di tipo diver so,
          ver r anno visualizzate solo le pr opr ietà in comune
          SelectedTab : r estituisce la scheda selezionata

In questo capitolo mi concentr er ò sul caso in cui si debba inter facciar e Pr oper tyGr id con un oggetto nuovo cr eato da
codice.




Binding di c lassi c reate dal programmatore
Per far sì che Pr oper tyGr id visualizzi cor r ettamente una classe cr eata dal pr ogr ammator e, basta assegnar e un
oggetto di quel tipo alla pr opr ietà SelectedObject, poichè tutto il pr ocesso viene svolto tr amite r eflection. Tuttavia ci
sono alcune situazioni in cui questo pr ocesso ha bisogno di un aiuto ester no per funzionar e: quando le pr opr ietà sono
di tipo r efer ence (str inghe escluse), non vengono visulizzati tutti i lor o membr i, poichè il contr ollo non è in gr ado di
conver tir e un valor e adatto in str inga. Ad esempio, se si deve legger e un oggetto di tipo Per son, il nome e la data di
nascita ver r anno analizzati cor r ettamente, ma il campo Fr etello As Per son come ver r à inter pr etato? Non è possibile
far star e una classe su una sola r iga, poichè non si conosce il modo di conver tir la in un valor e r appr esentabile (in
questo caso, in una str inga). Lo str umento che Vb.Net for nisce per ar ginar e questo pr oblema è un attr ibuto, di nome
TypeConver ter , definito nel namespace System.ComponentModel (dove, tr a l'altr o, sono situati tutti gli altr i attr ibuti
usati in questo capitolo). Questo accetta come costr uttor e un par ametr o di tipo Type, che espone il tipo di una classe
con la funzione di conver titor e. Ad esempio:

   01.     'Questa classe ha la funzione di convertire Person in stringa
   02.     Public Class PersonConverter
   03.         '(Per convenzione, i convertitori di questo tipo, devono
   04.         'terminare con la parola "Converter"
   05.           '...
   06.     End Class
   07.
   08.     Public Class Person
   09.         Private _Name As String
   10.         Private _Birthday As Date
   11.         Private _Brother As Person
   12.
   13.          '...
   14.
   15.         'Per la proprietà Brother (fratello), si applica l'attributo
   16.         'TypeConverter, specificando quale sia la classe convertitore.
   17.         'Si utilizza solo il tipo perchè la classe, come vedremo
   18.         'in seguito, espone solo metodi d'istanza, ma che possono
   19.         'essere utilizzati da soli semplicemente fornendo i parametri
   20.         'adeguati. Perciò sarà il programma stesso a creare,
   21.         'a runtime, un oggetto di questo tipo e ad usarne la funzioni
   22.         <TypeConverter(GetType(PersonConverter))> _
   23.         Public Property Brother() As Person
   24.         '...
   25.     End Class

Ecco un esempio di come si pr esenter à il contr ollo dopo aver for nito queste dir ettive:




La classe che implementa il conver titor e deve er editar e da Ex pandableObjectConver ter (una classe definita anch'essa in
System.ComponentModel) e deve sovr ascr iver e tr amite polimor fismo alcune funzioni: CanConver tFr om (deter mina se
si può conver tir e da tipo dato), CanConver tTo (deter mina se si può conver tir e nel tipo dato), Conver tFr om (conver te,
in questo caso, da Str ing a Per son, e in gener ale al tipo di cui si sta scr ivendo il conver titor e), Conver tTo (conver te, in
questo caso, da Per son a Str ing, e in gener ale dal tipo in questione a str inga).
Questa er a la par te più difficile, di cui si avr à un buon esempio nel codice a seguir e: quello che bisogna anlizzar e or a
consente di definir e alcune piccole car atter istiche per per sonalizzar e l'aspetto di una pr opr ietà. Ecco una lista degli
attr ibuti usati e delle lor o descr izioni:

         DisplayName : modifica il nome della pr opr ietà in modo che venga visualizzata a r un-time un'altr a str inga.
         Accetta un solo par ametr o del costr uttor e, il nuovo nome (nell'esempio, si r impiazza la denominazione inglese
         con la r ispettiva tr aduzione italiana)
         Descr iption : definisce una piccola descr izione per la pr opr ieta'
         Br ow sable : deter mina se il valor e della pr opr ietà sia modificabile dal contr ollo: l'unico par ametr o del
         costr uttor e è un valor e Boolean
         [ReadOnly] : indica se la pr opr ietà è in sola lettur a oppur e no. Come Br ow sable accetta un unico par ametr o
         booleano
         DesignOnly : specifica se la pr opr ietà si possa modificar e solo dur ante la scr ittur a del codice e non dur ante
         l'esecuzione
         Categor y : il nome della categor ia sotto la quale deve venir e r ipor tata la pr opr ietà: l'unico par ametr o è di tipo
         Str ing
         DefaultValue : il valor e di default della pr opr ietà. Accetta diver si over load, a seconda del tipo
         DefaultPr oper ty : applicato alla classe che r appr esenta il tipo dell'oggetto visualizzato, indica il nome della
         pr opr ietà che è selezionata di default nella Pr oper tyGr id



Pr ima di pr oceder e con il codice, ecco uno scr eenshot di come dovr ebbe appar ir e la veste gr afica in fase di
pr ogettazione:




C'è anche un'ImageList con un'immagine per gli elementi della listview lstBooks e un Contex tMenuStr ip che contiene le
voci "Aggiungi" e "Rimuovi", sempr e assegnato alla listview . Inoltr e, sia la lista che la Pr oper tyGr id sono inser ite
all'inter no di uno SplitContiner . Ecco il codice della libr er ia (nel Solution Ex plor er , cliccar e con il pulsante destr o sul
pr ogetto, quindi sceglier e Add New Item e poi Class Libr ar y):

  001.    'Questo namespace contiene gli attributi necessari a
  002.    'impostare le proprietà in modo che si interfaccino
  003.    'correttamente con PropertyGrid
  004.    Imports System.ComponentModel
  005.
  006.    'Quando si usa uno statementes Imports, la prima voce
  007.    'si riferisce al nome del file *.dll in s?. Dato che si
  008.    'vuole BooksManager sia consierato come una namespace, non
  009.    'bisogna aggiungere un altro namespace BooksManager in questo file
  010.
  011.    'L'autore del libro, con eventuale biografia
  012.    Public Class Author
  013.        'Il nome completo
  014.        Private _Name As String
  015.        'Data di nascita e morte
  016.        Private _Birth, _Death As Date
  017.        'Indica se l'autore è ancora vivo
  018.        Private _IsStillAlive As Boolean
  019.        'Una piccola biografia
  020.        Private _Biography As String
  021.
  022.             <DisplayName("Nome"), _
  023.                     Description("Il nome dell'autore."), _
  024.                     Browsable(True), _
  025.
Category("Generalita'")> _
026.   Public Property Name() As String
027.       Get
028.           Return _Name
029.       End Get
030.       Set(ByVal Value As String)
031.           _Name = Value
032.       End Set
033.   End Property
034.
035.   <DisplayName("Piccola biografia"), _
036.       Description("Un riassunto delle parti più significative della " & _
037.           "vita dell'autore."), _
038.       Browsable(True), _
039.       Category("Dettagli")> _
040.   Public Property Biography() As String
041.       Get
042.           Return _Biography
043.       End Get
044.       Set(ByVal Value As String)
045.           _Biography = Value
046.       End Set
047.   End Property
048.
049.   <DisplayName("Data di nascita"), _
050.       Description("La data di nascita dell'autore."), _
051.       Browsable(True), _
052.       Category("Generalita'")> _
053.   Public Property Birth() As Date
054.       Get
055.           Return _Birth
056.       End Get
057.       Set(ByVal Value As Date)
058.           'Nessun controllo: la data di nascita può essere
059.           'spostata a causa di uno sbaglio, che altrimenti
060.           'potrebbe produrre un'eccezione
061.           _Birth = Value
062.       End Set
063.   End Property
064.
065.   <DisplayName("Data di morte"), _
066.       Description("Data di morte dell'autore."), _
067.       Browsable(True), _
068.       Category("Generalita'")> _
069.   Public Property Death() As Date
070.       Get
071.           Return _Death
072.       End Get
073.       Set(ByVal Value As Date)
074.           'Bisogna assicurarsi che la data di morte sia
075.           'posteriore a quella di nascita
076.           If Value.CompareTo(Me.Birth) < 1 Then
077.                'Genera un'eccezione
078.                Throw New ArgumentException("La data di morte deve " & _
079.                "essere posteriore a quella di nascita!")
080.           Else
081.                'Prosegue l'assegnazione
082.                _Death = Value
083.                'Impostando la data di morte si suppone che l'autore
084.                'non sia più in vita...
085.                Me.IsStillAlive = False
086.           End If
087.       End Set
088.   End Property
089.
090.   <DisplayName("Vive"), _
091.       Description("Determina se l'autore è ancora in vita."), _
092.       Browsable(True), _
093.       Category("Generalita'")> _
094.   Public Property IsStillAlive() As Boolean
095.       Get
096.           Return _IsStillAlive
097.
End Get
098.         Set(ByVal Value As Boolean)
099.              _IsStillAlive = Value
100.         End Set
101.     End Property
102.
103.     'Un nome e una data di nascita sono obbligatori
104.     Sub New(ByVal Name As String, ByVal Birth As Date)
105.         Me.Name = Name
106.         Me.Birth = Birth
107.         Me.IsStillAlive = True
108.     End Sub
109.
110.     'Tuttavia, il controllo PropertyGrid richiede un costruttore
111.     'senza parametri
112.     Sub New()
113.         Me.Birth = Date.Now
114.         Me.IsStillAlive = True
115.     End Sub
116. End Class
117.
118. Public Class IsbnConverter
119.     'Facendo derivare questa classe da ExpandableObjectConverter
120.     'si comunica al compilatore che questa classe è usata per
121.     'convertire in stringa un valore rappresentabile in una
122.     'PropertyGrid. Così facendo, sarà possibile modificare
123.     'il codice agendo sulla stringa complessiva e non
124.     'obbligatoriamente sulle varie parti
125.     Inherits ExpandableObjectConverter
126.
127.     'Determina se sia possibile convertire nel tipo dato
128.     Public Overrides Function CanConvertTo(ByVal Context As ITypeDescriptorContext, _
129.         ByVal DestinationType As Type) As Boolean
130.         'Si può convertire in Isbn, dato che questa classe è
131.         'scritta apposta per questo
132.         If (DestinationType Is GetType(Isbn)) Then
133.              Return True
134.         End If
135.         Return MyBase.CanConvertFrom(Context, DestinationType)
136.     End Function
137.
138.     'Determina se sia possibile convertire dal tipo dato
139.     Public Overrides Function CanConvertFrom(ByVal Context As ITypeDescriptorContext, _
140.         ByVal SourceType As Type) As Boolean
141.         'Si può convertire da String, dato che questa classe è
142.         'scritta apposta per questo
143.         If (SourceType Is GetType(String)) Then
144.              Return True
145.         End If
146.         Return MyBase.CanConvertFrom(Context, SourceType)
147.     End Function
148.
149.     'Converte da stringa a Isbn
150.     Public Overrides Function ConvertFrom(ByVal Context As ITypeDescriptorContext, _
151.         ByVal Culture As Globalization.CultureInfo, _
152.         ByVal Value As Object) As Object
153.         If TypeOf Value Is String Then
154.              Dim Str As String = DirectCast(Value, String)
155.              'Cerca di creare un nuovo oggetto isbn
156.              Try
157.                  Dim Obj As Isbn = Isbn.CreateNew(Str)
158.                  Return Obj
159.              Catch ex As Exception
160.                  MessageBox.Show(ex.Message, "Books Manager", _
161.                  MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
162.                  Return New Isbn
163.              End Try
164.         End If
165.         Return MyBase.ConvertFrom(Context, Culture, Value)
166.     End Function
167.
168.     'Converte da Isbn a stringa
169.
Public Overrides Function ConvertTo(ByVal Context As ITypeDescriptorContext, _
170.           ByVal Culture As Globalization.CultureInfo, _
171.           ByVal Value As Object, ByVal DestinationType As Type) As Object
172.           If DestinationType Is GetType(String) And _
173.               TypeOf Value Is Isbn Then
174.               Dim Temp As Isbn = DirectCast(Value, Isbn)
175.               Return Temp.ToString
176.           End If
177.           Return MyBase.ConvertTo(Context, Culture, Value, DestinationType)
178.       End Function
179.   End Class
180.
181.   'Il codice ISBN, dal primo gennaio 2007, deve obbligatoriamente
182.   'essere a tredici cifre. Per questo motivo metterò solo
183.   'questo tipo nel sorgente
184.   'P.S.: per convenzione, gli acronimi con più di due lettere devono
185.   'essere scritti in Pascal Case
186.   Public Class Isbn
187.       'Un codice è formato da:
188.       'Un prefisso (3 cifre) - solitamente 978 indica un libro in generale
189.       Private _Prefix As Int16 = 978
190.       'Un identificativo linguistico (da 1 a 5 cifre): indica
191.       'il paese di provenienza dell'autore - in Italia è 88
192.       Private _LanguageID As Int16 = 88
193.       'Un prefisso editoriale (da 2 a 6 cifre): indica l'editore
194.       Private _PublisherID As Int64 = 89637
195.       'Un identificatore del titolo
196.       Private _TitleID As Int32 = 15
197.       'Un codice di controllo che può variare da 0 a 10. 10 viene
198.       'indicato con X, perciò lo imposto come Char
199.       Private _ControlChar As Char = "9"
200.
201.       <DisplayName("Prefisso"), _
202.           Description("Prefisso del codice, costituito da tre cifre."), _
203.           Browsable(True), _
204.           Category("Isbn")> _
205.       Public Property Prefix() As Int16
206.           Get
207.               Return _Prefix
208.           End Get
209.           Set(ByVal Value As Int16)
210.               If Value = 978 Or Value = 979 Then
211.                    _Prefix = Value
212.               Else
213.                    Throw New ArgumentException("Prefisso non valido!")
214.               End If
215.           End Set
216.       End Property
217.
218.       <DisplayName("ID Lingua"), _
219.           Description("Identifica l'area da cui previene l'autore."), _
220.           Browsable(True), _
221.           Category("Isbn")> _
222.       Public Property LanguageID() As Int16
223.           Get
224.               Return _LanguageID
225.           End Get
226.           Set(ByVal Value As Int16)
227.               _LanguageID = Value
228.           End Set
229.       End Property
230.
231.       <DisplayName("ID Editore"), _
232.           Description("Identifica il marchio dell'editore."), _
233.           Browsable(True), _
234.           Category("Isbn")> _
235.       Public Property PublisherID() As Int32
236.           Get
237.               Return _PublisherID
238.           End Get
239.           Set(ByVal Value As Int32)
240.               _PublisherID = Value
241.
End Set
242.       End Property
243.
244.       <DisplayName("ID Titolo"), _
245.           Description("Identifica il titolo del libro."), _
246.           Browsable(True), _
247.           Category("Isbn")> _
248.       Public Property TitleID() As Int32
249.           Get
250.               Return _TitleID
251.           End Get
252.           Set(ByVal Value As Int32)
253.               _TitleID = Value
254.           End Set
255.       End Property
256.
257.       <DisplayName("Carattere di controllo"), _
258.           Description("Verifica la correttezza degli altri valori."), _
259.           Browsable(True), _
260.           Category("Isbn")> _
261.       Public Property ControlChar() As Char
262.           Get
263.               Return _ControlChar
264.           End Get
265.           Set(ByVal Value As Char)
266.               _ControlChar = Value
267.           End Set
268.       End Property
269.
270.       Public Sub New()
271.
272.       End Sub
273.
274.       'Restituisce in forma di stringa il codice
275.       Public Overrides Function ToString() As String
276.           Return String.Format("{0}-{1}-{2}-{3}-{4}", _
277.           Me.Prefix, Me.LanguageID, Me.PublisherID, _
278.           Me.TitleID, Me.ControlChar)
279.       End Function
280.
281.       'Metodo statico factory per costruire un nuovo codice ISBN. Se
282.       'si mettesse questo codice nel costruttore, l'oggetto verrebbe
283.       'comunque creato anche se il codice inserito fosse errato.
284.       'In questo modo, la creazione viene fermata e restituito
285.       'Nothing in caso di errori
286.       Shared Function CreateNew(ByVal StringCode As String) As Isbn
287.           'Con le espressioni regolari, ottiene le varie parti
288.           Dim Split As New System.Text.RegularExpressions.Regex( _
289.           "(?<Prefix>d{3})-(?<Language>d{1,5})" & _
290.           "-(?<Publisher>d{2,6})-(?<Title>d+)-(?<Control>w)")
291.
292.           Dim M As System.Text.RegularExpressions.Match = _
293.               Split.Match(StringCode)
294.
295.           'Se la lunghezza del codice, senza trattini, è di
296.           '13 caratteri e il controllo tramite espressioni regolari
297.           'ha avuto successo, procede
298.           If StringCode.Length = 17 And M.Success Then
299.                Dim Result As New Isbn
300.                With Result
301.                    .Prefix = M.Groups("Prefix").Value
302.                    .LanguageID = M.Groups("Language").Value
303.                    .PublisherID = M.Groups("Publisher").Value
304.                    .TitleID = M.Groups("Title").Value
305.                    .ControlChar = M.Groups("Control").Value
306.                End With
307.                Return Result
308.           Else
309.                Throw New ArgumentException("Il codice inserito è errato!")
310.           End If
311.       End Function
312. End   Class
313.
314. 'Una classe che rappresenta un libro
315. Public Class Book
316.     Private _Title As String
317.     'Si suppone che un libro abbia meno di 32767 pagine XD
318.     Private _Pages As Int16 = 100
319.     'Si possono anche avere più autori: in questo caso si ha
320.     'una lista a tipizzazione forte.
321.     Private _Authors As New List(Of Author)
322.     'L'eventuale serie a cui il libro appartiene
323.     Private _Series As String
324.     'Casa editrice
325.     Private _Publisher As String
326.     'Data di pubblicazione
327.     Private _PublicationDate As Date
328.     'Argomento
329.     Private _Subject As String
330.     'Costo in euro
331.     Private _Cost As Single = 1.0
332.     'Ristampa
333.     Private _Reprint As Byte = 1
334.     'Codice ISBN13
335.     Private _Isbn As New Isbn
336.
337.     <DisplayName("Titolo"), _
338.         Description("Il titolo del libro."), _
339.         Browsable(True), _
340.         Category("Editoria")> _
341.     Public Property Title() As String
342.         Get
343.              Return _Title
344.         End Get
345.         Set(ByVal Value As String)
346.              _Title = Value
347.         End Set
348.     End Property
349.
350.     <DisplayName("Collana"), _
351.         Description("La collana o la serie a cui il libro appartiene."), _
352.         Browsable(True), _
353.         Category("Editoria")> _
354.     Public Property Series() As String
355.         Get
356.              Return _Series
357.         End Get
358.         Set(ByVal Value As String)
359.              _Series = Value
360.         End Set
361.     End Property
362.
363.     <DisplayName("Editore"), _
364.         Description("La casa editrice."), _
365.         Browsable(True), _
366.         Category("Editoria")> _
367.     Public Property Publisher() As String
368.         Get
369.              Return _Publisher
370.         End Get
371.         Set(ByVal Value As String)
372.              _Publisher = Value
373.         End Set
374.     End Property
375.
376.     <DisplayName("Pagine"), _
377.         Description("Il numero di pagine da cui il libro è composto."), _
378.         DefaultValue("100"), _
379.         Browsable(True), _
380.         Category("Dettagli")> _
381.     Public Property Pages() As Int16
382.         Get
383.              Return _Pages
384.         End Get
385.
Set(ByVal Value As Int16)
386.           If Value > 0 Then
387.                _Pages = Value
388.           Else
389.                Throw New ArgumentException("Numero di pagine insufficiente!")
390.           End If
391.       End Set
392.   End Property
393.
394.   <DisplayName("Autore/i"), _
395.       Description("L'autore o gli autori."), _
396.       Browsable(True), _
397.       Category("Editoria")> _
398.   Public ReadOnly Property Authors() As List(Of Author)
399.       Get
400.           Return _Authors
401.       End Get
402.   End Property
403.
404.   <DisplayName("Pubblicazione"), _
405.       Description("La data di pubblicazione della prima edizione."), _
406.       Browsable(True), _
407.       Category("Dettagli")> _
408.   Public Property PublicationDate() As Date
409.       Get
410.           Return _PublicationDate
411.       End Get
412.       Set(ByVal Value As Date)
413.           _PublicationDate = Value
414.       End Set
415.   End Property
416.
417.   <DisplayName("Codice ISBN"), _
418.       Description("Il codice ISBN conformato alla normativa di 13 cifre."), _
419.       Browsable(True), _
420.       Category("Editoria"), _
421.       TypeConverter(GetType(IsbnConverter))> _
422.   Public Property Isbn() As Isbn
423.       Get
424.           Return _Isbn
425.       End Get
426.       Set(ByVal Value As Isbn)
427.           _Isbn = Value
428.       End Set
429.   End Property
430.
431.   <DisplayName("Ristampa"), _
432.       Description("Il numero della ristampa."), _
433.       DefaultValue(1), _
434.       Browsable(True), _
435.       Category("Dettagli")> _
436.   Public Property Reprint() As Byte
437.       Get
438.           Return _Reprint
439.       End Get
440.       Set(ByVal Value As Byte)
441.           If Value > 0 Then
442.                _Reprint = Value
443.           Else
444.                Throw New ArgumentException("Ristampa: valore errato!")
445.           End If
446.       End Set
447.   End Property
448.
449.   <DisplayName("Costo"), _
450.       Description("Il costo del libro, in euro."), _
451.       Browsable(True), _
452.       Category("Editoria")> _
453.   Public Property Cost() As Single
454.       Get
455.           Return _Cost
456.       End Get
457.
Set(ByVal Value As Single)
  458.             If Value > 0 Then
  459.                  _Cost = Value
  460.             Else
  461.                  Throw New ArgumentException("Inserire prezzo positivo!")
  462.             End If
  463.         End Set
  464.     End Property
  465. End Class

E il codice del for m:

   01. Class Form1
   02.     Private Sub strAddBook_Click(ByVal sender As Object, _
   03.         ByVal e As EventArgs) Handles strAddBook.Click
   04.         Dim Title As String = _
   05.             InputBox("Inserire il titolo del libro:", "Books Manager")
   06.
   07.         'Controlla che la stringa non sia vuota o nulla
   08.         If Not String.IsNullOrEmpty(Title) Then
   09.             Dim Item As New ListViewItem(Title)
   10.             Dim Book As New Book()
   11.             Book.Title = Title
   12.             Item.ImageIndex = 0
   13.             Item.Tag = Book
   14.             lstBooks.Items.Add(Item)
   15.         End If
   16.     End Sub
   17.
   18.     Private Sub lstBooks_SelectedIndexChanged(ByVal sender As Object, _
   19.         ByVal e As EventArgs) Handles lstBooks.SelectedIndexChanged
   20.         'Esce dalla procedura se non ci sono elementi selezionati
   21.         If lstBooks.SelectedIndices.Count = 0 Then
   22.             Exit Sub
   23.         End If
   24.
   25.         'Altrimenti procede
   26.         pgBook.SelectedObject = lstBooks.SelectedItems(0).Tag
   27.     End Sub
C1. Introduzione ai database relazionali


Il modello r elazionale non è stato il pr imo in assoluto ad esser e usato per la gestione dei database, ma è stato
intr odotto più tar di, negli anni '70, gr azie alle idee di E. F. Co dd. Ad oggi, è il modello più diffuso e utilizzato per la
sua semplicità.
Tale modello si basa su un unico concetto, la r elazio ne, una tabella costituita da r ig he (o r eco r d o tuple) e colonne
(o attr ibuti). Per definir e una r elazione, basta specificar ne il nome e gli attr ibuti. Ad esempio:

 Person (FirstName, LastName, BirthDay)


indica una r elazione di nome Per son, che pr esenta tr e colonne, denominate r ispettivamente Fir stName, LastName e
Bir thDay. Una volta data la definizione, per ò, è necessar io anche for nir e dei dati che ne r ispettino le r egole: dobbiamo
aggiunger e delle r ighe a questa tabella per r appr esentar e i dati che ci inter essano, ad esempio:


                                                         Relazione Per son

                     Fir stName                              LastName                                 Bir thDay

                        Mar io                                  Rossi                                 1/1/1965

                        Luigi                                 Bianchi                                 13/7/1971

                                                                 ...


L'insieme di tutte le r ighe della r elazione si dice estensio ne della r e lazio ne, mentr e ogni singola tupla viene anche
chiamata istanza di r elazio ne. Facendo un par allelismo con la pr ogr ammazione ad oggetti, quindi, avr emo queste
"somiglianze" (che si r iveler anno di vitale impor tanza nella tipizzazione for te, come vedr emo in seguito):

 Database                                 Programmazione ad oggetti
 Relazione                          ->    Classe
 Tupla                              ->    Oggetto
 Estensione della relazione         ->    Lista di oggetti
 Attributo                          ->    Proprietà dell'oggetto
 Istanza di relazione               ->    Istanza di classe (= Oggetto)


Avendo or a chiar ito questi par allelismi, vi sar à più facile entr ar e nella mentalità del modello r elazionale, dato che, se
siete ar r ivati fino a qui, si assume che sappiate già benissimo tutti gli aspetti e i concetti della pr ogr ammazione ad
oggetti.
Uno sguar do attento, tuttavia, far à notar e che, tr a i car atter i fondamentali che si possono r intr acciar e in questi
par allelismi, manca il concetto di "tipo" di un attr ibuto. Infatti, per come abbiamo pr ima definito la r elazione, sar ebbe
del tutto lecito immetter e un numer o inter o nel campo Fir stName o una str inga in Bir thDay. Per for tuna, il modello
pr evede anche che ogni colonna possegga un dom inio , ossia uno specifico r ange di valor i che essa può assumer e: ciò
che noi abbiamo sempr e chiamato tipo. Il tipo di un attr ibuto può esser e scelto tr a una gamma molto limitata: inter i,
valor i a vir gola mobile, str inghe (a lunghezza limitata e non), date, car atter i, boolean e dati binar i (ar r ay di bytes). In
sostanza, questi sono i tipi pr imitivi o atomici di ogni linguaggio e pr opr io per questo motivo, si dice che il dominio di
un attr ibuto può esser e solo di tipo atomico, ossia non è possibile costr uir e tipi di dato complessi come le str uttur e o
le classi. Questa peculiar ità sembr er ebbe molto limitativa, ma in r ealtà non è così, poiché possiamo instaur ar e dei
collegamenti (o vincoli) tr a una r elazione e l'altr a, gr azie all'uso di elementi detti chiav i.
La chiave più impor tante è la chiav e pr im ar ia (pr imar y key), che ser ve ad identificar e univocamente una tupla
all'inter no della r elazione. Facendo un par agone con la pr ogr ammazione, se una tupla è assimilabile ad un oggetto ed
esistono due tuple con attr ibuti identici, esse non r appr esentano comunque la stessa entità, pr opr io come due oggetti
con pr opr ietà uguali non sono lo stesso oggetto. E se per gli oggetti esiste un codice "segr eto" per distinguer li (guid), a
cui solo il pr ogr amma ha accesso, così esiste un par ticolar e valor e che ser ve per indicar e senza ombr a di dubbio se
due r ecor d sono differ enti: questo valor e è la chiave pr imar ia. Essa è solitamente un numer o inter o positivo ed è
anche la pr ima colonna definita dalla r elazione. Modificando la definizione di Per son data pr ecedente, ed intr oducendo
anche il dominio degli attr ibuti, si otter r ebbe:

 'Questa sintassi è del tutto inventata!
 'Serve solo per esemplificare i concetti:
 Person (ID As Integer, FirstName As String, LastName As String, BirthDay As Date)


                                                       Relazione Per son

          ID                Fir stName                         LastName                           Bir thDay

           1                   Mar io                                Rossi                        1/1/1965

           2                   Luigi                             Bianchi                         13/7/1971

                                                               ...


Per distinguer e le singole r ighe esiste, poi, un'altr a tipologia di chiave, detta chiav e candidata, costituita dal più
piccolo insieme di attr ibuti per cui non esistono due tuple in cui quegli attr ibuti hanno lo stesso valor e. In gener ale,
tutte le chiavi pr imar ie sono chiavi canditate, a causa della stessa definizione data poco fa; mentr e esistono chiavi
candidate che non sono chiavi pr imar ie. Ad esempio, in questo caso, l'insieme degli attr ibuti Fir stName, LastName e
Bir thDay costituisce una chiave candidata, poichè è pr aticamente impossibile tr ovar e due per sone con lo stesso nome
nate nello stesso gior no alla stessa or a (almeno, è impossibile nella nostr a r elazione for mata da due elementi XD e
questo ci basta): quindi, questi tr e attr ibuti soddisfano le condizioni della definizione e identificano univocamente un
r ecor d. In gener e, si sceglie una fr a tutte le chiavi candidate possibili che viene assunta come chiave pr imar ia: a r igor
di logica, essa dovr à esser e la più semplice di tutte. In questo caso, il singolo numer o ID è molto più maneggevole che
non l'insieme di due str inghe e una data.
Or a, ammettiamo di aver e una r elazione così definita:

 Program (ProgramID As Integer, Path As String, Description As String)


che indica un qualsiasi pr ogr amma installato su un computer ; e quest'altr a r elazione:

 User (UserID As Integer, Name As String, MainFolder As String)


che indica un qualsiasi utente di quel computer . Ammettiamo anche che la macchina sulla quale sono installati i
pr ogr ammi pr esenti nell'estensione della r elazione sia condivisa da più utenti: vogliamo stabilir e, tr amite r elazioni,
quale utente possa acceder e a quale pr ogr amma. In questa cir costanza, abbiamo diver se soluzioni possibili, ma una sola
è la miglior e:

       Abbiamo detto che la r elazione Pr ogr am ha una chiave pr imar ia, e la r elazione User pur e. Dato che si tr atta di
       due tabelle diver se, potr ebbe venir e in mente di stabilir e questa policy di accesso: un utente può acceder e a un
       pr ogr amma solo se la sua chiave pr imar ia (User ID) coincide con la chiave pr imar ia del pr ogr amma (Pr ogr amID).
       In questo caso, tuttavia, le cir costanze sono molto r estr ittive, in quanto un utente può usar e uno e un solo
       pr ogr amma (e vicever sa). La r elazione (in senso letter ale, ossia il collegamento) tr a le due tabelle si dice uno a
       uno .
Aggiungiamo alla r elazione Pr ogr am un altr o attr ibuto User ID, che dovr ebbe indicar ci l'utente che può usar e un
dato pr ogr amma. Tuttavia, come abbiamo visto pr ima, i valor i delle colonne devono esser e atomici e per ciò non
possiamo inser ir e in quella singola casella tutta un'altr a r iga (anche per chè non sapr emmo che tipo specificar e
come dominio). Qui ci viene in aiuto la chiave pr imar ia: sappiamo che nella r elazione User , ogni tupla è
univocamente identificata da una e una sola chiave pr imar ia chiamata User ID, quindi indicando una chiave,
indichiamo anche la r iga ad essa associata. Per cui, possiamo ben cr ear e un nuovo attr ibuto di tipo inter o (in
quanto la chiave è un numer o inter o in questo caso), nel quale specifichiamo l'User ID dell'utente che può usar e il
nostr o pr ogr amma. Ad esempio:

 Program (ProgramID As Integer, Path As String, Description As String, UserID As String)


                                                   Relazione Pr ogr am

        Pr ogr amID                            Path                                  Descr iption           User ID

             1                     C:WINDOWSnotepad.ex e                         Editor di testo            2

             2                C:Pr ogr ammiFir eFox fir efox .ex e          Fir eFox w eb br ow ser        1

             3           C:Pr ogr ammiWor ld of War cr aftWoW.ex e            Wor ld of War cr aft         2

                                                            ...



                                                      Relazione User

             User ID                         Name                                    MainFolder

                 1                        Mar io Rossi                             C:User sMRossi

                 2                       Luigi Bianchi                              C:User sGigi

                                                            ...


Come evidenziano i color i, il pr ogr amma 1 (notepad) e il pr ogr amma 3 (Wor ld of War cr aft) possono esser e usati
dall'utente 2 (Luigi Bianchi), mentr e il pr ogr amma 2 (Ffir efox ) può esser e usato dall'utente 1 (Mar io Rossi). Da un
pr ogr amma possiamo r isalir e all'utente associato, contr ollar ne l'identità e quindi consentir ne o pr oibir ne l'uso.
Questa soluzione, tuttavia, per mette l'accesso a un dato pr ogr amma da par te di un solo utente, anche se tale
utente può usar e più pr ogr ammi.
La r elazione che collega User a Pr ogr am è detta uno a m o lti (un utente può usar e più pr ogr ammi). Se la
guar diamo al contr ar io, ossia da Pr ogr am a User , è detta m o lti a uno (più pr ogr ammi possono esser e usati da
un solo utente). Entr ambr e le pr ospettive sono le due facce della stessa r elazione uno a molti, la più utilizzata.


Dato che il pr ecedente tentativo non ha funzionato, pr oviamo quindi a intr odur r e una nuova tabella:

 UsersPrograms (UserID As Integer, ProgramID As Integer)


In questa tabella imponiamo che n on es is ta alcun a chiave primaria. Infatti lo scopo di questa r elazione è un
altr o: ad un cer to pr ogr amma associa un utente, ma questo lo si può far e più volte. Ad esempio:


                                                   Relazione Pr ogr am

        Pr ogr amID                                Path                                      Descr iption
1                          C:WINDOWSnotepad.ex e                              Editor di testo

                     2                     C:Pr ogr ammiFir eFox fir efox .ex e                Fir eFox w eb br ow ser

                     3                 C:Pr ogr ammiWor ld of War cr aftWoW.ex e                 Wor ld of War cr aft

                                                                     ...



                                                              Relazione User

                     User ID                          Name                                      MainFolder

                         1                        Mar io Rossi                                C:User sMRossi

                         1                        Luigi Bianchi                                C:User sGigi

                                                                     ...



                                                        Relazione User sPr ogr ams

                                User ID                                               Pr ogr amID

                                   1                                                      1

                                   1                                                      2

                                   2                                                      2

                                   2                                                      3

                                                                     ...


       Nell'ultima r elazione tr oviamo un 1 (due volte) associamo pr ima ad un 1 e poi ad un 2: significa che lo stesso
       utente 1 (Mar io Rossi) può acceder e sia al pr ogr amma 1 (notepad) sia al pr ogr amma 2 (fir efox ). Allo stesso
       modo, l'utente 2 può acceder e sia al pr ogr amma 2 (fir efox ) sia al pr ogr amma 3 (Wor ld of War cr aft). Con
       l'aggiunta di un'altr a tabella siamo r iusciti a legar e più utenti a più pr ogr ammi. Relazioni tr a tabelle di questo
       tipo si dicono m o lti a m o lti.

In ognuno di questi esempi, l'inter o con cui ci si r ifer isce ad un'altr a tupla viene detto chiav e ester na.
 Vb.net
C2. Descrizione dei componenti principali


Dettagli tec nic i
Pr ima di iniziar e, qualche dettaglio tecnico. Per i pr ossimi esempi user ò MySql. Tr ovate una guida su come scar icar lo,
configur ar lo e gestir lo nel capitolo A 1 del tutor ial dedicato a LINQ. Oltr e a ciò che viene descr itto in quella sezione,
avr emo bisogno di alcune classi per inter facciar ci con MySql, e che non sono pr esenti nell'installazione standar d del
fr amew or k. Potete scar icar e gli assemblies necessar i da qui. Essi ver r anno automaticamente installati nella GAC e
sar anno accessibili sucessivamente assieme a tutti gli altr i assemblies fondamentali nella scheda ".NET" della finestr a di
dialogo "Add Refer ence", già spiegata pr ecedentemente. Per usar li, impor tate i r ifer imenti e aggiungete le dir ettive
Impor ts all'inizio del sor gente.




Connessione
La pr ima cosa da far e per iniziar e a smanettar e su un database consiste pr incipalmente nel collegar si alla macchina
sulla quale esso esiste, che possiamo definir e ser ver . L'applicazione è quindi un client (vedi capitolo sui Socket), che
instaur a un collegamento non solo fisico (tr amite Inter net), ma anche logico, con il pr ogr amma che for nisce il ser vizio
di gestione dei database; nel nostr o caso si tr atta di MySql. Per gli esempi che user ò, l'host, ossia l'elabor ator e che
ospita il ser vizio, coincider à con il vostr o stesso computer , ossia ci connetter emo a localhost (127.0.0.1). Se avete
installato tutto senza pr oblemi e avviato MySql cor r ettamente, possiamo iniziar e ad analizzar e la pr ima classe
impor tante: MySqlConnection. Essa for nisce le funzionalità di connessione sopr acitate mediante due semplici metodi:
Open (apr e la connessione) e Close (la chiude). Il suo costr uttor e pr incipale accetta come ar gomento una str inga detta
co nnectio n str ing , la quale definisce dove e come eseguir e il collegamento. Tipicamente, è for mata da var ie par ti,
separ ate da punti e vir gola, ciascuna delle quali imposta una data pr opr ietà. Eccone un esempio:

   01.   Imports MySql.Data.MySqlClient
   02.
   03.   '...
   04.
   05.   'Crea una nuova connessione all'host locale,
   06.   'accedendo al servizio come utente "root" e con
   07.   'password "root". Se non avete modificato le
   08.   'impostazioni di sicurezza, questa coppia di username
   09.   'e password è quella predefinita.
   10.   'Naturalmente è un'idiozia mantenere queste
   11.   'credenziali così ovvie e dovrebbero essere cambiate
   12.   'subito, ma per i miei esempi userò sempre root.
   13.   Dim Conn As New MySqlConnection("Server=localhost; Uid=root; Pwd=root;")
   14.
   15.   Try
   16.       'Avvia la connessione
   17.       Conn.Close()
   18.   Catch Ex As Exception
   19.       '...
   20.   Finally
   21.       'E la chiude. Ricordatevi che è sempre bene
   22.       'chiudere la connessione anche quando si verifichino
   23.       'errori (anzi, soprattutto in queste situazioni).
   24.       Conn.Close()
   25.   End Try

Esiste anche un'altr a var iante della connection str ing, che si pr esenta come segue:

 "Data Source=localhost; UserId=root; PWD=root;"
Una volta connessi, è possibile effettuar e oper azioni var ie sui database, anche se, a dir la ver ità, noi non abbiamo
ancor a nessun database su cui oper ar e...




Esec uzione di una query
Il ter mine quer y indica una str inga mediante la quale si inter r oga un database per ottener ne infor mazioni, o per
cr ear e/modificar e quelle già contenutevi. Le quer y pr esentano una lor o sintassi par ticolar e, la quale, pur var iando
legger mente da un gestor e all'altr o (MySql, Sql Ser ver , Or acle, Access, ecceter a...), ader isce ad uno standar d univer sale:
l'SQL, appunto (Str uctur ed Quer y Language). In questo capitolo e nei pr ossimi analizzer ò solo qualche semplice esempio
di quer y, poiché la tr attazione del linguaggio inter o r ichieder ebbe svar iati capitoli suppletivi che esulano dalle
intenzioni di questa guida. Vi invito, comunque, a sceglier e una guida a questo linguaggio, o almeno un r efer ence
manual, da legger e in par allelo con i pr ossimi capitoli. Alcuni link inter essanti: W o r ld W ide W eb Co nsor tium ,
M o r pheus W eb, HTM L.it (SQL) HTM L.it (M ySQL).
Per iniziar e, vediamo quale classe gestisca le quer y. Si tr atta di MySqlCommand. Essa espone alcuni costr uttor i, tr a cui
uno senza par ametr i, ma tutti gli ar gomenti specificabili sono anche accessibili tr amite le sue pr opr ietà. I membr i
significativi sono:

         Cancel() : cancella l'esecuzione di una quer y in cor so;
         CommandTex t : indica la quer y stessa;
         CommandTimeout : il tempo massimo, in millisecondi, oltr e il quale l'esecuzione della quer y viene annullata;
         Connection : deter mina l'oggetto MySqlConnecction mediante il quale si è connessi al database. Senza
         connessione, ovviamente, non si potr ebbe far e un bel niente;
         Ex ecuteNonQuer y() : esegue la quer y specificata e r estituisce il numer o di r ecor d da essa affetti, ossia
         selezionati, cr eati, cancellati o modificati. Restituisce -1 in caso di fallimento;
         Ex ecuteReader () : esegue la quer y e r estituisce un oggetto MySqlDataReader che per mette di scor r er e una alla
         volta le r ighe che sono state selezionate. Il r eader , come sugger isce il nome, "legge" i dati, ma questi sono
         vir tualmente posti su un flusso immaginar io, poiché la quer y non viene eseguita tutto in un colpo, ma di volta in
         volta. Vedr emo successivamente, più in dettaglio come usar e un oggetto di questo tipo e quali limitazioni
         compor ta;
         Ex ecuteScalar () : esegue la quer y e r estituisce il contenuto della pr ima colonna della pr ima r iga di tutti i
         r isultati. Restituisce Nothing se la quer y fallisce;

Or a, per pr ima cosa, dobbiamo cr ear e un nuovo database: potete far lo manualmente da SQL Yog o da qualsiasi altr o
pr ogr amma di gestione, ma io user ò solo codice, per evitar e di impor r e vincoli inutili:

   01.    'Potete porre questo sorgente sia
   02.    'in una Windows Application sia in una Console Application,
   03.    'anche perchè lo useremo solo una volta.
   04.    Dim Conn As New MySqlConnection("Server=localhost; Uid=root; Pwd=root;")
   05.    Dim Cmd As New MySqlCommand()
   06.
   07.    Try
   08.        Conn.Open()
   09.        Cmd.Connection = Conn
   10.        'Crea un nuovo database di nome "appdata"
   11.        Cmd.CommandText = "CREATE DATABASE appdata;"
   12.        Cmd.ExecuteNonQuery()
   13.    Catch Ex As Exception
   14.
   15.    Finally
   16.        Conn.Close()
   17.    End Try

Quando il nuovo database è cr eato, possiamo modificar e la connection str ing in modo da apr ir e quello desider ato: la
sintassi per indicar e il database di default è "Database=[nome db];" oppur e "Initial Catalog=[nome db];". Per
esemplificar e questa nuova aggiunta alla str inga di connessione, utilizziamo anche un codice per cr ear e la tabella su cui
lavor er emo:

   01. Dim Conn As New MySqlConnection("Server=localhost; Database=appdata; Uid=root; Pwd=root;")
   02. Dim Cmd As New MySqlCommand()
   03.
   04. Try
   05.     Conn.Open()
   06.     Cmd.Connection = Conn
   07.     'Crea una nuova tabella di nome Customers nel database
   08.     'appdata. I suoi attributi (colonne) sono:
   09.     ' - ID : identificativo numerico del record; non può essere
   10.     '   vuoto, viene autoincrementato quando si aggiunge una
   11.     '   nuova riga ed è la chiave primaria della
   12.     '   relazione Customers
   13.     ' - FirstName : nome del cliente (max 150 caratteri)
   14.     ' - LastName : cognome del cliente (max 150 caratteri)
   15.     ' - Address : indirizzo (max 255 caratteri)
   16.     ' - PhoneNumber : numero telefonico (max 30 caratteri)
   17.     Cmd.CommandText = "CREATE TABLE Customers (ID int NOT NULL AUTO_INCREMENT, FirstName
              char(150), LastName char(150), Address char(255), PhoneNumber char(30), PRIMARY KEY
              (ID));"
   18.     Cmd.ExecuteNonQuery()
   19. Catch Ex As Exception
   20.
   21. Finally
   22.     Conn.Close()
   23. End Try

Con lo stesso pr ocedimento, è anche possibile inser ir e tuple nella tabella mediante il "comando" inser t into:

 INSERT INTO Customers VALUES(1, 'Mario', 'Rossi', 'Via Roma 89, Milano', '50 288 41 971');


Qui potete tr ovar e una cinquantina di quer ies cr eate a r andom da eseguir e sulla tabella per inser ir e qualche valor e,
giusto per aver e un po' di dati su cui lavor ar e.




Enumerazione di rec ord
Come accennato, pr ecedentemente, quando si esegue Ex ecuteReader , viene r estituito un oggetto MySqlDataReader che
per mette di scor r er e i r isultati di una quer y. Ecco una br eve descr izione dei suoi membr i:

       Close() : chiude il r eader . Dato che mentr e il r eader è aper to, nessuna oper azione può esser e eseguita sul
       database, è sempr e obbligator io chiuder e l'oggetto dopo l'uso;
       FieldCount : indica il numer o di attr ibuti della r iga cor r ente;
       Get...(i) : tutte le funzioni il cui nome inizia per "Get" ser vono per ottener e il valor e della i-esima colonna
       sottofor ma di un par ticolar e tipo;
       HasRow s : deter mina se il r eader contenga almeno un r ecor d da legger e;
       IsClosed : indica se l'oggetto è stato chiuso;
       IsDbNull(i) : r estituisce Tr ue se il campo i-esimo del r ecor d cor r ente non contiene un valor e (r appr esentato dalla
       costante DBNull.Value);
       Read() : legge una nuova r iga e r estituisce Tr ue se l'oper azione è r iuscita. False significa che non c'è più nulla da
       legger e;

Ecco un esempio di come usar e il Reader , in un'applicazione Window s For m con una listview e un pulsante:

   01. Imports MySql.Data.MySqlClient
   02.
   03. Class Form1
   04.
   05.     Private Sub btnLoad_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
Handles btnLoad.Click
06.         Dim Conn As New MySqlConnection("Server=localhost; Database=appdata; Uid=root;
               Pwd=root;")
07.         Dim Command As New MySqlCommand
08.
09.         Try
10.               'Seleziona tutti i record della tabella Customers,
11.               'includendovi tutti gli attributi. Questa query è
12.               'equivalente a:
13.               ' SELECT ID, FirstName, LastName, Address, PhoneNumber FROM Customers
14.               Command.CommandText = "SELECT * FROM Customers;"
15.               Command.Connection = Conn
16.               Conn.Open()
17.
18.               'Esegue la query e restituisce il reader
19.               Dim Reader As MySqlDataReader = Command.ExecuteReader()
20.
21.               lstRecords.Columns.Clear()
22.               'Aggiunge tante colonne alla listview quanti sono
23.               'gli attributi dei record selezionati. È possibile
24.               'ottenere il nome di ogni colonna con la funzione
25.               'GetName di MySqlDataReader
26.               For I As Int32 = 0 To Reader.FieldCount - 1
27.                    lstRecords.Columns.Add(Reader.GetName(I))
28.               Next
29.
30.               Dim L As ListViewItem
31.               Dim S(Reader.FieldCount - 1) As String
32.
33.               'Fintanto che c'è qualche record da leggere,
34.               'lo aggiunge alla listview
35.               Do While Reader.Read()
36.                    'Riempie l'array S con i valori degli attributi
37.                    'della tupla corrente. Se una cella non contiene
38.                    'valori, mette una stringa vuota al suo posto
39.                    For I As Int32 = 0 To S.Length - 1
40.                         If Not Reader.IsDBNull(I) Then
41.                              'GetString(I) ottiene il valore della cella
42.                              'I sottoforma di stringa
43.                              S(I) = Reader.GetString(I)
44.                         Else
45.                              S(I) = ""
46.                         End If
47.                    Next
48.                    L = New ListViewItem(S)
49.                    lstRecords.Items.Add(L)
50.               Loop
51.
52.             'Chiude il Reader
53.             Reader.Close()
54.         Catch ex As Exception
55.             MessageBox.Show(ex.Message, Me.Text, MessageBoxButtons.OK,
                   MessageBoxIcon.Exclamation)
56.         Finally
57.             'Chiude la connessione
58.             Conn.Clone()
59.         End Try
60.     End Sub
61.
62. End Class
 Vb.net
C3. Un esempio pratico


Applicando i concetti del capitolo scor so, ho scr itto un piccolo esempio pr atico di applicazione basata su database, ossia
un semplicissimo gestionale per or ganizzar e la tabella Customer s. L'inter faccia è questa:




Il contr ollo vuoto è una ListView con View =Details e HideSelection=False. Le tr e tex tbox hanno un tag associato: la
pr ima ha tag "Fir stName", la seconda "LastName", la ter za "Addr ess" e la quar ta "PhoneNumber ". Ecco il codice:

 001. Imports MySql.Data.MySqlClient
 002.
 003. Class Form1
 004.     Private Conn As MySqlConnection
 005.
 006.     'Esegue una query sul database, quindi carica i
 007.     'risultati nella listview
 008.     Private Sub LoadData(ByVal Query As String)
 009.         Dim Command As New MySqlCommand
 010.
 011.         Command.CommandText = Query
 012.         Command.Connection = Conn
 013.
 014.         Dim Reader As MySqlDataReader = Command.ExecuteReader()
 015.
 016.         If lstRecords.Columns.Count = 0 Then
 017.              For I As Int32 = 0 To Reader.FieldCount - 1
 018.                   lstRecords.Columns.Add(Reader.GetName(I))
 019.              Next
 020.         End If
 021.
 022.         Dim L As ListViewItem
 023.         Dim S(Reader.FieldCount - 1) As String
 024.
 025.         lstRecords.Items.Clear()
 026.         Do While Reader.Read()
 027.              For I As Int32 = 0 To S.Length - 1
 028.                   If Not Reader.IsDBNull(I) Then
 029.
S(I) = Reader.GetString(I)
030.                  Else
031.                      S(I) = ""
032.                  End If
033.              Next
034.              L = New ListViewItem(S)
035.              lstRecords.Items.Add(L)
036.       Loop
037.
038.       Reader.Close()
039.       Command.Dispose()
040.       Command = Nothing
041.   End Sub
042.
043.   Private Sub LoadData()
044.       Me.LoadData("SELECT * FROM Customers")
045.   End Sub
046.
047.   'Scorciatoia per eseguire una query velocemente
048.   Private Function ExecuteQuery(ByVal Query As String) As Int32
049.       Dim Command As New MySqlCommand(Query, Conn)
050.       Dim Result As Int32 = Command.ExecuteNonQuery()
051.
052.       Command.Dispose()
053.       Command = Nothing
054.
055.       Return Result
056.   End Function
057.
058.   Private Sub Form_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles
          MyBase.Load
059.        Conn = New MySqlConnection("Server=localhost; Database=appdata; Uid=root; Pwd=root;")
060.
061.       Try
062.           Conn.Open()
063.       Catch ex As Exception
064.           Conn.Close()
065.           MessageBox.Show(ex.Message, Me.Text, MessageBoxButtons.OK,
                  MessageBoxIcon.Exclamation)
066.           Me.Close()
067.       End Try
068.
069.       LoadData()
070.   End Sub
071.
072.   Private Sub btnAdd_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
          Handles btnAdd.Click
073.        If ExecuteQuery(String.Format("INSERT INTO Customers VALUES(null, '{0}', '{1}', '{2}',
               '{3}');", txtFirstName.Text, txtLastName.Text, txtAddress.Text,
               txtPhoneNumber.Text)) Then
074.             MessageBox.Show("Cliente aggiunto!", Me.Text, MessageBoxButtons.OK,
                    MessageBoxIcon.Information)
075.             LoadData()
076.        End If
077.   End Sub
078.
079.   Private Sub btnEdit_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
          Handles btnEdit.Click
080.        If lstRecords.SelectedIndices.Count = 0 Then
081.            MessageBox.Show("Nessun record selezionato!", Me.Text, MessageBoxButtons.OK,
                   MessageBoxIcon.Exclamation)
082.            Exit Sub
083.        End If
084.
085.       Dim ID As Int32 = CType(lstRecords.SelectedItems(0).SubItems(0).Text, Int32)
086.       Dim Query As New System.Text.StringBuilder()
087.
088.       'L'istruzione UPDATE aggiorna i campi della tabella
089.       'specificata usando i valori posti dopo la clausola
090.       'SET. Solo i record che rispettano i vincoli imposti
091.       'dalla clausola WHERE vengono modificati
092.       Query.Append("UPDATE Customers SET")
093.
For Each T As TextBox In New TextBox() {txtFirstName, txtLastName, txtAddress,
                txtPhoneNumber}
094.              Query.AppendFormat(" {0} = '{1}',", T.Tag.ToString(), T.Text)
095.         Next
096.         'Rimuove l'ultima virgola...
097.         Query.Remove(Query.Length - 1, 1)
098.
099.         Query.AppendFormat(" WHERE ID = {0};", ID)
100.
101.         ExecuteQuery(Query.ToString())
102.         Query = Nothing
103.         LoadData()
104.     End Sub
105.
106.     Private Sub btnFilter_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
            Handles btnFilter.Click
107.          Dim Query As New System.Text.StringBuilder()
108.          Dim Conditions As New List(Of String)
109.
110.         Query.Append("SELECT * FROM Customers")
111.
112.         'La scrittura:
113.         ' Field LIKE '%Something%'
114.         'equivarrebbe teoricamente a:
115.         ' Field.Contains(Something)
116.         For Each T As TextBox In New TextBox() {txtFirstName, txtLastName, txtAddress,
                txtPhoneNumber}
117.              If Not String.IsNullOrEmpty(T.Text) Then
118.                  Conditions.Add(String.Format("WHERE {0} LIKE '%{1}%'", T.Tag.ToString(),
                         T.Text))
119.              End If
120.         Next
121.
122.         If Conditions.Count >= 1 Then
123.             Query.AppendFormat(" {0}", Conditions(0))
124.             If Conditions.Count > 1 Then
125.                 For I As Int32 = 1 To Conditions.Count - 1
126.                      Query.AppendFormat(" AND {0}", Conditions(I))
127.                 Next
128.             End If
129.         End If
130.         Query.Append(";")
131.
132.         LoadData(Query.ToString())
133.         Query = Nothing
134.     End Sub
135.
136.     Private Sub Form1_FormClosing(ByVal sender As System.Object, ByVal e As
            System.Windows.Forms.FormClosingEventArgs) Handles MyBase.FormClosing
137.          If Conn.State <> ConnectionState.Closed Then
138.              Conn.Close()
139.          End If
140.     End Sub
141.
142.     Private Sub btnReload_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
            Handles btnReload.Click
143.          LoadData()
144.     End Sub
145.
146.     Private Sub lstRecords_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As
            System.EventArgs) Handles lstRecords.SelectedIndexChanged
147.          If lstRecords.SelectedItems.Count = 0 Then
148.              Exit Sub
149.          End If
150.
151.         Dim Selected As ListViewItem = lstRecords.SelectedItems(0)
152.         txtFirstName.Text = Selected.SubItems(1).Text
153.         txtLastName.Text = Selected.SubItems(2).Text
154.         txtAddress.Text = Selected.SubItems(3).Text
155.         txtPhoneNumber.Text = Selected.SubItems(4).Text
156.     End Sub
157. End Class
 Vb.net
C4. Dalle relazioni agli oggetti - Parte I


Usar e quer ies per manipolar e il database è un mezzo molto efficacie, anche se il pr ocesso per cr ear e una quer y
sottofor ma di str inga può r isultar e alquanto macchinoso in alcuni casi. A questo pr oposito, vor r ei invitar vi a legger e le
pr ime lezioni del tutor ial che ho scr itto r iguar do a LINQ, il linguaggio di quer ying integr ato disponibile dalla ver sione
2008 del linguaggio (fr amew or k v3.5).
In questo capitlo, invece, inizier emo a passar e dalle r elazioni, ossia dalle tabelle del database nel lor o ambiente, agli
oggetti, tr asponendo, quindi, tutte le oper azioni a costr utti che già conosciamo. Possiamo r appr esentar e un database
e le sue tabelle in due modi:

       Mediante l'appr occio standar d, con le classi DataSet e DataTable, che r appr esentano esattamente il database, con
       tutte le sue pr opr ietà e car atter istiche. Queste classi astr aggono tutta la str uttur a r elazione e la tr aspor tano
       nel linguaggio ad oggetti;
       Mediante l'appr occio LINQ, con nor mali classi scr itte dal pr ogr ammator e, ar tificalmente collegate tr amite
       attr ibuti e metadati, alle r elazioni pr esenti nel database;

Vedr emo or a solo il pr imo appr occio, poiché il secondo è tr attato già nel tutor ial menzionato pr ima.




DataSet
La classe DataSet ha lo scopo di r appr esentar e un database. Mediante un apposito oggetto, detto Adapter (che
analizzer emo in seguito), è possibile tr avasar e tutti i dati del database in un oggetto DataSet e quindi oper ar e su
questo senza bisogno di quer y. Una volta ter minate le elabor azioni, si esegue il pr ocesso inver so aggior nando il
database con le nuove modifiche appor tate al DataSet. Questo è il pr incipio di base con cui si affr onta il passaggio dalle
r elazioni agli oggetti.
Questa classe espone una gr an quantità di membr i, tr a cui menzioniamo i più impor tanti:

       AcceptChanges() : confer ma tutte le modifiche appor tate al DataSet; questo metodo deve esser e r ichiamato
       obbligator iamente pr ima di iniziar e la pr ocedur a di aggior namento del database a cui è collegato;
       CaseSensitive : indica se la compar azione di campi di tipo str ing avviene in modalità case-sensitive;
       Clear () : elimina tutti i dati pr esenti nel DataSet;
       Clone() : esegue una clonazione deep dell'oggetto DataSet e r estituisce la nuova istanza;
       DataSetName : nome del DataSet;
       GetChanges() : r estituisce una copia del DataSet in cui sono pr esenti tutti i dati modificati (come se si fosse
       r ichiamato AcceptChanges());
       GetXml() : r estituisce una str inga contenente la r appr esentazione x ml di tutti i dati pr esenti nel DataSet;
       HasChanges : deter mina se il DataSet sia stato modificato dall'ultimo salvataggio o car icamento;
       IsInitialized : indica se è inizializzato;
       Mer ge(D As DataSet) : unisce D al DataSet cor r ente. Le tabelle e i dati di D vengono aggiunti all'oggetto
       cor r ente;
       RejectChanges() : annulla tutte le modifiche appor tate dall'ultimo salvataggio o car icamento;
       ReadXml(R) : legge un file XML mediante l'oggetto R (di tipo XmlReader ) e tr asfer isce i dati ivi contenuti nel
       DataSet;
       Reset() : annulla ogni modifica appor tata e r ipor ta il DataSet allo stato iniziale (ossia come er a dopo il
       car icamento);
Tables : r estituisce una collezione di oggetti DataTable, che r appr esentano le tabelle pr esenti nel DataSet (e
       quindi nel database);




DataTable
Se un DataSet r appr esenta un database, allor a un oggetto di tipo DataTable r appr esenta un singola tabella, composta di
r ighe e colonne. Nessun nuovo concetto da intr odur r e, quindi: si tr atta solo di una classe che r appr esenta ciò che
abbiano visto fin or a per una r elazione. I suoi membr i sono pr essoché simili a quelli di DataSet, con l'aggiunta delle
pr opr ietà Columns, Row s, Pr imar yKey e del metodo AddRow (ho menzionato solo quelli di uso più fr equente).




Caric amento e binding
Or a possiamo car icar e i dati in un DataSet ed eseguir e un binding ver so un contr ollo. Abbiamo già il concetto di binding
nei capitoli sulla r eflection, in r ifer imento alla possibilità di legar e un identificator e a un valor e. In questo caso, pur
essendo fondamentalmente la stessa cosa, il concetto è legger mente differ ente. Noi vogliamo legar e un cer to insieme di
valor i ad un contr ollo, in modo che esso li visualizzi senza dover scr iver e un codice par ticolar e per inser ir li. Il contr ollo
che, per eccellenza, r ende gr aficamente nel modo miglior e le tabelle è DataGr idView . Assumendo, quindi, di aver e nel
for m solo un contr ollo DataGr idView 1, possiamo scr iver e questo codice:

   01. Imports MySql.Data.MySqlClient
   02. Class Form1
   03.     Private Data As New DataSet
   04.
   05.     Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles
              MyBase.Load
   06.          Dim Conn As New MySqlConnection("Server=localhost; Database=appdata; Uid=root;
                   Pwd=root;")
   07.          Dim Adapter As New MySqlDataAdapter
   08.
   09.          Conn.Open()
   10.
   11.          Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Customers;", Conn)
   12.          Adapter.Fill(Data)
   13.
   14.          Conn.Clone()
   15.
   16.          DataGridView1.DataSource = Data.Tables(0)
   17.     End Sub
   18.
   19. End Class

DataGr idView 1 ver r à r iempito con tutti i dati pr esenti nella pr ima (e unica) tabella del dataset.




DataSet tipizzati
Nel pr ossimo esempio vedr emo di accennar e alla costr uzione di una semplice applicazione per gestir e br ani musicali
con un database, ed eventualmente intr odur r ò un po' di codice per la r ipr oduzione audio. In questo esempio, per ò, non
user emo un gener ico database, ma uno specifico database di cui conosciamo le pr opr ietà e della cui esistenza siamo
cer ti. Quindi far emo a meno di usar e un gener ico DataSet, ma ne cr eer emo uno specifico per quel database, che sia
più semplice da manipolar e. User emo, quindi, un DataSet tipizzato. Non dovr emo scr iver e alcuna r iga di codice per far
questo, poiché baster à "disegnar e" la str uttur a del database con uno specifico str umento che il nostr o IDE mette a
disposizioni, e che si chiama Table Designer . Mediante quest'ultimo, possiamo delinar e lo schema di un database e delle
sue tabelle, e l'ambiente di sviluppo pr ovveder à a scr iver e un codice adeguato per cr ear e un dataset tipizzato specifico
per quel database. A livello pr atico, un dataset tipizzato non è altr o che una classe der ivata da DataSet che definisce
alcune pr opr ietà e metodi atti a semplificar e la scr ittur a di codice. Ad esempio, invece di r efer enziar e la pr ima cella
della pr ima r iga di una tabella, ottener ne il valor e e conver tir lo in inter o, la ver sione tipizzata del dataset espone
dir ettamente una pr opr ietà ID (ad esempio) che fa tutto questo. C'è solo un difetto nel codice autogener ato, ma lo
illustr er ò in seguito.
Pr ima di iniziar e, bisogna cr ear e effettivamente le tabelle che user emo nel database AppData. Per questo pr ogr amma,
ho ideato tr e tabelle: author s, songs e albums, costr uite come segue:

 CREATE TABLE `albums` (
   `ID` int(11) NOT NULL AUTO_INCREMENT,
   `Name` char(255) NOT NULL,
   `Year` int(11) DEFAULT NULL,
   `Description` text,
   `Image` text,
   PRIMARY KEY (`ID`)
 );

 CREATE TABLE `authors` (
   `ID` int(11) NOT NULL AUTO_INCREMENT,
   `Name` char(255) NOT NULL,
   `Nickname` char(255) DEFAULT NULL,
   `Description` text,
   `Image` text,
   PRIMARY KEY (`ID`)
 );

 CREATE TABLE `songs` (
   `ID` int(11) NOT NULL AUTO_INCREMENT,
   `Path` char(255) NOT NULL,
   `Title` char(255) DEFAULT NULL,
   `Author` int(11) DEFAULT NULL,
   `Album` int(11) DEFAULT NULL,
   PRIMARY KEY (`ID`)
 );


[Gli accenti tonici sono stati aggiunti da SQLyog, e sono obbligator i solo se il nome della colonna o della tabella contiene
degli spazi.]
Pr ima di pr oceder e, potr ebbe esser e utile mostr ar e la toolbar di gestione delle basi di dati: per far questo, cliccate
con il pulsante destr o su uno spazio vuoto della toolbar e spuntate Data Design per far appar ir e le r elative icone:
Per aggiunger e un nuovo dataset tipizzato, invece, cliccate sempr e col destr o sul nome del pr ogetto nel solution
ex plor er , scegliete Add Item e quindi DataSet. Dovr ebbe appar ir vi un nuovo spazio vuoto simile a questo:




Spostando il mouse sulla toolbox a fianco, potr ete notar e che è possibile aggiunger e tabelle, r elazioni, quer ies e alcune
altr e cose. Dato che dobbiamo r icr ear e la stessa str uttur a del database AppData, è necessar io cr ear e tr e tabelle:
Albums, Author s e Songs, ciascuna con le stesse colonne di quelle sopr a menzionate. Tr ascinate un componente
DataTable all'inter no della scher mata e r inominatelo in Songs, quindi fate click col destr o sull'header della tabella e
scegliete Add > Column:




Aggiungete quindi tante colonne quante sono quelle del codice SQL. Or a selezionate la pr ima colonna (ID) e por tate in
pr imo piano la finestr a della pr opr ietà (la stessa usata per i contr olli). Essa visualizzer à alcune infor mazioni sulla
colonna ID. Per r ispettar e il vincolo con il database r eale, essa deve esser e dichiar ata di tipo inter o, deve suppor tar e
l'autoincr emento e non può esser e NULL:




Or a fate lo stesso con tutte le altr e colonne, tenendo conto che char (255) e tex t sono entr ambi contenibili dallo stesso
tipo (Str ing). Pr ima di passar e alla compilazione delle altr e tabelle, r icor datevi di impostar e ID come chiave pr imar ia:
cliccate ancor a sull'header della tabella, scegliendo Add > Key:




Bene. Come avr ete sicur amente notate, i campi Author e Album di Songs non sono str inghe, bensì inter i. Infatti, come
abbiamo visto qualche capitolo fa, è possibile collegar e logicamente due tabelle tr amite una r elazione (uno-a-uno,
uno-a-molti o molti-a-molti). In questo caso, vogliamo collegar e al campo Author di una canzone, la tupla che
r appr esenta quell'autor e nella tabella Author s. Questa è una r elazione uno-a-molti (in questo pr ogr amma semplificato,
assumiamo che tutti color o che hanno par tecipato alla r ealizzazione siano consider ati "autor i", senza le var ie
distinzioni tr a autor e dei testi, ar tist, compositor i ecceter a...). Mediante l'editor integr ato nell'ambiente di sviluppo
possiamo anche aggiunger e questa r ealzione (che andr à a popolar e la pr opr ietà Relations del DataSet). Basta
aggiunger e un oggetto Relation e compilar e i campi r elativi:




Una volta completati tutti i passaggi, è possibile iniziar e a scr iver e qualche r iga di codice (non dimenticatevi di
r iempir e il database con qualche tupla di esempio pr ima di iniziar e il debug).




Music a, maestro!
Ecco l'inter faccia del pr ogr amma:
Oltr e ai contr olli che si vedono nell'immagine, ho aggiunto anche il dataset tipizzato cr eato pr ima dall'editor ,
AppDataSet. Dato che nella listbox sulla sinistr a visualizzer emo i titoli delle canzoni, possiamo eseguir e un binging di
tali dati sulla listbox . Dopo aver la selezionata, andate nella pr opr ietà DataSour ce e scegliete la tabella Songs:




Dopodiché, impostate il campo DisplayMember su Title e ValueMember su ID: come avevo spiegato nel capitolo sulla
listbox , queste pr opr ietà ci per mettono di modificar e cosa viene visualizzato coer entemente con gli oggetti
immagazzinati nella lista. Se avete fatto tutto questo, l'IDE cr eer à automaticamente un nuovo oggetto di tipo
BindingSour ce (il SongsBindingSour ce dell'immagine pr ecedente). Esso ha il compito di mediar e tr a la sor gente dati e
l'inter faccia utente, e ci sar à utile in seguito per la r icer ca.
Ecco il codice:

  001. Public Class Form1
  002.
  003.     Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles
              MyBase.Load
  004.          Dim Conn As New MySqlConnection("Server=localhost; Database=appdata; Uid=root;
                   Pwd=root;")
  005.          Dim Adapter As New MySqlDataAdapter
  006.
  007.          Conn.Open()
  008.
  009.          'Carica le tabelle nel dataset. Le tabelle sono ora
  010.          'accessibili mediante omonime proprietà dal
  011.          'dataset tipizzato
  012.
  013.          Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Songs;", Conn)
  014.          Adapter.Fill(AppDataSet.Songs)
  015.
  016.          Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Authors;", Conn)
  017.          Adapter.Fill(AppDataSet.Authors)
  018.
  019.
Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Albums;", Conn)
020.       Adapter.Fill(AppDataSet.Albums)
021.
022.       Conn.Clone()
023.   End Sub
024.
025.   Private Sub lstSongs_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As
          System.EventArgs) Handles lstSongs.SelectedIndexChanged
026.        If lstSongs.SelectedIndex < 0 Then
027.            Exit Sub
028.        End If
029.
030.       'Trova la riga con ID specificato. Il tipo SongsRow è stato
031.       'definito dall'IDE durante la creazione del dataset tipizzato.
032.       Dim S As AppDataSet.SongsRow = AppDataSet.Songs.FindByID(lstSongs.SelectedValue)
033.
034.       lblName.Text = S.Title
035.
036.       tabAuthor.Tag = Nothing
037.       'Anche il metodo IsAuthorNull è stato definito dall'IDE.
038.       'In generale, per ogni proprietà per cui non è stata
039.       'specificata la clausola NOT NULL, l'IDE crea un metodo per
040.       'verificare se quel dato attributo contiene un valore
041.       'nullo. Equivale a chiamare S.IsNull(3).
042.       If Not S.IsAuthorNull() Then
043.           'Ottiene un array che contiene tutte le righe della
044.           'tabella Authors che soddisfano la relazione definita
045.           'tra Songs e Authors. Dato che la chiave esterna della
046.           'tabella figlio era un ID, la realzione, pur essendo
047.           'teoricamente uno-a-molti, è in realtà
048.           'uno-a-uno. Perciò, se questo array ha almeno
049.           'un elemento, ne avrà solo uno.
050.           Dim Authors() As AppDataSet.AuthorsRow = S.GetAuthorsRows()
051.           'Come IsNull, questo metodo equivale a chiamare
052.           'S.GetChildRows("Songs_Authors")
053.
054.           If Authors.Length > 0 Then
055.               Dim Author As AppDataSet.AuthorsRow = Authors(0)
056.
057.               lblAuthorName.Text = Author.Name
058.               If Not Author.IsNicknameNull() Then
059.                    lblAuthorName.Text &= vbCrLf & Chr(34) & Author.Nickname & Chr(34)
060.               End If
061.               If Not Author.IsImageNull() Then
062.                    imgAuthor.Image = Image.FromFile(Author.Image)
063.               Else
064.                    imgAuthor.Image = Nothing
065.               End If
066.
067.               If Not Author.IsDescriptionNull() Then
068.                    txtAuthorDescription.Text = Author.Description
069.               Else
070.                    txtAuthorDescription.Text = ""
071.               End If
072.               tabAuthor.Tag = Author.ID
073.           End If
074.       End If
075.
076.       tabAlbum.Tag = Nothing
077.       If Not S.IsAlbumNull() Then
078.           Dim Albums() As AppDataSet.AlbumsRow = S.GetAlbumsRows()
079.
080.           If Albums.Length > 0 Then
081.               Dim Album As AppDataSet.AlbumsRow = Albums(0)
082.
083.               lblAlbumName.Text = Album.Name
084.               If Not Album.IsYearNull() Then
085.                    lblAlbumYear.Text = Album.Year
086.               Else
087.                    lblAlbumYear.Text = ""
088.               End If
089.               If Not Album.IsImageNull() Then
090.
imgAlbum.Image = Image.FromFile(Album.Image)
091.                 Else
092.                      imgAlbum.Image = Nothing
093.                 End If
094.                 If Not Album.IsDescriptionNull() Then
095.                      txtAlbumDescription.Text = Album.Description
096.                 Else
097.                      txtAlbumDescription.Text = ""
098.                 End If
099.                 tabAlbum.Tag = Album.ID
100.             End If
101.         End If
102.     End Sub
103.
104.     Private Sub btnSearch_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
            Handles btnSearch.Click
105.          If Not String.IsNullOrEmpty(txtSearch.Text) Then
106.               'La proprietà Filter di un binding source è come
107.               'una condizione SQL. In questo caso cerchiamo tutte le
108.               'canzoni il cui titolo contenga la stringa specificata.
109.               SongsBindingSource.Filter = String.Format("title like '%{0}%'", txtSearch.Text)
110.          Else
111.               SongsBindingSource.Filter = ""
112.          End If
113.     End Sub
114.
115. End Class
C5. Dalle relazioni agli oggetti - Parte II


Aggiungere, eliminare, modific are
L'ultimo esempio di codice per metteva solo di scor r er e elementi già pr esenti nel database, ma questo è davver o poco
utile all'utente. Vediamo, allor a, di aggiunger e un po' di dinamismo all'applicazione. Volendo gestir e tutto in manier a
or dinata, sar ebbe bello che ci fosse un contr ollo dedicato a visualizzar e, modificar e e salvar e le infor mazioni sull'autor e
e uno identico per l'album. A questo scopo, possiamo scr iver e dei nuovi contr olli utente. Io ho scr itto solo il pr imo,
poiché il codice per il secondo è pr essoché identico:

   01. Public Class AuthorViewer
   02.     Private _Author As AppDataSet.AuthorsRow
   03.
   04.     'Evento generato quando un autore viene aggiunto. Questo
   05.     'evento si verifica se l'utente salva dei cambiamenti
   06.     'quando la proprietà Author è Nothing.
   07.     'Non potendo modificare una riga esistente, quindi, ne
   08.     'viene creata una nuova. Poich´, tuttavia, questo
   09.     'autore deve essere associato alla canzone, bisogna che
   10.     'qualcuno ponga l'ID della nuova riga nel campo
   11.     'Author della canzone opportuna e, dato che questo
   12.     'controllo non può n´ logicamente né
   13.     'praticamente arrivare a fare ciò, bisogna che
   14.     'qualcun altro se ne occupi.
   15.     Public Event AuthorAdded As EventHandler
   16.
   17.     Public Property Author() As AppDataSet.AuthorsRow
   18.          Get
   19.              Return _Author
   20.          End Get
   21.          Set(ByVal value As AppDataSet.AuthorsRow)
   22.              If value IsNot Nothing Then
   23.                   _Author = value
   24.                   With value
   25.                       lblName.Text = .Name
   26.                       If Not .IsImageNull() Then
   27.                            imgAuthor.ImageLocation = .Image
   28.                       Else
   29.                            imgAuthor.ImageLocation = Nothing
   30.                       End If
   31.                       If Not .IsDescriptionNull() Then
   32.                            txtDescription.Text = .Description
   33.                       Else
   34.                            txtDescription.Text = ""
   35.                       End If
   36.                   End With
   37.              Else
   38.                   lblName.Text = "Nessun nome"
   39.                   imgAuthor.ImageLocation = Nothing
   40.                   txtDescription.Text = ""
   41.              End If
   42.              imgSave.Visible = False
   43.          End Set
   44.     End Property
   45.
   46.     Private Sub imgAuthor_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
              Handles imgAuthor.Click
   47.          'FOpen è un OpenFileDialog dichiarato nel designer.
   48.          'Questo codice serve per caricare un'immagine da disco
   49.          'fisso
   50.          If FOpen.ShowDialog = DialogResult.OK Then
   51.              imgAuthor.ImageLocation = FOpen.FileName
   52.              imgSave.Visible = True
   53.          End If
   54.
End Sub
   55.
   56.         'L'immagine del floppy diventa visibile solo quando c'è stata
   57.         'una modifica, ossia è stato cambiato uno di questi
   58.         'parametri: nome, immagine, descrizione.
   59.         Private Sub txtDescription_TextChanged(ByVal sender As System.Object, ByVal e As
                  System.EventArgs) Handles txtDescription.TextChanged
   60.              imgSave.Visible = True
   61.         End Sub
   62.
   63.         Private Sub lblName_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
                  Handles lblName.Click
   64.              Dim NewName As String = InputBox("Inserire nome:")
   65.
   66.             If Not String.IsNullOrEmpty(NewName) Then
   67.                 lblName.Text = NewName
   68.                 imgSave.Visible = True
   69.             End If
   70.         End Sub
   71.
   72.       Private Sub imgSave_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
                Handles imgSave.Click
   73.            If _Author Is Nothing Then
   74.                 'Crea la nuova riga e la inserisce nel dataset
   75.                 'principale. Notare che questo approccio non è
   76.                 'il migliore possibile, poich´ è sempre
   77.                 'consigliabile rendere il codice il più generale
   78.                 'possibile, e limitare i riferimenti agli altri form.
   79.                 'Sarebbe stato più utile rendere AppDataSet
   80.                 'visibile all'intero progetto mediante un
   81.                 'modulo pubblico.
   82.                 _Author = My.Forms.Form1.AppDataSet.Authors.AddAuthorsRow(lblName.Text, "",
                          txtDescription.Text, imgAuthor.ImageLocation)
   83.                 'Genera l'evento AuthorAdded
   84.                 RaiseEvent AuthorAdded(Me, EventArgs.Empty)
   85.            Else
   86.                 _Author.Name = lblName.Text
   87.                 _Author.Description = txtDescription.Text
   88.                 _Author.Image = imgAuthor.ImageLocation
   89.            End If
   90.            imgSave.Visible = False
   91.       End Sub
   92.   End Class

E questa è l'inter faccia:
È pr esente uno split container , in cui nella par te sinistr a c'è la pictur ebox (con dock=fill) e nella par te destr a la
tex tbox . L'immagine del floppy ser ve per avviar e il salvataggio dei dati nell'oggetto Author sRow sotteso (ma non nel
database).
E questo è il codice dell'applicazione, modificato in modo da suppor tar e il nuovo contr ollo (solo per l'autor e):

 001. Imports MySql.Data.MySqlClient
 002.
 003. Public Class Form1
 004.
 005.     Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles
             MyBase.Load
 006.          Dim Conn As New MySqlConnection("Server=localhost; Database=appdata; Uid=root;
                  Pwd=root;")
 007.          Dim Adapter As New MySqlDataAdapter
 008.
 009.          Conn.Open()
 010.
 011.          Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Songs;", Conn)
 012.          Adapter.Fill(AppDataSet.Songs)
 013.
 014.          Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Authors;", Conn)
 015.          Adapter.Fill(AppDataSet.Authors)
 016.
 017.          Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Albums;", Conn)
 018.          Adapter.Fill(AppDataSet.Albums)
 019.
 020.          Conn.Clone()
 021.     End Sub
 022.
 023.     Private Sub lstSongs_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As
             System.EventArgs) Handles lstSongs.SelectedIndexChanged
 024.          If lstSongs.SelectedIndex < 0 Then
 025.               Exit Sub
 026.          End If
 027.
 028.          Dim S As AppDataSet.SongsRow = AppDataSet.Songs.FindByID(lstSongs.SelectedValue)
 029.
 030.          lblName.Text = S.Title
 031.
 032.          tabAuthor.Tag = Nothing
 033.          If Not S.IsAuthorNull() Then
 034.               Dim Authors() As AppDataSet.AuthorsRow = S.GetAuthorsRows()
 035.
 036.               'Imposta la proprietà Author del controllo avAuthor,
 037.               'che non è altro che un'istanza di AuthorViewer,
 038.               'il controllo utente creato poco fa
 039.               If Authors.Length > 0 Then
 040.                    Dim Author As AppDataSet.AuthorsRow = Authors(0)
 041.                    avAuthor.Author = Author
 042.               Else
 043.                    avAuthor.Author = Nothing
 044.               End If
 045.          End If
 046.
 047.          tabAlbum.Tag = Nothing
 048.          If Not S.IsAlbumNull() Then
 049.               Dim Albums() As AppDataSet.AlbumsRow = S.GetAlbumsRows()
 050.
 051.               If Albums.Length > 0 Then
 052.                    Dim Album As AppDataSet.AlbumsRow = Albums(0)
 053.
 054.                    lblAlbumName.Text = Album.Name
 055.                    If Not Album.IsYearNull() Then
 056.                         lblAlbumYear.Text = Album.Year
 057.                    Else
 058.                         lblAlbumYear.Text = ""
 059.
End If
060.                 If Not Album.IsImageNull() Then
061.                      imgAlbum.Image = Image.FromFile(Album.Image)
062.                 Else
063.                      imgAlbum.Image = Nothing
064.                 End If
065.                 If Not Album.IsDescriptionNull() Then
066.                      txtAlbumDescription.Text = Album.Description
067.                 Else
068.                      txtAlbumDescription.Text = ""
069.                 End If
070.                 tabAlbum.Tag = Album.ID
071.             End If
072.         End If
073.     End Sub
074.
075.     Private Sub btnSearch_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
            Handles btnSearch.Click
076.          If Not String.IsNullOrEmpty(txtSearch.Text) Then
077.               SongsBindingSource.Filter = String.Format("title like '%{0}%'", txtSearch.Text)
078.          Else
079.               SongsBindingSource.Filter = ""
080.          End If
081.     End Sub
082.
083.     Private Sub avAuthor_AuthorAdded(ByVal sender As System.Object, ByVal e As
            System.EventArgs) Handles avAuthor.AuthorAdded
084.          If lstSongs.SelectedIndex < 0 Then
085.              Exit Sub
086.          End If
087.
088.         Dim S As AppDataSet.SongsRow = AppDataSet.Songs.FindByID(lstSongs.SelectedValue)
089.         'Imposta il campo Author della canzone corrente
090.         S.Author = avAuthor.Author.ID
091.     End Sub
092.
093.     Private Sub Form1_FormClosing(ByVal sender As System.Object, ByVal e As
            System.Windows.Forms.FormClosingEventArgs) Handles MyBase.FormClosing
094.          Dim Conn As New MySqlConnection("Server=localhost; Database=appdata; Uid=root;
                 Pwd=root;")
095.          Dim Adapter As New MySqlDataAdapter
096.
097.         'Un oggetto di tipo CommandBuilder genera automaticamente
098.         'tutti le istruzioni update, insert e delete che servono
099.         'a un adapter per funzionare. Tali istruzioni vengono
100.         'generate relativamente alla tabella dalla quale si stanno
101.         'caricando i dati
102.         Dim Builder As MySqlCommandBuilder
103.
104.         Conn.Open()
105.
106.         Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Songs;", Conn)
107.         Builder = New MySqlCommandBuilder(Adapter)
108.         Adapter.Update(AppDataSet.Songs)
109.
110.         Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Authors;", Conn)
111.         Builder = New MySqlCommandBuilder(Adapter)
112.         Adapter.Update(AppDataSet.Authors)
113.
114.         Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Albums;", Conn)
115.         Builder = New MySqlCommandBuilder(Adapter)
116.         Adapter.Update(AppDataSet.Albums)
117.
118.         Conn.Clone()
119.     End Sub
120. End Class
C6. Il controllo BindingNavigator


Funzionamento
Questo contr ollo per mette di navigar e attr aver so insiemi di dati, siano essi tabelle di database o semplici liste di
oggetti non fa differ enza, per mettendo di visualizzar e o modificar e una qualsiasi delle lor o pr opr ietà e di aggiunger e
od eliminar e uno qualsiasi dei suoi elementi. La par ticolar ità che lo distingue da qualsiasi altr o contr ollo del gener e
(come potr ebber o esser e ListView o DataGr idView ) consiste nel fatto che la sua inter faccia non è una tabella: anzi, è a
pr ior i indefinita. Se si consider a poi il fatto che aggiunger lo semplicemente al for m non por ter à alcun r isultato, si
potr ebbe pensar e che BindingNavigator è pr opr io una fr egatur a XD
In effetti, per veder lo funzionar e cor r ettamente bisogna aggiunger e un po' di altr i contr olli e scr iver e qualche r iga di
codice. Infatti, ho appena detto che esso per mette di navigar e attr aver so un insieme di dati e visualizzar e tali dati su
una cer ta inter faccia gr afica che per or a non conosciamo: le incognite, quindi, sono due, ossia da do v e attinger e i dati
e co m e visualizzar li. Per questo motivo, sono necessar i almeno altr i due componenti. Il pr imo di questi è un contr ollo
BindingSour ce, il quale, come già visto nel capitolo pr ecedente, si pr eoccupa di gestir e e mediar e l'inter azione con una
cer ta r isor sa di infor mazioni. Il secondo (e gli altr i eventuali) è ar bitr ar io e dipende dalla natur a dei dati da
visualizzar e: per delle str inghe, ad esempio, avr emo bisogno di una Tex tBox .




Autori illustri...
Per esemplificar e il compor tamento di BindingNavigator , ecco una semplice applicazione che per mette di visualizzar e
una ser ie di gr andi nomi e le lor o oper e pr incipali. La nostr a fonte di dati sar à una lista di oggetti di tipo Author ,
classe così definita:

   01. Public Class Form1
   02.
   03.     Public Class Author
   04.         Private _Name As String
   05.         Private _Works As List(Of String)
   06.
   07.         Public Property Name() As String
   08.              Get
   09.                  Return _Name
   10.              End Get
   11.              Set(ByVal value As String)
   12.                  _Name = value
   13.              End Set
   14.         End Property
   15.
   16.         Public ReadOnly Property Works() As List(Of String)
   17.              Get
   18.                  Return _Works
   19.              End Get
   20.         End Property
   21.
   22.         Public Sub New()
   23.              _Works = New List(Of String)
   24.         End Sub
   25.
   26.         Public Sub New(ByVal Name As String, ByVal ParamArray WorksNames() As String)
   27.              Me.New()
   28.              Me.Name = Name
   29.              Me.Works.AddRange(WorksNames)
   30.         End Sub
   31.     End Class
   32.
   33.
Public Authors As New List(Of Author)
   34.
   35. End Class

Or a aggiungiamo al for m un BindingNavigator di nome bnMain:




All'aspetto sembr a solo una toolstr ip con qualche button, due label e una tex tbox . È questo, e anche di più.
Aggiungiamo or a un BindingSour ce di nome bsData e impostiamo la pr opr ietà bsMain.BindingSour ce su bsData.
Aggiungete altr i contr olli in modo che l'inter faccia sia la seguente:




Vor r emo usar e il pulsanti fr eccia del binding navigator per spostar ci avanti e indietr o nella lista, e i r ispettivi pulsanti
per aggiunger e o eliminar e un elemento. Il codice:

   01. Public Class Form1
   02.
   03.     Public Class Author
   04.          Private _Name As String
   05.          Private _Works As List(Of String)
   06.
   07.          Public Property Name() As String
   08.               Get
   09.                   Return _Name
   10.               End Get
   11.               Set(ByVal value As String)
   12.                   _Name = value
   13.               End Set
   14.          End Property
   15.
   16.          Public ReadOnly Property Works() As List(Of String)
   17.               Get
   18.                   Return _Works
   19.               End Get
   20.          End Property
   21.
   22.          Public Sub New()
   23.               _Works = New List(Of String)
   24.          End Sub
   25.
   26.          Public Sub New(ByVal Name As String, ByVal ParamArray WorksNames() As String)
   27.               Me.New()
   28.               Me.Name = Name
   29.               Me.Works.AddRange(WorksNames)
   30.          End Sub
   31.     End Class
   32.
   33.     Public Authors As New List(Of Author)
   34.
   35.     Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles
              MyBase.Load
   36.          'Aggiungie alcuni elementi iniziali alla lista
   37.          Authors.Add(New Author("Dante Alighieri", "Comedìa", "Vita Nova", "De vulgari
                   eloquentia", "De Monarchia"))
   38.          Authors.Add(New Author("Luigi Pirandello", "Il fu Mattia Pascal", "Uno, nessuno,
                   centomila", "Il gioco delle parti"))
   39.          'Imposta la sorgente di dati del bindingsource
   40.          bsAuthors.DataSource = Authors
   41.
   42.          'Aggiunge un binding alla textbox. Ciò significa
   43.          'che la proprietà Text di txtName sarà da
   44.          'ora in poi sempre legata alla proprietà Name
   45.          'dell'elemento corrente della sorgente di dati di
   46.          'bsAuthors. Dato che quest'ultima è una lista di
   47.          'Author, ogni suo elemento espone la proprietà
   48.          'Name.
   49.          txtName.DataBindings.Add("Text", bsAuthors, "Name")
   50.
51.            'Non possiamo fare la stessa cosa con lstWorks.Items,
   52.            'poiché Items è una proprietà readonly.
   53.            'Questo capita abbastanza spesso: si ha bisogno di
   54.            'visualizzare una lista per ogni elemento dell'insieme.
   55.            'La soluzione consiste nel caricare gli items della
   56.            'lista quando viene caricato un nuovo elemento
   57.        End Sub
   58.
   59.        Private Sub bsAuthors_CurrentChanged(ByVal sender As System.Object, ByVal e As
                 System.EventArgs) Handles bsAuthors.CurrentChanged
   60.             'L'evento CurrentChanged si verifica quando la proprietà
   61.             'Current del binding source viene modificata. Ciò significa
   62.             'che l'utente si è spostato tramite il binding
   63.             'navigator. Ottenendo l'oggetto Current, possiamo risalire alla
   64.             'lista di stringhe che esso contiene
   65.             Dim Author As Author = bsAuthors.Current
   66.             lstWorks.Items.Clear()
   67.             lstWorks.Items.AddRange(Author.Works.ToArray())
   68.        End Sub
   69.
   70.        Private Sub btnAdd_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
                 Handles btnAdd.Click
   71.             'Aggiunge alla listbox e alla lista Works un nuovo
   72.             'titolo aggiunto dall'utente
   73.             If Not String.IsNullOrEmpty(txtAdd.Text) Then
   74.                 lstWorks.Items.Add(txtAdd.Text)
   75.                 DirectCast(bsAuthors.Current, Author).Works.Add(txtAdd.Text)
   76.                 txtAdd.Text = ""
   77.             End If
   78.        End Sub
   79.
   80.       Private Sub btnDelete_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
                Handles btnDelete.Click
   81.            'Elimina una delle opere visualizzate
   82.            If lstWorks.SelectedIndex >= 0 Then
   83.                DirectCast(bsAuthors.Current, Author).Works.RemoveAt(lstWorks.SelectedIndex)
   84.                lstWorks.Items.RemoveAt(lstWorks.SelectedIndex)
   85.            End If
   86.       End Sub
   87.   End Class

Come vedete, il codice è molto r idotto anche se l'applicazione suppor ta un numer o più elevato di funzionalità: tutto ciò
che non abbiamo scr itto viene automaticamente gestito dal BindingNavigator .
La pr opr ietà DataBindings, per inciso, non appar tiene solo a Tex tBox , ma a tutti i contr olli e non è necessar io
specificar e come sor gente di dati un binding sour ce, ma un qualsiasi oggetto, poiché tutto viene gestito tr amite
r eflection. È possibile associar e una qualsiasi pr opr ietà di un contr ollo ad un campo di un qualsiasi altr o oggetto.
Allo stesso modo, è possibile associar e alla pr opr ietà DataSour ce di BindingSour ce una tabella di un database, o un
dataset (e associate un dataset, dovr ebe usar e la pr opr ietà DataMember per specificar e quale tabella).
C7. DataGridView - Parte I


Introduzione
DataGr idView è uno dei contr olli più potenti e gr andi del Fr amew or k .NET. Consente la visualizzazione di dati in una
tabella ed è per questo motivo for temente cor r elato all'uso dei database, anche se natur almente suppor ta, tr amite la
pr opr ietà DataSour ce, il binding di qualsiasi oggetto:




Ecco un elenco delle pr opr ietà inter essanti:

       Ar eAllCellsSelected(includeInvisible As Boolean) : r estituisce Tr ue se tutte le celle sono selezionate. Se
       includeInvisible è Tr ue, include nel contr ollo anche quelle colonne che nor malmente sono nascoste (è possibile
       nasconder e colonne indesider ate, come ad esempio l'ID);
       Allow User To AddRow s, DeleteRow s, Or der Columns, ResizeColumns, ResizeRow s: una ser ie di pr opr ietà booleane
       che deter minano se l'utente sia o meno in gr ado di aggiunger e o r imuover e r ighe, or dinar e le colonne o
       r idimensionar e sia le r ighe che le colonne;
       Alter natingRow sDefaultCellStyle : specifica il CellStyle (un insieme di pr opr ietà che definiscono l'aspetto estetico
       di una cella) per le r ighe di posto dispar i. Se questo valor e è diver so da DefaultCellStyle, avr emo le r ighe di stile
       alter nato (ad esempio una di un color e e una di un altr o), da cui il nome di questo membr o;
       AutoResize... : tutti i metodi che iniziano con "AutoResize" ser vono per r idimensionar e r ighe o colonne;
       AutoSizeColumnsMode : pr opr ietà enumer ata che deter mina in che modo le colonne vengano r idimensionate
       quando del testo va oltr e i confini visibili. I valor i che può assumer e sono:
               None : le colonne r imangono sempr e della stessa lar ghezza, a meno che l'utente non le r idimensioni
               manualmente;
               AllCells : la colonna viene allar gata affinché il testo di tutte le celle sottostanti e della stessa intestazione
               sia visibile;
               AllCellsEx ceptHeader : la colonna viene allar gata in modo che solo il testo di tutte le celle sottostanti sia
               visibile;
               ColumnHeader : la colonna viene allar gata in modo che il testo dell'header sia inter amente visibile;
               DisplayedCells : come AllCells, ma solo per le celle visibili nei mar gini del contr ollo;
               DisplayedCellsEx ceptHeader : come AllCellsEx ceptHeader , ma solo per le celle visibili nei mar gini del
               contr ollo;
               Fill : le colonne vengono r idimensionate affinché la lor o lar ghezza totale sia quanto più possibile vicina
               all'ar ea effettivamente visibile a scher mo, nei limiti imposti dalla pr opr ietà MinimumWidth di ciascuna
               colonna.
       AutoSizeRow sMode : come sopr a, ma per le r ighe;
       CancelEdit() : ter mina l'editing di una cella e annulla tutte le modifiche ad essa appor tate;
       CellBor der Style : pr opr ietà enumer ata che definisce come sia visualizzato il bor do delle celle. Inutile descr iver ne
       tutti i valor i: basta pr ovar li tutti per veder e come appaiono;
       Clear Selection: deseleziona tutte le celle selezionate
       ColumnCount / Row Count : deter mina il numer o iniziale di colonne o r ighe visualizzate sul contr ollo
       ColumnHeader s / Row Header s : imposta lo stile di visualizzazione, i bor di, le dimensioni e la visibilità delle
       intestazioni
       Columns : insieme di tutte le colonne del contr ollo. Tr amite questa pr opr ietà è possibile deter minar e quali siano
i tipi di valor i che si possono immetter e in una cella. Nella finestr a di dialogo dur ante la scr ittur a del
pr ogr amma, infatti, quando si aggiunge una colonna non a r untime, viene anche chiesto quale debba esser e il
suo tipo, pr oponendo una gamma abbastanza ampia di possibilità (tex tbox , combobox , checkbox , image, button,
linklabel)
Cur r entCell : imposta o r estituisce la cella selezionata (un oggetto di tipo DataGr idView Cell)
Cur r entCellAddr ess : r estituisce due coor dinate sotto for ma di Point che indicano la colonna e la r iga della cella
selezionata
Cur r entRow : indica la r iga contenente la cella selezionata (o la r iga selezionata se tutte le sue celle sono
selezionate);
DefaultCellStyle : specifica lo stile pr edefinito per una cella; ogni cella, poi, può modificar e il pr opr io aspetto
mediante la pr opr ietà Style;
DisplayedPar tialColumns/Row s(includePar tial As Boolean) : r estituisce il numer o di colonne / r ighe visibili nel
contr ollo. Se includePar tial è Tr ue, include nel conteggio anche quelle che si vedono solo par zialmente;
EditMode : pr opr ietà enumer ata che indica in che modo sia possibile iniziar e a modificar e il contenuto di una
cella. I valor i che può assumer e sono:
        EditOnEnter : inizia la modifica quando la cella viene selezionata, quando r iceve il focus oppur e quando
        viene pr emuto invio su di essa;
        EditOnF2 : inizia la modifica quando l'utente pr eme F2 sulla cella selezionata;
        EditOnKeystr oke : inizia la modifica quando viene pr emuto un qualsiasi tasto (alfanumer ico) mentr e la
        cella ha il focus;
        EditOnKeystr okeOr F2 : è palese...
        EditPr ogr ammatically : inizia la modifica so lo quando viene esplicitamente r ichiamato da codice il
        metodo BeginEdit;
Fir stDisplayedCell : ottiene o imposta un r ifer imento alla pr ima cella visualizzata;
GetCellCount(Filter ) : r estituisce il numer o di celle che soddisfano il filtr o impostato. Filter non à altr o che un
valor e enumer ato codificato a bit che contempla questi valor i: Displayed (celle visualizzate), Fr ozen (celle che è
impossibile scr ollar e), None (celle che sono in stato di default), ReadOnly (celle a sola lettur a), Resizable and
ResizableSet (se specificati entr ambi, indicano le celle r idimensionabili), Selected (celle selezionate), Visible (celle
visibili, nel senso che è possibile veder le, non che sono effettivamente visualizzate);
HitTest(x , y) : r estituisce un valor e str uttur ato contenente r iga e colonna della cella che si tr ova alle coor dinate
r elative x e y (in pix el);
IsCur r entCellDir ty : indica se la cella cor r ente contiene modifiche non salvate;
IsCur r entCellInEditMode : indica se la cella cor r ente è in modalità edit (modifica);
IsCur r entRow Dir ty : indica se la r iga cor r ente contiene modifiche non salvate;
Item(x ,y): r estituisce la cella alle coor dinate x e y (colonna e r iga). La sua pr opr ietà più impor tante è Value, che
r estituisce o imposta il valor e contenuto nella cella, che può esser e un testo, un valor e booleano, una combobox
ecceter a
MultiSelect: deter mina qualor a sia possibile selezionar e più celle, colonne o r ighe insieme;
Row ... : tutte le pr opr ietà che iniziano con "Row " sono analoghe a quelle spiegate per Column;
ReadOnly : deter mina se l'utente possa o meno modificar e i contenuto delle celle
SelectedCells/Columns/Row s : r estituisce un insieme delle celle/colonne/r ighe cor r entemente selezionate;
SelectionMode : pr opr ietà enumer ata che indica come debba avvenir e la selezione. Può assumer e 5 valor i:
CellSelect (solo la cella), FullRow Select (tutta la r iga), FullColumnSelect (tutta la colonna), Row Header Select (solo
l'intestazione della r iga) o ColumnHeader Select (solo l'intestazione della colonna);
Show CellToolTip : indica se visualizzar e il tooltip della cella;
Show EditingIcon : indica se visualizzar e l'icona di modifica;
Selected Cells, Columns, Row : tr e collezioni che r estituiscono un insieme di tutte le celle, colonne o r ighe
selezionate;
Sor t(a, b): utilissima pr ocedur a che or dina la colonna a della datagr idview secondo un or dine ascendente o
           discendente definito da un enumer ator e di tipo ComponentModel.ListSor tDir ection;
           Standar dTab : indica se il pulsante Tab viene usato per ciclar e i contr olli (tr ue) o le celle all'inter no del
           datagr idview (false);

Come avete visto c'è una mar ea di membr i, e un numer o consistente di questi sono dedicati ad impostar e l'"estetica"
del contr ollo. Ma tutto questo non è nulla se confr ontato alla quantità di eventi che DataGr idView espone (e al numer o
di distur bi mentali che è solita causar e nei pr ogr ammator i sani).




Un c lassic o esempio di gestionale
La DataGr idView è un contr ollo usatissimo sopr attutto in quei noiosissimi pr ogr ammi che qualcuno chiama gestionali,
e che un gr an numer o di pover i pr ogr ammator i è costr etto a scr iver e per guadagnar si il pane quotidiano. Per questo
motivo, il pr ossimo esempio sar à par ticolar mente r ealistico. Andr emo a scr iver e un'applicazione per gestir e clienti e
or dini.
Pr ima di iniziar e, cr eiamo le tabelle che ci ser vir anno per il pr ogr amma (sì, user emo un database):

 CREATE TABLE `customers` (
     `ID` int(11) NOT NULL AUTO_INCREMENT,
     `FirstName` char(150) DEFAULT NULL,
     `LastName` char(150) DEFAULT NULL,
     `Address` char(255) DEFAULT NULL,
     `PhoneNumber` char(30) DEFAULT NULL,
     `RegistrationDate` date NOT NULL,
     `AccountType` int(5) DEFAULT '0',
     PRIMARY KEY (`ID`)
     );

 CREATE TABLE `orders` (
     `ID` int(11) NOT NULL AUTO_INCREMENT,
     `CustomerID` int(11) NOT NULL,
     `ItemName` char(255) NOT NULL,
     `ItemPrice` float unsigned NOT NULL,
     `ItemCount` int(10) unsigned DEFAULT '1',
     `CreationDate` date NOT NULL,
     `Settled` tinyint(1) NOT NULL,
     PRIMARY KEY (`ID`)
     );


E, per completezza, bisogna aggiunger e anche le r ispettive r appr esentazioni tabular i in un nuovo dataset (si tr atta
solo di r icopiar e).
Lo scopo del pr ogr amma consiste nel gestir e una ser ie di clienti e di or dini associati, indicando lo stato di ciascuno e le
sue condizioni di pagamento. Avr emo, quindi, una datagr idview per contener e i clienti, una per conter e gli or dini e
una listview per visualizzar e un r iepilogo delle infor mzioni. Inoltr e, iniziamo subito con una casistica un po' complicata:
pr ima di tutto vogliamo che il tipo dell'account di ciascun cliente sia visualizzato sottofor ma di icona; poi vogliamo
anche che la seconda datagr idview visualizzi so lo gli or dini associati al cliente cor r entemente selezionato. La pr ima
r ichiesta ver r à gestita completamente da codice, ma è oppor tuno che si aggiunga un'ImageList contenente almeno tr e
piccole immagini (16x 16 vanno bene), mentr e per la seconda avr emo bisogno un po' di aiuto da par te di BindingSour ce.
Nei capitoli pr ecedenti, infatti, si è visto che questo componente espone una pr opr ietà Filter mediante la quale
possiamo r estr inger e l'insieme di dati visualizzati sotto cer ti par ametr i (aggiungete quindi anche un BindingSour ce di
nome bsOr der s, ed impostate il suo DataSour ce sul dataset pr incipale, e il suo DataMember su Or der s). Ecco il codice:

  001. Imports MySql.Data.MySqlClient
  002.
003.   'Prima di iniziare:
004.   ' - MainDatabase è un'istanza di AppDataSet, ossia il
005.   ' dataset tipizzato creato mediante l'editor che contiene le
006.   ' due tabelle.
007.   ' - bsOrders è il BindingSource che useremo come sorgente
008.   ' dati per dgvOrders. Ricordatevi che:
009.   '   bsOrders.DataSource = MainDatabase
010.   '   bsOrders.DataMember = "Orders"   o   MainDatabase.Orders
011.   ' - AllowUserToAddRows = False per entrambi i datagridview
012.
013.   Public Class Form1
014.
015.       Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles
              MyBase.Load
016.            Dim Connection As New MySqlConnection("Server=localhost; Database=appdata; Uid=root;
                   Pwd=root;")
017.            Dim Adapter As New MySqlDataAdapter()
018.
019.           Try
020.               Connection.Open()
021.               Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Customers;", Connection)
022.               Adapter.Fill(MainDatabase.Customers)
023.               Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Orders;", Connection)
024.               Adapter.Fill(MainDatabase.Orders)
025.           Catch Ex As Exception
026.               MessageBox.Show("Impossibile connettersi al database!", Me.Text,
                      MessageBoxButtons.OK, MessageBoxIcon.Error)
027.           Finally
028.               Connection.Close()
029.           End Try
030.
031.           'Imposta la sorgente dati di dgvCustomers. Quando questa
032.           'proprietà viene impostata, crea automaticamente tutte
033.           'le colonne necessarie, ne imposta il tipo e
034.           'l'intestazione. Per questo motivo, tutte le colonne che
035.           'andremo ad usare iniziano ad esistere solo dopo questa
036.           'riga di codice. Tutte quelle che erano state definite
037.           'prima vengono eliminate.
038.           dgvCustomers.DataSource = MainDatabase.Customers
039.           'Nasconde la prima e la settima colonna, ossia l'ID e
040.           'l'AccountType. La prima non deve essere visibile poiché
041.           'contiene dati che non riguardano l'utente, mentre
042.           'l'ultima la nascondiamo perchè avevamo detto di
043.           'voler visualizzare il tipo di account con un'icona
044.           dgvCustomers.Columns(0).Visible = False
045.           dgvCustomers.Columns(6).Visible = False
046.
047.           'Crea una nuova colonna di datagridview adatta a contenere
048.           'immagini. Esistono molti tipi di colonna (button, combobox,
049.           'linklabel, image, eccetera...), di cui la più comune
050.           'è una che contiene solo testo. Il tipo di una
051.           'colonna indica il tipo di dati che tutte le celle ad essa
052.           'sottostanti devono contenere. In questo caso vogliamo
053.           'che l'ultima colonna contenga una piccola immagine
054.           'indicante il livello dell'account (ci saranno tre livelli)
055.           Dim Img As New DataGridViewImageColumn
056.           'Imposta l'immagine di default
057.           Img.Image = imgAccountTypes.Images(0)
058.           'E l'intestazione della colonna
059.           Img.HeaderText = "AccountLevel"
060.           'Quindi la aggiunge a quelle esistenti
061.           dgvCustomers.Columns.Add(Img)
062.
063.           'Poi cicla attraverso tutte le righe, controllando il
064.           'contenuto della settima cella di ogni riga (ossia il
065.           'valore corrdispondente ad AccountType, un numero intero),
066.           'e imposta il contenuto dell'ottava prelevando la
067.           'rispettiva immagine dall'imagelist.
068.           'Ricordate che la colonna di indice 6, pur essendo
069.           'nascosta, esiste comunque
070.           For Each Row As DataGridViewRow In dgvCustomers.Rows
071.
Row.Cells(7).Value = imgAccountTypes.Images(CInt(Row.Cells(6).Value))
072.       Next
073.
074.       'Imposta come sorgente di dati di dgvOrders il binding
075.       'source bsOrders, specificato in precedenza
076.       dgvOrders.DataSource = bsOrders
077.       'E nasconde le prime due colonne, ossia CustomerID e ID
078.       dgvOrders.Columns(0).Visible = False
079.       dgvOrders.Columns(1).Visible = False
080.       'Impone di visualizzare solo le tuple di ID pari a -1,
081.       'ossia nessuna
082.       bsOrders.Filter = "ID=-1"
083.   End Sub
084.
085.   Private Sub dgvCustomers_KeyDown(ByVal sender As System.Object, ByVal e As
          System.Windows.Forms.KeyEventArgs) Handles dgvCustomers.KeyDown
086.        'Intercettiamo la pressione di un pulsante quando l'utente
087.        'si trova nell'ultima colonna (quella dell'icona)
088.        If dgvCustomers.CurrentCell.ColumnIndex = 7 Then
089.            'Ottiene il valore di AccountType
090.            Dim CurValue As Int32 = CInt(dgvCustomers.CurrentRow.Cells(6).Value)
091.
092.              'Se è stato premuto +, aumenta il valore di 1
093.              'Se è stato premuto -, lo decrementa di 1
094.              If e.KeyCode = Keys.Oemplus Then
095.                  CurValue += 1
096.              ElseIf e.KeyCode = Keys.OemMinus Then
097.                  CurValue -= 1
098.              End If
099.
100.              'Fa in modo di non andare oltre i limit imposti
101.              If CurValue < 0 Then
102.                  CurValue = 0
103.              ElseIf CurValue > 2 Then
104.                  CurValue = 2
105.              End If
106.
107.           'Quindi imposta il nuovo valore di AccountType
108.           dgvCustomers.CurrentRow.Cells(6).Value = CurValue
109.           'E la corrispondente nuova immagine
110.           dgvCustomers.CurrentCell.Value = imgAccountTypes.Images(CurValue)
111.       End If
112.   End Sub
113.
114.   'L'evento DataError viene generato ogniqualvolta l'utente
115.   'non rispetti i vincoli imposti dal database. Ad esempio,
116.   'viene generato se un campo marcato come NOT NULL viene
117.   'lasciato vuoto, o se una data non è valida, o
118.   'se un numero è troppo grande o troppo piccolo,
119.   'oppure ancora se non viene soddisfatto il vincolo di
120.   'unicità, eccetera...
121.   'In questi casi, se il programmatore non gestisce l'evento,
122.   'appare una finestra di default che riporta tutto il testo
123.   'dell'eccezione e vi assicuro che non è una bella cosa
124.   Private Sub dgvCustomers_DataError(ByVal sender As System.Object, ByVal e As
          System.Windows.Forms.DataGridViewDataErrorEventArgs) Handles dgvCustomers.DataError
125.        Dim Result As DialogResult
126.
127.       'Riporta l'errore all'utente, e lascia scegliere se
128.       'modificare i dati incompatibili oppure annullare
129.       'le modifiche e cancellare la riga
130.       Result = MessageBox.Show("Si è verificato un errore di compatibilità dei dati immessi.
              Messaggio:" & _
131.            Environment.NewLine & e.Exception.Message & Environment.NewLine & _
132.            "E' possibile che dei dati mancanti compromettano il database. Premere Sì per
                   modificare opportunamente " & _
133.            "tali valori, o No per cancellare la riga.", Me.Text, MessageBoxButtons.YesNo,
                   MessageBoxIcon.Exclamation)
134.
135.       If Result = Windows.Forms.DialogResult.Yes Then
136.           'Annulla questo evento: non viene generata la
137.           'finestra di errore
138.
e.Cancel = True
139.              'Pone il cursore sulla casella corrente e obbliga
140.              'ad iniziare l'edit mode. Il valore booleano tra
141.              'parentesi indica di selezionare l'intero contenuto
142.              'della cella corrente
143.              dgvCustomers.BeginEdit(True)
144.       Else
145.           'Annulla l'eccezione e l'evento, quindi cancella
146.           'la riga corrente
147.           e.ThrowException = False
148.           e.Cancel = True
149.           'Le righe "nuove", ossia quelle in cui non è
150.           'stato salvato ancora nessun dato, non possono essere
151.           'eliminate (così dice il datagridview...)
152.           If Not dgvCustomers.CurrentRow.IsNewRow Then
153.               dgvCustomers.Rows.Remove(dgvCustomers.CurrentRow)
154.           End If
155.       End If
156.   End Sub
157.
158.   'Questa procedura aggiorna la ListView con alcuni dettagli
159.   'sullo stati dei pagamenti
160.   Private Sub RefreshPreview(ByVal CustomerID As Int32)
161.       lstPreview.Items.Clear()
162.
163.       'Cerca il cliente
164.       Dim Customer As AppDataSet.CustomersRow = _
165.           MainDatabase.Customers.FindByID(CustomerID)
166.
167.       'Se non esiste, esce
168.       If Customer Is Nothing Then
169.           Exit Sub
170.       End If
171.
172.       Dim TotalPaid, TotalUnpaid As Single
173.       Dim Delay As TimeSpan
174.
175.       'Notate che qui agiamo direttamente sul dataset,
176.       'perchè contiene campi tipizzati, e ci consente di
177.       'utilizzare meno operatori di cast
178.       For Each Order As AppDataSet.OrdersRow In MainDatabase.Orders
179.           'Conta solo gli ordini associati a un cliente
180.           If Order.CustomerID <> CustomerID Then
181.               Continue For
182.           End If
183.
184.              'Se l'ordine è stato pagato, aggiunge il
185.              'totale alla variabile TotalPaid, altrimenti a
186.              'TotalUnpaid.
187.              'Se l'ordine non è stato pagato, inoltre,
188.              'calcola il ritardo maggiore nel pagamento
189.              If Order.Settled Then
190.                   TotalPaid += Order.ItemPrice * Order.ItemCount
191.              Else
192.                   TotalUnpaid += Order.ItemPrice * Order.ItemCount
193.                   If Date.Now - Order.CreationDate > Delay Then
194.                       Delay = Date.Now - Order.CreationDate
195.                   End If
196.              End If
197.       Next
198.
199.       Dim Item1 As New ListViewItem
200.       Item1.Text = "Ammontare pagato"
201.       Item1.SubItems.Add(String.Format("{0:N2}€", TotalPaid))
202.
203.       Dim Item2 As New ListViewItem
204.       Item2.Text = "Oneri futuri"
205.       Item2.SubItems.Add(String.Format("{0:N2}€", TotalUnpaid))
206.
207.       Dim Item3 As New ListViewItem
208.       Item3.Text = "Ritardo pagamento"
209.       Item3.SubItems.Add(CInt(Delay.TotalDays) & " giorni")
210.
211.               Dim DaysLimit As Int32
 212.               'Un diverso tipo di account permette un maggior ritardo
 213.               'nei pagamenti...
 214.               Select Case Customer.AccountType
 215.                   Case 0
 216.                       DaysLimit = 60
 217.                   Case 1
 218.                       DaysLimit = 90
 219.                   Case 2
 220.                       DaysLimit = 120
 221.                   Case Else
 222.                       DaysLimit = 60
 223.               End Select
 224.
 225.               'Se il cliente ha superato il limite con almeno uno
 226.               'dei suoi ordini, la riga viene colorata in rosso
 227.               If Delay.TotalDays > DaysLimit Then
 228.                   Item3.ForeColor = Color.Red
 229.               End If
 230.
 231.             lstPreview.Items.Add(Item1)
 232.             lstPreview.Items.Add(Item2)
 233.             lstPreview.Items.Add(Item3)
 234.         End Sub
 235.
 236.         'Evento generato quando l'utente si posizione su una riga
 237.         Private Sub dgvCustomers_RowEnter(ByVal sender As System.Object, ByVal e As
                 System.Windows.Forms.DataGridViewCellEventArgs) Handles dgvCustomers.RowEnter
 238.              'Se si tratta di una riga valida...
 239.              If e.RowIndex < dgvCustomers.Rows.Count And e.RowIndex >= 0 Then
 240.                  Dim CurID As Int32 = CInt(dgvCustomers.Rows(e.RowIndex).Cells(0).Value)
 241.                  'Aggiorna il filtro di bsOrders, per visualizzare solo gli
 242.                  'ordini di quel dato cliente
 243.                  bsOrders.Filter = "CustomerID=" & CurID
 244.                  'E aggiorna la listview
 245.                  RefreshPreview(CurID)
 246.              End If
 247.         End Sub
 248.
 249.         'Quando una cella di dgvOrders viene modificata, aggiorna
 250.         'la listview...
 251.         Private Sub dgvOrders_CellEndEdit(ByVal sender As System.Object, ByVal e As
                 System.Windows.Forms.DataGridViewCellEventArgs) Handles dgvOrders.CellEndEdit
 252.              Try
 253.                  RefreshPreview(CInt(dgvCustomers.CurrentRow.Cells(0).Value))
 254.              Catch Ex As Exception
 255.
 256.         End Try
 257.     End Sub
 258. End Class

Ed ecco come potr ebbe pr esentar si:




Come avr ete cer tamente notato, fatta eccezione per l'unica pr ocedur a Refr eshPr eview , abbiamo agito solo sul
datagr idview e non sul dataset. Questo accade per chè l'applicazione cr eata è "str atificata": può esser e consider ata
come un par ticolar e caso di applicazione 3-tier . L'ar chitettur a thr ee-tier indica una par ticolar e str uttur azione del
softw ar e in cui ci sono tr e layer (str ati) pr incipali: data layer , buisness layer e gui layer . Il pr imo si dedica alla
gestione dei dati per sistenti (nel nostr o caso, il database), il secondo si occupa di for nir e delle logiche funzionali, ossia
metodi che gestiscono le infor mazioni in manier a da r ispecchiar e il funzionamento dell'applicazione e che per mettono
di inter facciar si più facilmente con il data layer (il nostr o buisness layer è il dataset, r appr esentazione oggettiva e
funzionale dei dati per sistenti) mentr e il ter zo ha il compito di mediar e l'inter azione con l'utente attr aver so
l'inter faccia gr afica. Sentir ete par lar e molto spesso di questo tipo di ar chitettur a nel campo dei gestionali.
C8. DataGridView - Parte II


Manipolazione dei dati
Nell'esempio pr ecedente, l'utente poteva modificar e ed eventualmente cancellar e dati esistenti, ma, ancor a una volta,
ho tr alasciato di implementar e l'aggiunta. In questo caso, per ò, aver lasciato la possibilità di agir e liber amente sui dati
aggiunti avr ebbe causato non pochi danni, poiché gli ID sono tutti nascosti e il contr ollo datagr idview no n implementa
nessun tipo di autoincr emento per i valor i numer ici: aggiungendo nuove r ighe, l'utente non avr ebbe potuto influir e
sulle celle ID, che sar ebber o r imaste vuote e avr ebber o causato sempr e lo stesso er r or e (avendolo noi gestito nel modo
che sapete, l'unica scelta possibile sar ebbe stata quella di cancellar e l'ultima r iga e per ciò non si sar ebbe potuto
aggiunger e nulla in ogni caso). In poche par ole, bisogna inter venir e a livello di codice.
Possiamo cor r egger e in modo elegante aggiungendo due Contex tMenu con un solo elemento "Aggiungi cliente" o
"Aggiungi or dine" ed associar e ciascuno dei due a uno dei datagr idview . Per aggiunger e un nuovo cliente basta agir e
dir ettamente sulla tabella customer , r ichiamando AddCustomer sRow e lasciando tutti i par ametr i vuoti (con la data di
default), poiché nessuno di essi è specificato come NOT NULL nella str uttur a della tabella. Per l'or dine, invece, non è
possibile seguir e la stessa str ada, poiché quasi tutti gli attr ibuti non possono esser e null. Per questo cr eer emo una
nuova finestr a di dialogo di nome Cr eateOr der Dialog con quest'aspetto:




e con questo semplice codice:

   01. Public Class CreateOrderDialog
   02.
   03.     Private CustomerID As Int32
   04.     Private _NewOrder As AppDataSet.OrdersRow
   05.
   06.     'Restituisce una nuova riga con gli attributi impostati
   07.     'nel dialog
   08.     Public ReadOnly Property NewOrder() As AppDataSet.OrdersRow
   09.          Get
   10.              Return _NewOrder
   11.          End Get
   12.     End Property
   13.
   14.     'Per creare un nuovo ordine ci serve l'ID del cliente ad
   15.     'esso associato, perciò dobbiamo costringere il chiamante
   16.     '(ossia noi stessi XD) a passarci questo dato in qualche
   17.     'modo. In questo caso, sovrascriviamo il metodo ShowDialog
   18.     'mediante shadowing:
   19.     Public Shadows Function ShowDialog(ByVal CustomerID As Int32) As DialogResult
   20.          Me.CustomerID = CustomerID
   21.          Return MyBase.ShowDialog()
   22.     End Function
   23.
   24.     Private Sub OK_Button_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
              Handles OK_Button.Click
   25.          If String.IsNullOrEmpty(txtItemName.Text) Then
   26.              MessageBox.Show("Specificare una descrizione valida del prodotto!", Me.Text,
                       MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
   27.              Exit Sub
   28.          End If
   29.
   30.          If nudItemPrice.Value = 0.0F Then
   31.              MessageBox.Show("Prezzo non valido!", Me.Text, MessageBoxButtons.OK,
                       MessageBoxIcon.Exclamation)
   32.              Exit Sub
   33.          End If
   34.
   35.
_NewOrder = My.Forms.Form1.MainDatabase.Orders.NewOrdersRow()
   36.                 With _NewOrder
   37.                     .CustomerID = Me.CustomerID
   38.                     .ItemName = txtItemName.Text
   39.                     .ItemPrice = nudItemPrice.Value
   40.                     .ItemCount = nudItemCount.Value
   41.                     .CreationDate = dtpCreationDate.Value
   42.                     .Settled = chbSettled.Checked
   43.                 End With
   44.
   45.            Me.DialogResult = System.Windows.Forms.DialogResult.OK
   46.            Me.Close()
   47.        End Sub
   48.
   49.        Private Sub Cancel_Button_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
                 Handles Cancel_Button.Click
   50.             Me.DialogResult = System.Windows.Forms.DialogResult.Cancel
   51.             Me.Close()
   52.        End Sub
   53.
   54. End Class

Tenendo conto del nuovo dialog appena scr itto, il codice del for m diventer ebbe:

   01. Imports MySql.Data.MySqlClient
   02. Public Class Form1
   03.
   04.     '...
   05.
   06.     Private Sub strAddCustomer_Click(ByVal sender As System.Object, ByVal e As
              System.EventArgs) Handles strAddCustomer.Click
   07.          MainDatabase.Customers.AddCustomersRow("", "", "", "", Date.Now, 0)
   08.     End Sub
   09.
   10.     Private Sub strAddOrder_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
              Handles strAddOrder.Click
   11.          If dgvCustomers.CurrentRow Is Nothing Then
   12.              Exit Sub
   13.          End If
   14.
   15.          Dim CID As Int32 = CInt(dgvCustomers.CurrentRow.Cells(0).Value)
   16.          Dim OrderDialog As New CreateOrderDialog()
   17.
   18.          If OrderDialog.ShowDialog(CID) = Windows.Forms.DialogResult.OK Then
   19.              MainDatabase.Orders.AddOrdersRow(OrderDialog.NewOrder)
   20.              RefreshPreview(CID)
   21.          End If
   22.     End Sub
   23.
   24. End Class

Manca ancor a un'ultima cosa per ter minar e il pr ogr amma, ossia il salvataggio. Possiamo, ad esempio, salvar e tutto alla
chiusur a del for m:

   01. Public Class Form1
   02.     '...
   03.
   04.     Private Sub Form1_FormClosing(ByVal sender As System.Object, ByVal e As
              System.Windows.Forms.FormClosingEventArgs) Handles MyBase.FormClosing
   05.          Dim Connection As New MySqlConnection("Server=localhost; Database=appdata; Uid=root;
                   Pwd=root;")
   06.          Dim Adapter As New MySqlDataAdapter()
   07.          Dim Builder As MySqlCommandBuilder
   08.
   09.          Try
   10.               Connection.Open()
   11.               Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Customers;", Connection)
   12.               Builder = New MySqlCommandBuilder(Adapter)
   13.               Adapter.Update(MainDatabase.Customers)
   14.
   15.               Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Orders;", Connection)
   16.
Builder = New MySqlCommandBuilder(Adapter)
   17.                   Adapter.Update(MainDatabase.Orders)
   18.
   19.            Catch Ex As Exception
   20.                If MessageBox.Show("Impossibile connettersi al database per il salvataggio.
                         Proseguire nella chiusura? Tutti i dati non salvati andranno persi.", Me.Text,
                         MessageBoxButtons.YesNo, MessageBoxIcon.Error) = Windows.Forms.DialogResult.No
                         Then
   21.                     e.Cancel = True
   22.                End If
   23.            Finally
   24.                Connection.Close()
   25.            End Try
   26.        End Sub
   27.
   28. End Class

P.S.: r icor datevi di r iassegnar e le immagini all'ultima cella dopo che le r ighe sono state r ior dinate (è possibile or dinar e
automaticamente i dati cliccando sull'intestazione di una colonna).




Stampa
Il passo successivo di un gestionale consiste, di nor ma, nel voler stampar e i dati che si gestiscono. Esistono par ecchi
componenti già pr onti per stampar e un datagr idview , nonché molti str umenti integr ati nell'IDE (r epor ts), acquistabili
e non, e anche un discr eto numer o di "scor ciatoie", che non fanno altr o che disegnar e il contr ollo su un qualche suppor to
e delegar ne la stampa ad un'altr a applicazione. Potete sceglier e liber amente di usar e uno dei metodi sopr acitati senza
spr ecar vi più di tanto nel codice. In ogni caso, la soluzione che pr opongo potr ebbe esser e utile per r ipassar e l'uso di
Gr aphics:

 001. Public Class Form1
 002.
 003.     '...
 004.
 005.     'Indici dei campi da stampare
 006.     Private PrintingFields() As Int32 = {1, 2, 3, 4, 5, 7}
 007.
 008.     Private Sub PrintDoc_PrintPage(ByVal sender As System.Object, ByVal e As
             System.Drawing.Printing.PrintPageEventArgs) Handles PrintDoc.PrintPage
 009.          'Indice della prima riga della prima pagina
 010.          Static RowIndex As Int32 = 0
 011.          'Indica se si tratta della prima pagina
 012.          Static FirstPage As Boolean = True
 013.
 014.          'Offset orizzontale
 015.          Dim CellOffset As Int32 = e.MarginBounds.X
 016.          'Offset verticale
 017.          Dim Y As Int32 = e.MarginBounds.Y
 018.          'Larghezza totale colonne
 019.          Dim TotalWidth As Int32 = 0
 020.
 021.          'Se è la prima pagina, stampa le intestazioni
 022.          If FirstPage Then
 023.              For Each Column As DataGridViewColumn In dgvCustomers.Columns
 024.                   'Stampa solo gli header dei campi contemplati
 025.                   If Array.IndexOf(PrintingFields, Column.Index) < 0 Then
 026.                        Continue For
 027.                   End If
 028.
 029.                   e.Graphics.DrawString(Column.HeaderText,
                           dgvCustomers.ColumnHeadersDefaultCellStyle.Font, New
                           SolidBrush(dgvCustomers.ColumnHeadersDefaultCellStyle.ForeColor),
                           CellOffset, Y)
 030.                   CellOffset += Column.Width
 031.                   TotalWidth += Column.Width
 032.              Next
 033.              Y += dgvCustomers.ColumnHeadersHeight
 034.
End If
035.
036.   'Stampa le righe
037.   For I As Int32 = RowIndex To dgvCustomers.RowCount - 1
038.       Dim Row As DataGridViewRow = dgvCustomers.Rows(I)
039.
040.       CellOffset = e.MarginBounds.X
041.       'Ottiene lo stile delle celle
042.       Dim Style As DataGridViewCellStyle
043.       'Distingue fra quelle pari e dispari
044.       If Row.Index Mod 2 = 0 Then
045.            Style = dgvCustomers.DefaultCellStyle
046.       Else
047.            Style = dgvCustomers.AlternatingRowsDefaultCellStyle
048.       End If
049.
050.       'Se nessun font particolare è specificato, prende
051.       'quello del controllo
052.       If Style.Font Is Nothing Then
053.           Style.Font = dgvCustomers.Font
054.       End If
055.       'Se nessun colore particolare è specificato (di
056.       'default valore argb=(0,0,0,0)) prende quello del
057.       'controllo
058.       If Style.ForeColor.A = 0 Then
059.           Style.ForeColor = dgvCustomers.ForeColor
060.       End If
061.
062.       'Disegna una striscia del colore di sfondo della riga
063.       e.Graphics.FillRectangle(New SolidBrush(Style.BackColor), CellOffset, Y,
              TotalWidth, dgvCustomers.RowTemplate.Height)
064.       'E la contorna con un tratto nero
065.       e.Graphics.DrawRectangle(Pens.Black, CellOffset, Y, TotalWidth,
              dgvCustomers.RowTemplate.Height)
066.
067.       For Each Cell As DataGridViewCell In Row.Cells
068.           'Stampa solo gli attributi contemplati
069.           If Array.IndexOf(PrintingFields, Cell.ColumnIndex) < 0 Then
070.               Continue For
071.           End If
072.
073.            'Se la cella contiene un'immagime, la stampa
074.            If Cell.ValueType Is GetType(Image) Then
075.                 Dim Img As Image = Cell.Value
076.                 e.Graphics.DrawImage(Img, CellOffset +
                        dgvCustomers.Columns(Cell.ColumnIndex).Width  2 - Cell.Value.Width 
                        2, Y + dgvCustomers.CurrentRow.Height  2 - Img.Height  2)
077.            Else
078.                 Dim Height As Int32
079.                 Dim StrVal As String
080.
081.                'Formatta la data in forma compatta
082.                If Cell.ValueType Is GetType(Date) Then
083.                     StrVal = CType(Cell.Value, Date).ToShortDateString()
084.                Else
085.                     StrVal = Cell.Value.ToString()
086.                End If
087.                'Calcola l'altezza del testo per centrarlo
088.                Height = Cell.MeasureTextHeight(e.Graphics, StrVal, Style.Font, 500,
                       TextFormatFlags.Default)
089.
090.                'Stampa il testo
091.                e.Graphics.DrawString(StrVal, Style.Font, New SolidBrush(Style.ForeColor),
                       CellOffset, Y + dgvCustomers.RowTemplate.Height  2 - Height  2)
092.            End If
093.
094.            'Si sposta alla prossima ascissa
095.            CellOffset += dgvCustomers.Columns(Cell.ColumnIndex).Width
096.
097.            'Per tutte le celle tranne l'ultima, disegna una linea
098.            'verticale di separazione dalla cella successiva
099.            If Array.IndexOf(PrintingFields, Cell.ColumnIndex) < PrintingFields.Length - 1
Then
 100.                                 e.Graphics.DrawLine(Pens.Black, CellOffset - 4, Y, CellOffset - 4, Y +
                                         dgvCustomers.RowTemplate.Height)
 101.                            End If
 102.                     Next
 103.
 104.                     'Aumenta l'ordinata
 105.                     Y += dgvCustomers.RowTemplate.Height
 106.              Next
 107.
 108.             If e.HasMorePages Then
 109.                  FirstPage = False
 110.             Else
 111.                  FirstPage = True
 112.                  RowIndex = 0
 113.             End If
 114.         End Sub
 115.
 116.         'strPrint è un sottoelemento del context menu
 117.         Private Sub strPrint_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
                 Handles strPrint.Click
 118.              Dim PDialog As New PrintDialog
 119.
 120.         PDialog.Document = PrintDoc
 121.         If PDialog.ShowDialog = Windows.Forms.DialogResult.OK Then
 122.             PrintDoc.PrinterSettings = PDialog.PrinterSettings
 123.             'Ridimensiona le colonne per far stare i testi in modo
 124.             'corretto. N.B.: ho impostato la larghezza minima della
 125.             'colonna Address a 190 pixel
 126.             dgvCustomers.AutoResizeColumns()
 127.             PrintDoc.Print()
 128.         End If
 129.     End Sub
 130. End Class

Risultato:




V alidazione dell'input
E' possibile convalidar e l'input dell'utente o indicar e che alcuni dati sono er r onei utilizzando l'evento CellValidating o
Row Validating:

   01. Private Sub dgvCustomers_CellValidating(ByVal sender As System.Object, ByVal e As
          System.Windows.Forms.DataGridViewCellValidatingEventArgs) Handles
          dgvCustomers.CellValidating
   02.      If e.ColumnIndex > 0 And e.ColumnIndex < 5 Then
   03.          With dgvCustomers.Item(e.ColumnIndex, e.RowIndex)
   04.              If String.IsNullOrEmpty(e.FormattedValue.ToString()) Then
   05.                   .ErrorText = "Il campo non dovrebbe essere lasciato vuoto"
   06.              Else
   07.                   .ErrorText = Nothing
   08.              End If
   09.          End With
   10.      End If
   11. End Sub
D1. Il controllo WebBrowser


WebBr ow ser è uno dei contr olli standar d for niti dal Fr amew or k .NET fin dalla ver sione 1.0, e le sue potenzialità sono
abbastanza elevate da per metter ci di "cr ear e" (o quanto meno, simular e) un nostr o per sonale w eb br ow ser , come
Mozilla Fir eFox , Oper a o Google Chr ome. Non a caso ho messo tr a vir golette il ver bo creare, poiché il contr ollo che
andr emo ad analizzar e tr a poco assolve un'unica funzione, che costituisce, per ò, il fulcr o di tutta la navigazione.
WebBr ow ser per mette di car icar e al pr opr io inter no una pagina w eb e di visualizzar la senza pr aticamente scr iver e
alcun codice. Nei pr ossimi par agr afi illustr er ò come scr iver e un semplicissimo pr ogr amma di navigazione basato su
questa classe.




La fisionomia di W ebBrow ser
Dopo aver cr eato un nuovo pr ogetto Window s For ms, tr ascinate sulla super ficie del designer un nuovo contr ollo
WebBr ow ser . Una volta posizionato dovr ebbe mostr ar si come un'ar ea totalmente bianca: per or a, infatti, non contiene
ancor a nessuna pagina. Pr ima di pr oceder e, ecco uno sguar do alla lista dei suoi membr i più impor tanti:

       CanGoBack : deter mina se sia possibile tor nar e indietr o nella cr onologia
       CanGoFor w ar d : deter mina se sia possibile andar e avanti nella cr onologia
       Document : un oggetto di tipo HtmlDocument contenente tutte le infor mazioni sulla pagina. Tr a le sue
       pr opr ietà, inoltr e, ci sono molti modi per ottener e una vasta gamma di tag, ma illustr er ò in dettaglio questi
       meccanismi nel pr ossimo capitolo
       DocumentStr eam : per mette di legger e la pagina w eb come da un file. Restituisce un oggetto System.IO.Str eam
       DocumentTex t : r estituisce o imposta il codice della pagina. Dopo aver car icato una pagina, contiene il suo
       codice HTML. Modificando questa pr opr ietà, anche la pagina visualizzata ver r à r ielabor ata (e r icar icata) di
       conseguenza
       DocumentTitle : il titolo del documento
       DocumentType : il tipo del documento
       GoBack : tor na indietr o alla pagina pr ecedente
       GoFor w ar d : pr ocede alla pagina successiva
       GoHome : r itor na all'Home Page. Per ottener e l'indir izzo di quest'ultima, r icer ca nel r egistr o di sistema le
       pr efer enze che l'utente ha impostato per il br ow ser Inter net Ex plor er
       GoSear ch : si r eca alla pagina di r icer ca pr edefinita. Esegue lo stesso pr ocedimento di GoHome
       IsBusy : indica se il contr ollo sta car icando un nuovo documento
       IsOffline : indica se il contr ollo è in modalità offline (sta pr ocessando pagine w eb su disco fisso)
       IsWebBr ow ser Contex tMenuEnabled : deter mina se sia attivo il menù contestuale pr edefinito per il Web Br ow ser
       Naviagate(S) : apr e la pagina r efer enziata dall'indir izzo ur l S
       Pr int : stampa il documento aper to con i settaggi impostati della stampante cor r ente
       ReadyState : r estituisce lo stato del contr ollo. L'enumer ator e può assumer e quattr o valor i: Complete (pagina
       completa), Inter active (le par ti della pagina car icate sono sufficienti a gar antir e un minimo di inter azione con
       l'utente, ad esempio con dei click sui link pr esenti), Loaded (il documento è car icato e inizializzato, ma non tutti
       i dati sono ancor a stati r icevuti), Loading (il documento è in car icamento) e Uninitialized (nessun documento è
       stato aper to)
       Show PageSetupDialog : visualizza le impostazioni pagina con una finestr a di dialogo Inter net Ex plor er
       Show Pr intDialog : visualizza la finestr a di stampa di Inter net Ex plor er
Show Pr intPr eview Dialog : visualizza l'antepr ima di stampa in una finestr a Inter net Ex plor er
       Show Pr oper tiesDialog : visualizza la finestr a delle pr opr ietà pagina come Inter net Ex plor er
       Show SaveAsDialog : visualizza la finestr a di dialogo di salvataggio di Inter net Ex plor er
       Ur l : r estituisce un oggetto Ur i r appr esentante l'indir izzo della pagina car icata
       Ver sion : la ver sione di Inter net Ex plor er installata

Alcune delle funzionalità esposte da questi membr i si r eggono pesantemente su Inter net Ex plor er , come ad esempio la
visualizzazione dell'antepr ima o la r icer ca della home page (che potete cambiar e solo dal menù opzioni di IE).
Nonostante tali pesanti impedimenti, è possibile usar e il contr ollo con semplicità.
Nel nostr o pr ogetto possiamo quindi aggiunger e qualche altr o contr ollo:

       btnBack per andar e indietr o;
       btnFor w ar d per andar e avanti;
       btnRefr esh per aggior nar e la pagina;
       tx tUr l per contener e l'indir izzo a cui r ecar si;




Come vedete ho inser ito tutti i contr olli sopr a menzionati in un ToolStr ip, e tutti i pulsanti sono di default disattivati
(Enabled = False), poiché all'inizio non è car icata nessuna pagina e di conseguenza non si può effettuar e alcuna
oper azione. Con questo semplice codice potr emo iniziar e a navigar e un po':

   01. Public Class Form1
   02.
   03.     Private Sub txtUrl_KeyDown(ByVal sender As System.Object, ByVal e As
              System.Windows.Forms.KeyEventArgs) Handles txtUrl.KeyDown
   04.          'Quando si preme invio durante la digitazione, naviga
   05.          'alla pagina indicata
   06.          If e.KeyCode = Keys.Enter Then
   07.              wbBrowser.Navigate(txtUrl.Text)
   08.              'Poiché si inizia a navigare, è lecito fermare
   09.              'il caricamento, quindi attiva btnCancel
   10.              btnCancel.Enabled = True
   11.
End If
   12.       End Sub
   13.
   14.       Private Sub btnCancel_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
                Handles btnCancel.Click
   15.            'Ferma l'attività del WebBrowser
   16.            wbBrowser.Stop()
   17.            btnCancel.Enabled = False
   18.            btnRefresh.Enabled = True
   19.       End Sub
   20.
   21.       Private Sub wbBrowser_Navigating(ByVal sender As System.Object, ByVal e As
                System.Windows.Forms.WebBrowserNavigatingEventArgs) Handles wbBrowser.Navigating
   22.            'L'evento Navigating si genera prima della navigazione
   23.            btnCancel.Enabled = True
   24.       End Sub
   25.
   26.       Private Sub wbBrowser_DocumentCompleted(ByVal sender As System.Object, ByVal e As
                System.Windows.Forms.WebBrowserDocumentCompletedEventArgs) Handles
                wbBrowser.DocumentCompleted
   27.            'L'evento DocumentCompleted si verifica quando una pagina
   28.            'è stata completamente caricata. Se anche una sola
   29.            'delle parti della pagina non è completa, l'evento
   30.            'non viene generato. Per evitare brutte soprese, potete
   31.            'utilizzare l'evento Navigated, che si verifica dopo la
   32.            'navigazione (indipendentemente dal successo o meno
   33.            'dell'operazione)
   34.            btnCancel.Enabled = False
   35.            btnBack.Enabled = wbBrowser.CanGoBack
   36.            btnForward.Enabled = wbBrowser.CanGoForward
   37.            btnRefresh.Enabled = True
   38.       End Sub
   39.
   40.       Private Sub btnRefresh_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
                Handles btnRefresh.Click
   41.            wbBrowser.Refresh()
   42.       End Sub
   43.
   44.       Private Sub btnBack_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
                Handles btnBack.Click
   45.            wbBrowser.GoBack()
   46.       End Sub
   47.
   48.       Private Sub btnForward_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
                Handles btnForward.Click
   49.            wbBrowser.GoForward()
   50.       End Sub
   51.
   52. End Class

Come alter nativa a DocumentCompleted, si può utilizzar e Navigated:

    1. Private Sub wbBrowser_Navigated(ByVal sender As System.Object, ByVal e As
          System.Windows.Forms.WebBrowserNavigatedEventArgs) Handles wbBrowser.Navigated
    2.      btnCancel.Enabled = False
    3.      btnBack.Enabled = wbBrowser.CanGoBack
    4.      btnForward.Enabled = wbBrowser.CanGoForward
    5.      btnRefresh.Enabled = True
    6. End Sub

Possiamo or a aggiunger e una bar r a di stato in basso per comunicar e lo stato della navigazione:

   01. Public Class Form1
   02.
   03.     Private Sub txtUrl_KeyDown(ByVal sender As System.Object, ByVal e As
              System.Windows.Forms.KeyEventArgs) Handles txtUrl.KeyDown
   04.          If e.KeyCode = Keys.Enter Then
   05.              wbBrowser.Navigate(txtUrl.Text)
   06.              btnCancel.Enabled = True
   07.          End If
   08.     End Sub
   09.
10.     Private Sub btnCancel_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
           Handles btnCancel.Click
11.          wbBrowser.Stop()
12.          btnCancel.Enabled = False
13.          btnRefresh.Enabled = True
14.     End Sub
15.
16.     Private Sub wbBrowser_Navigating(ByVal sender As System.Object, ByVal e As
           System.Windows.Forms.WebBrowserNavigatingEventArgs) Handles wbBrowser.Navigating
17.          btnCancel.Enabled = True
18.          'La proprietà StatusText contiene in forma leggibile
19.          'un resoconto dell'operazione che il controllo sta svolgendo
20.          lblStatus.Text = wbBrowser.StatusText
21.     End Sub
22.
23.     Private Sub wbBrowser_Navigated(ByVal sender As System.Object, ByVal e As
           System.Windows.Forms.WebBrowserNavigatedEventArgs) Handles wbBrowser.Navigated
24.          btnCancel.Enabled = False
25.          btnBack.Enabled = wbBrowser.CanGoBack
26.          btnForward.Enabled = wbBrowser.CanGoForward
27.          btnRefresh.Enabled = True
28.          lblStatus.Text = "Pagina caricata"
29.     End Sub
30.
31.     Private Sub btnRefresh_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
           Handles btnRefresh.Click
32.          wbBrowser.Refresh()
33.     End Sub
34.
35.     Private Sub btnBack_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
           Handles btnBack.Click
36.          wbBrowser.GoBack()
37.     End Sub
38.
39.     Private Sub btnForward_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
           Handles btnForward.Click
40.          wbBrowser.GoForward()
41.     End Sub
42.
43.     Private Sub wbBrowser_ProgressChanged(ByVal sender As System.Object, ByVal e As
           System.Windows.Forms.WebBrowserProgressChangedEventArgs) Handles
           wbBrowser.ProgressChanged
44.          prgProgress.Value = e.CurrentProgress / e.MaximumProgress * 100
45.          lblStatus.Text = wbBrowser.StatusText
46.     End Sub
47.
48. End Class
Dato che questo non vuole esser e un tutor ial su come cr ear e un br ow ser , ma solo un abstr act per mostr ar e le
funzionalità del contr ollo, non mi dilungher ò oltr e nella modifica e nella r affinazione dell'applicazione pr oposta in
esempio, anche per chè sono sicur o che qualche lettor e lo star à già facendo e non vor r ei toglier gli il diver timento XD
D2. Parsing di codice HTML


È possibile scar icar e pagine w eb in molti modi diver si, di cui WebBr ow ser è solo il pr imo che ho intr odotto. Nel
capitolo pr ecedente non ci siamo posti alcun pr oblema sull'analsi del codice di una pagina, poiché l'impor tante er a
r iuscir e a visualizzar la ed a navigar e da essa ad alr e pagine pr esenti in r ete. Tuttavia, pr esto o tar di incor r er ete nel
bisogno di ottener e infor mazioni sui tag html pr esenti in un data pagina, ad esempio per effettuar e un login
automatico senza esser e per sonalmente al computer , o per aggiunger e alcune funzionalità al br ow ser che state
scr ivendo di nascosto.
Per nostr a for tuna esistono un paio di classi, HtmlDocument e HtmlElement, che eseguono autonomamente il par sing
del sor gente html e ci per mettono di agir e su di esso mediante oggetti di alto livello.




Uno sguardo alle c lassi
HtmlDocument è la classe di par tenza, che ci per mette di iniziar e ad ispezionar e il codice. Essa non espone costr uttor i,
né metodi statici, e quindi non esiste alcun modo di inizializzar la o di applicar la ad un file html. L'unico modo in cui
possiamo ottener ne un'istanza è attr aver so la pr opr ietà Document del contr ollo WebBr ow ser . HtmlDocument espone
alcuni membr i inter essanti:

       ActiveElement : r estituisce un oggetto HtmlElement che r appr esenta l'elemento che possiede il focus al momento.
       Può indicar e, ad esempio, un tag tex tar ea se l'utente sta digitando del testo, od un div se è stato selezionata
       una par te di par agr afo;
       All : r estituisce una collezione di tutti i tag pr esenti nel documento, sempr e sottofor ma di HtmlElement;
       Body : r estituisce l'elemento body della pagina;
       Cr eateElement(tagName) : cr ea un nuovo HtmlElement con tagName dato. Questo è l'unico modo in cui possiamo
       cr ear e nuovi oggetti da aggiunger e alla pagina (tr anne ovviamente r icopiar e il codice, modificar lo, e poi
       impostar e di nuovo la pr opr ietà DocumentTex t);
       For ms : r estituisce una collezione di tutti i tag form pr esenti nel documento;
       GetElementById(id As Str ing) : r estituisce un r ifer imento all'elemento con specifico id;
       GetElementFr omPoint(p As Point) : r estituisce un r ifer imento all'elemento che contiene il punto p; le coor dinate
       del punto sono r elative all'estr emo super ior e sinistr o della pagina;
       GetElementsByTagName(tagName As Str ing) : r estituisce una collezione di tutti i tag con dato tagName.
       GetElementsByTagName("div"), ad esempio, r estituisce l'insieme di tutti i div della pagina;
       Images : r estituisce una collezione di tutti i tag image;
       InvokeScr ipt(scr iptName As Str ing, ar gs() As Object) : esegue il metodo di nome scr iptName passandogli gli
       ar gomenti specificati in ar gs. Il metodo deve esser e definito all'inter no di un tag s cript nella pagina (non è
       impor tante il linguaggio, ma per or a ho ver ificato che funzioni solo con javascr ipt e actionscr ipt);
       Links : r estituisce una collezione di tutti i tag a;
       Title : indica il titolo della pagina;
       Ur l : l'indir izzo della pagina car icata;
       Window : r estituisce un oggetto HtmlWindow associato alla finestr a che visualizza la pagina. Questo oggetto
       espone alcuni membr i molto inter essanti, tr a cui:
               Aler t(S) : visualizza il messaggio S in una finestr a di dialogo;
               Confir m(S) : visualizza il messaggio S in una finestr a di dialogo e per mette di sceglier e tr a OK e Annulla;
               r estituisce Tr ue se è stato pr emuto OK, altr imenti False;
Pr ompt(S, D) : visualizza il messaggio S in una finestr a di dialogo e chiede di inser ir e un valor e in una
               casella di testo (il valor e pr edefinito è D). Restituisce il valor e che l'utente ha immesso.

Gli oggetti HtmlElement contengono più o meno gli stessi membr i, con l'aggiunta di GetAttr ibute e SetAttr ibute per
modificar e gli attr ibuti di un tag.
Nei pr ossimi par agr afi far ò alcuni esempi di come utilizzar e tali classi.




Login automatic o
Ecco un modo con cui potr este automatizzar e il login in una pagina salvando le infor mazioni e compilando i campi con
un solo pulsante.
Ipotizziamo di aver e un dizionar io LoginInfo in cui sono contenute delle coppie indir izzo-dizionar io. I valor i sono a lor o
volta altr i dizionar i che contengono le infor mazioni per il login. Potr emmo utilizzar e il codice seguente per
automatizzar e il tutto:

   01. Class Form1
   02.
   03.     'Qui c'è il codice del capitolo precedente
   04.
   05.     'Ecco il dizionario che contiene tutto
   06.     Dim LoginInfo As New Dictionary(Of String, Dictionary(Of String, String))
   07.
   08.     Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles
              MyBase.Load
   09.          'Per semplicità, in questo esempio carichiamo dei
   10.          'dati di prova al caricamento del form
   11.
   12.          Dim TInfo As New Dictionary(Of String, String)
   13.          With TInfo
   14.              'ID del form di login
   15.              .Add("form-id", "totemlogin")
   16.              'ID della textbox per l'username
   17.              .Add("username-field", "lname")
   18.              'ID della textbox per la password
   19.              .Add("password-field", "lpassw")
   20.              'Username e password
   21.              .Add("username", "prova")
   22.              .Add("password", "prova")
   23.          End With
   24.
   25.          'Associa alla pagina il suo login
   26.          LoginInfo.Add("http://totem.altervista.org/guida/versione3/login.php", TInfo)
   27.     End Sub
   28.
   29.
   30.     Private Sub btnAction_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
              Handles btnAction.Click
   31.          'Quando viene premuto il pulsante, ricava il dizionario
   32.          'dei dati dall'url della pagina
   33.          Dim Info As Dictionary(Of String, String) = LoginInfo(wbBrowser.Url.ToString())
   34.          'Quindi compila i campi e invia la richiesta di login
   35.          With wbBrowser.Document
   36.              .GetElementById(Info("username-field")).SetAttribute("value", Info("username"))
   37.              .GetElementById(Info("password-field")).SetAttribute("value", Info("password"))
   38.              'InvokeMember invoca un metodo usabile da un
   39.              'certo elemento. I metodi sono gli stessi che si
   40.              'usano in javascript
   41.              .GetElementById(Info("form-id")).InvokeMember("submit")
   42.          End With
   43.     End Sub
   44.
   45. End Class

Nonostante possa sempr ar e inutile, questo appr occio potr ebbe diventar e molto più intr igante, ad esempio, se l'utente
immettesse semplicemente una tesser a o una chiavetta in un dispositivo collegato al computer e, usando il vostr o
br ow ser , potesse inter facciar si con tale dispositivo per automatizzar e e per sonalizzar e tutti i login a seconda
dell'utente. Oppur e potr este sfr uttar e il r iconoscimento vocale offer to dalle libr er ie del fr amew or k 3.5 per confer mar e
l'accesso mediante una par ola detta a voce.




Trasformazioni
Nel par agr afo pr ecedente ho mostr ato come modificar e degli elementi. In questo mostr er ò come aggiunger e nuovi
elementi alla pagina dinamicamente e come gestir ne gli eventi.
Sempr e tenendo come guida il codice pr oposto nel par agr afo pr ecedente, cambiamo la funzione del pulsante btnAction
con la seguente: dopo il click sul pulsante, cliccando su qualsiasi immagine nella pagina, questa viene tr asfor mata in un
link all'immagine. Ecco il codice:

   01. Class Form1
   02.
   03.     '...
   04.
   05.     Private Sub btnAction_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
              Handles btnAction.Click
   06.          With wbBrowser.Document
   07.               'Scorre tutte le immagini nella pagina e ad ognuna
   08.               'aggiunge un nuovo gestore d'evento per l'evento OnClick.
   09.               'Il metodo AttachEventHandler può essere usato
   10.               'da qualsiasi HtmlElement, ed accetta come primo
   11.               'parametro il nome dell'evento da gestire (vedere la
   12.               'documentazione ufficiale W3C) e come secondo un
   13.               'delegate che punta al sottoscrittore.
   14.               For Each img As HtmlElement In .Images
   15.                    .AttachEventHandler("onclick", AddressOf ImageToLink)
   16.               Next
   17.          End With
   18.     End Sub
   19.
   20.     'Questo è il nuovo gestore d'evento. Nonostante i
   21.     'parametri, sender è sempre Nothing
   22.     Private Sub ImageToLink(ByVal sender As Object, ByVal e As EventArgs)
   23.          'Ottiene un riferimento all'immagine con il metodo
   24.          'GetElementFromPoint, sfruttando il fatto che questo
   25.          'codice viene eseguito subito dopo un click.
   26.          'MousePosition indica la posizione del mouse sullo schermo,
   27.          'Me.Location determina la posizione del form sullo schermo
   28.          'e wbBrowser.Location la posizione del browser sul form.
   29.          'La differenza tra questi punti è la posizione
   30.          'del mouse rispetto al browser. Anche se un po' grezzo,
   31.          'questo metodo dovrebbe funzionare abbastanza
   32.          Dim Img As HtmlElement = wbBrowser.Document.GetElementFromPoint(MousePosition -
                   Me.Location - wbBrowser.Location)
   33.          'Crea un nuovo link mediante il metodo CreateElement
   34.          'di HtmlDocument
   35.          Dim Link As HtmlElement = wbBrowser.Document.CreateElement("a")
   36.
   37.          'Imposta l'attributo href dell'immagine
   38.          Link.SetAttribute("href", Img.GetAttribute("src"))
   39.          'Imposta il testo del link
   40.          If Not String.IsNullOrEmpty(Img.GetAttribute("longdesc")) Then
   41.               Link.InnerText = Img.GetAttribute("longdesc")
   42.          ElseIf Not String.IsNullOrEmpty(Img.GetAttribute("alt")) Then
   43.               Link.InnerText = Img.GetAttribute("alt")
   44.          Else
   45.               Link.InnerText = "Immagine"
   46.          End If
   47.
   48.          'Aggiunge il link prima dell'immagine
   49.          Img.InsertAdjacentElement(HtmlElementInsertionOrientation.BeforeBegin, Link)
   50.          'Dato che non è possibile eliminare elementi,
   51.          'impone all'immagine larghezza 0
   52.          Img.SetAttribute("width", "0")
   53.
End Sub
54.
55. End Class
D3. Scaricare file dalla rete


Oltr e al WebBr ow ser , ci sono altr i tr e modi di scar icar e file da inter net. In questo par agr afo li analizzer ò uno per uno.




Dow nload sinc rono gestito
Il pr imo e più semplice dei suddetti modi consiste nell'utilizzar e una classe messa a disposizione dal Fr amew or k, ossia
WebClient (del namespace System.Net). Una volta istanziato un oggetto di questo tipo, è possibile r ichiamar e da esso
molti metodi diver si per scar icar e pr aticamente qualsiasi cosa. Qui espongo i metodi sincr oni:

         Dow nloadData(ur i) : scar ica il file con dato ur i (Unifor m Resour ce Identifier , una for ma più gener ale dell'ur l) e
         r estituisce tutti i dati scar icati sottofor ma di un ar r ay di bytes. Par ticolar mente indicato per scar icar e piccoli
         file binar i ad uso tempor aneo;
         Dow nloadFile(ur l, path) : scar ica il file indicato dall'indir izzo ur l e lo salva nel per cor so path su disco fisso;
         Dow nloadStr ing(ur i) : molto simile a Dow nloadData, ma anziché r estituir e un ar r ay di bytes, r estituisce una
         str inga.

Ci sono, poi, altr i membr i che è inter essante conoscer e:

         Cr edentials : indica le cr edenziali usate per acceder e alla data r isor sa. È utile impostar e questa pr opr ietà
         quando si accede a ser ver che r ichiedono un'autenticazione tr amite nome utente e passw or d, come ad esempio
         si usa far e quando si utilizza il pr otocollo ftp per il tr asfer imento di file. Ad esempio:

             1. Dim W As New Net.WebClient
             2. W.Credentials = New Net.NetworkCredential("username", "password")

         Header s : espone una collezione degli header posti all'inizio della r ichiesta per il file. Quando un metodo di
         dow nload viene invocato, la classe WebClient si pr eoccupa di inviar e una r ichiesta oppor tuna al ser ver . Ad essa
         può aggiunger e alcune metainfor mazioni note come header s, definite dallo standar d del pr otocollo HTTP (di cui
         potete tr ovar e una descr izione appr ofondita qui). Nei pr ossimi esempi user ò un solo tipo di header , Range, che
         per mette di ottener e solo una data par te del file;
         Pr ox y : imposta il pr o xy che la classe attr aver sa per inoltr ar e la r ichiesta;
         Quer yStr ing : indica un insieme di chiavi e valor i che costituiscono la quer y applicata alla pagina r ichiesta. Una
         quer y str ing può esser e accodata alla fine dell'ur l intr oducendola con un "?", definendo una coppia come
         nome=valor e e separ ando tutte le copie da un car atter e "&". Ser ve per ottener e r isultati diver si da una stessa
         pagina, specificando cosa si sta cer cando.

Alcuni semplici esempi:

   01.    Dim W As New Net.WebClient
   02.    'Scarica l'home page del sito e la salva in C:
   03.    W.DownloadFile("http://totem.altervista.org/index.php", "C:index.php")
   04.
   05.    Dim S As String
   06.    'Scarica il contenuto del file Capitoli.txt e lo salva
   07.    'nella stringa S
   08.    S = W.DownloadString("http://totem.altervista.org/guida/versione3/Capitoli.txt")
   09.
   10.    'Aggiunge una coppia nome-valore alla query
   11.    W.QueryString.Add("name", "twaveeditor")
   12.    'La prossima richiesta sarà quindi equivalente a:
   13.    ' http://totem.altervista.org/download/details.php?name=twaveeditor
   14.
'Ossia scaricherà la pagina di download di TWave Editor.
   15. 'Il contenuto del file verrà salvato in B
   16. Dim B() As Byte = W.DownloadData("http://totem.altervista.org/download/details.php")

La pecca di questi metodi è che sono sincr oni, ossia bloccano il funzionamento dell'applicazione fino a quando il dow nload
non è ter minato. Questo compor tamento può r ivelar si utile in cer ti casi e r ender e più maneggevole il codice per
scar icar e file di piccole dimensioni, ma è tutt'altr o che accettabile per gr andi quantità di dati.




Dow nload asinc rono gestito
Per file molto gr andi, invece, ci vengono in aiuto le ver sioni asincr one dei metodi sopr a esposti: sono r iconoscibili dal
suffisso "Async" dopo il nome del metodo. Questi eseguono il dow nload in un thr ead separ ato, per ciò non inter fer iscono
con le nor mali oper azioni del pr ogr amma. In compenso, sono un po' più difficili da gestir e, ma nulla di par ticolar mente
complicato.
I metodi asincr oni si r ichiamano usando esattamente gli stessi par ametr i delle ver sioni sincr one, ma per saper e come
stanno andando le cose, dobbiamo far e uso di due eventi della classe WebClient: Dow nloadPr ogr essChanged, che notifica
il pr ogr esso del dow nload, e Dow nloadFileCompleted (o Dow nloadDataCompleted o Dow nloadStr ingCompleted, a seconda
dei casi). Ecco un semplice esempio:

   01. Class Form1
   02.     'WithEvents permette di gestire gli eventi di W
   03.     Private WithEvents W As New Net.WebClient()
   04.
   05.     Private Sub btnDownload_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
              Handles btnDownload.Click
   06.          'Inizia il download asincrono
   07.          W.DownloadFileAsync(New Uri(txtUrl.Text), txtFile.Text)
   08.          btnCancel.Enabled = True
   09.          btnDownload.Enabled = False
   10.     End Sub
   11.
   12.     Private Sub W_DownloadProgressChanged(ByVal sender As Object, ByVal e As
              Net.DownloadProgressChangedEventArgs) Handles W.DownloadProgressChanged
   13.          'Il parametro e contiene alcune informazioni
   14.          'sul progresso del download
   15.          lblStatus.Text = _
   16.               String.Format("Bytes ricevuti: {0} B{3}Dimensione file: {1} B{3}Progresso:
                        {2:N0}%", _
   17.               e.BytesReceived, e.TotalBytesToReceive, _
   18.               e.ProgressPercentage, Environment.NewLine)
   19.     End Sub
   20.
   21.     Private Sub W_DownloadFileCompleted(ByVal sender As Object, ByVal e As
              System.ComponentModel.AsyncCompletedEventArgs) Handles W.DownloadFileCompleted
   22.          'e.Cancelled vale True se il download è stato annullato.
   23.          'e.Error è di tipo Exception e contiene l'eccezione
   24.          ' generata nel caso si sia verificato un errore.
   25.          If e.Cancelled Then
   26.               MessageBox.Show("Il download è stato cancellato!", Me.Text, MessageBoxButtons.OK,
                        MessageBoxIcon.Exclamation)
   27.          ElseIf e.Error IsNot Nothing Then
   28.               MessageBox.Show("Si è verificato un errore: " & e.Error.Message, Me.Text,
                        MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
   29.          Else
   30.               MessageBox.Show("Download completato con successo!", Me.Text,
                        MessageBoxButtons.OK, MessageBoxIcon.Information)
   31.          End If
   32.          btnDownload.Enabled = True
   33.          btnCancel.Enabled = False
   34.     End Sub
   35.
   36.     Private Sub btnCancel_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
              Handles btnCancel.Click
   37.          'Il metodo CancelAsync cancella il download asincrono
   38.          W.CancelAsync()
   39.
btnDownload.Enabled = True
   40.         btnCancel.Enabled = False
   41.     End Sub
   42.
   43. End Class




Dow nload sinc rono/asinc rono non gestito
Come ho illustr ato nei par agr afi pr ecedenti, WebClient si occupa di eseguir e molte istr uzioni r iguar do al dow nload:
connetter si al ser ver indicato, cr ear e una r ichiesta valida secondo il pr otocollo usato (HTTP o FTP o altr i), inoltr ar e la
r ichiesta, aspettar e una r isposta, legger e dallo str eam di r ete i dati for niti dal ser ver e copiar li nel file indicato,
quindi chiuder e la connessione. Insomma, non lascia nulla al contr ollo del pr ogr ammator e. Con il pr ossimo metodo che
andr ò ad intr odur r e, potr emmo manipolar e alcuni di questi passaggi a nostr o piacimento.
Le classi che ci inter essano or a sono WebRequest e WebResponse, del namespace System.Net. Esse sono classi astr atte,
poiché ogni pr otocollo implementa le pr opr ie r ichieste e r isposte secondo deter minati standar d. Nel nostr o esempio,
user emo HttpWebRequest per cr ear e ed inviar e una r ichiesta http ad un ser ver e HttpWebResponse per inter pr etar ne
la r isposta. Sappiate, per ò, che esistono anche le r ispettive ver sioni per il pr otocollo FTP, ossia FtpWebRequest e
FtpWebResponse. Ecco una pr ima semplice ver sione del codice:

   01. Public Sub DownloadFile(ByVal Address As String, ByVal Path As String)
   02.     'Crea una richiesta http per l'indirizzo Address.
   03.     'Address può anche contenere una query string
   04.     Dim Request As Net.HttpWebRequest = Net.HttpWebRequest.Create(Address)
   05.     'Invia la richieste a ottiene la risposta
   06.     Dim Response As Net.HttpWebResponse = Request.GetResponse()
   07.     'Ottiene da Response uno stream di rete dal quale si
   08.     'potrà leggere il file richiesto.
   09.     Dim Reader As IO.Stream = Response.GetResponseStream()
   10.     'Crea un nuovo file in locale
   11.     Dim Writer As New IO.FileStream(Path, IO.FileMode.Create)
   12.     'Un buffer di byte che contiene i blocchi letti
   13.     'dallo stream. La lettura a blocchi è più
   14.     'conveniente che trasferire in massa tutto il contenuto,
   15.     'poiché altrimenti si dovrebbe usare un buffer
   16.     'gigantesco (almeno le dimensioni del file)
   17.     Dim Buffer(8127) As Byte
   18.     Dim BytesRead As Int32
   19.
   20.     'La funzione Read, vi ricordo, restituisce come risultato
   21.     'il numero di bytes effettivamente letti dallo stream
   22.     BytesRead = Reader.Read(Buffer, 0, Buffer.Length)
   23.     Do While BytesRead > 0
   24.          Writer.Write(Buffer, 0, BytesRead)
   25.          BytesRead = Reader.Read(Buffer, 0, Buffer.Length)
   26.     Loop
   27.
   28.     Reader.Close()
   29.     Writer.Close()
   30.     Response = Nothing
   31.     Request = Nothing
   32. End Sub

Pr ima di pr oceder e, vor r ei far e alcuni chiar imenti sullo str eam di r ete. Esso r appr esenta un flusso di dati che
pr oviene dal ser ver a cui si è inviata la r ichiesta, ed è un flusso a senso unico, per ciò non suppor ta oper azioni di
r icer ca (invocando il metodo Seek o modificando la pr opr ietà Position otter r ete degli er r or i). Non è neppur e possibile
saper ne la dimensione complessiva, poiché anche la pr opr ietà Length gener a eccezioni. E, infine, non è possibile
scr iver vi   sopr a.   Esiste   un   modo   per   saper e   le   dimensioni   dei   dati,   ossia   r ichiamar e   la   pr opr ietà
Reponse.ContentLength, che, tuttavia, potr ebbe contener e valor i pr ivi di senso (ad esempio -1). Questo succede per chè
essa si limita ad espor r e il valor e di un header posto nella r isposta http: tuttavia, il ser ver non è obbligato ad inser ir e
questo header , e se non lo fa, non c'è modo di legger lo.
Osser viamo or a che tutte le oper azioni svolte sono sincr one, ma, come il titolo sugger isce, è possibile r ender e tutto il
metodo asincr ono, facendo uso dei thr ead. Infatti, è sufficiente eseguir e la pr ocedur a in un thr ead differ ente: per
ulter ior i infor mazioni sul multithr eading, veder e capitolo r elativo.
In ultimo, è possibile ottener e solo una par te del file aggiungendo l'header Range alla r ichiesta, come anticipato nei
par agr afi pr ecedenti. Dato che la pr opr ietà Header s di WebClient vieta l'uso di questo header (non è ben chiar a la
r agione), l'unico modo per usar lo consiste nell'impiegar e quest'ultimo metodo di dow nload. Basta r ichiamar e AddRange
pr ima dell'invio della r ichiesta:

   01.   '...
   02.   'Indica al server che vogliamo iniziare la lettura
   03.   'dall'offset n
   04.   Request.AddRange(n)
   05.
   06.   'oppure
   07.
   08.   'Indica al server che vogliamo iniziare la lettura dalla
   09.   'posizione n, ma solo fino alla posizione q
   10.   Request.AddRange(n, q)

Non ser ve eseguir e altr e oper azioni par ticolar i per la lettur a. Lo str eam ottenuto consentir à di legger e esattamente
ciò che si è r ichiesto come se fosse un unico flusso di dati.
D4. I Socket - Parte I


I socket sono uno str umento che per mette di inviar e e r icever e dati tr a due applicazioni che cor r ono su macchine
collegate da una r ete, la quale, nel caso più fr equente, coincide con Inter net. Le classi che espongono i metodi necessar i
sono contenute nel namespace System.Net.Sockets, di cui la classe Socket costituisce il membr o più eminente. In questo
capitolo e nel successivo, tuttavia, non user emo dir ettamente tale classe, poiché è poco pr atica da gestir e e fa
massiccio uso di tecniche di pr ogr ammazione un po' complesse, quali il multithr eading, che non ho ancor a spiegato.
Intr odur r ò, invece, al suo posto, due classi più semplici che fanno da w r apper ad alcune funzioni basilar i del socket:
TcpListener e TcpClient.




Server
Il ser ver , nel nostr o caso, è il computer sul quale r isiede l'applicazione pr incipale deputata alla gestione delle
connessioni e dei ser vizi dei client ester ni. Più in gener ale è una componente infor matica che for nisce ser vizi ad altr e
componenti attr aver so una r ete. Per implementar e un'applicazione Ser ver da codice dobbiamo far sì che essa possa
accettar e connessioni da par te di altr i. Per far questo è necessar io usar e la classe TcpListener , che si mette in ascolto
su di una por ta, e r ifer isce quando ci sono r ichieste di connessioni in attesa su di essa. I suoi membr i di spicco sono:

       AcceptTcpClient() : accetta una connessione in attesa e r estituisce un oggetto TcpClient collegato al client che ha
       inviato la r ichiesta. Usando tale oggetto sar à possibile inviar e o r icever e dati, poiché il Listener , di per sé, non
       fa altr o che attender e e accettar e connessioni, ma l'azione ver a viene intr apr esa da oggetti TcpClient;
       Pending() : r estituisce Tr ue se si cono connessioni in attesa;
       Ser ver : r estituisce l'oggetto socket che TcpListener sfr utta. Come avevo detto nel par agr afo intr oduttivo,
       queste due classi fanno uso inter namente di Socket, ma espongono metodi di più semplice gestione;
       Star t() : inizia l'oper azione di listening su una por ta data;
       Stop() : inter r ompe il listening;

Il costr uttor e di TcpListener che user emo r ichiede come unico par ametr o la por ta su cui metter si in ascolto.




Client
La classe fondamentalmente usata per un client è TcpClient. I suoi membr i più significativi sono:

       Available: r estituisce il numer o di bytes r icevuti e pr onti per la lettur a
       Close(): chiude la connessione
       Connect(IP, P): tenta una connessione ver so il ser ver identificato da IP sulla por ta P. IP può esser e sia un
       indir izzo IP che DNS
       Connected: r estituisce Tr ue se è connesso, altr imento False
       GetStr eam(): funzione impor tantissima che r estituisce un oggetto di tipo Sockets.Netw or kStr eam su cui e da cui
       si scr ivono e leggono tutti i dati scambiati tr a client e ser ver
       ReceiveBuffer Size: imposta la gr andezza del buffer di bytes r icevuti
       SendBuffer Size: imposta la gr andezza del buffer di bytes inviati

Il client tenta la connessione al ser ver e, se accettato, può dialogar e con esso scambiando messaggi. I dati vengono
inviati e r icevuti attr aver so uno str eam di r ete bidir ezionale, che è possibile ottener e r ichiamando GetStr eam().
Quando il client scr ive su questo str eam, il ser ver r iceve i dati e li può legger e, e vicever sa.




Un semplic e sc ambio di messaggi
Per iniziar e scr iver emo un semplice pr ogr amma per scambiar e messaggi (chiamar lo "chat" sar ebbe a dir poco
inoppor tuno). Per semplicità d'uso, la stessa applicazione potr à far e sia da ser ver che da client, così un utente potr à
sia collegar si ad un altr o che attender e connessioni (ma non far e le due cose contempor aneamente). L'inter faccia che ho
pr epar ato è questa:




Ci sono anche due timer , tmr Connections e tmr Data. Ecco il codice:

 001. Imports System.Net.Sockets
 002. Imports System.Text.UTF8Encoding
 003.
 004. Public Class Form1
 005.
 006.     Private Listener As TcpListener
 007.     Private Client As TcpClient
 008.     Private NetStream As NetworkStream
 009.
 010.     'Questa procedura serve per attivare o disattivare i
 011.     'controlli a seconda che si sia connessi oppure no. Serve
 012.     'per impedire che si tenti di inviare un messaggio quando
 013.     'non si è connessi, ad esempio
 014.     Private Sub EnableControls(ByVal Connected As Boolean)
 015.         btnConnect.Enabled = Not Connected
 016.         btnListen.Enabled = Not Connected
 017.         txtIP.Enabled = Not Connected
 018.
 019.         btnSend.Enabled = Connected
 020.         txtMessage.Enabled = Connected
 021.         btnDisconnect.Enabled = Connected
 022.
023.       If Connected Then
024.            tmrData.Start()
025.       Else
026.            tmrData.Stop()
027.       End If
028.   End Sub
029.
030.   Private Sub btnListen_Click(ByVal sender As Object, ByVal e As EventArgs) Handles
          btnListen.Click
031.        'Inizializza il listener e inizia l'ascolto sulla porta
032.        '5000. Inoltre, attiva il timer per controllare se ci
033.        'sono connessioni in arrivo. Il timer scatta ogni 100ms
034.        Listener = New TcpListener(5000)
035.        Listener.Start()
036.        tmrConnections.Start()
037.        btnListen.Enabled = False
038.        btnConnect.Enabled = False
039.        txtLog.AppendText("Server - in ascolto..." & Environment.NewLine)
040.   End Sub
041.
042.   Private Sub tmrConnections_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs)
          Handles tmrConnections.Tick
043.        'Se ci sono connessioni...
044.        If Listener.Pending() Then
045.            'Ferma un attimo il timer
046.            tmrConnections.Stop()
047.
048.           'Chiede all'utente se confermare la connessione
049.           If MessageBox.Show("Rilevato un tentativo di connessione. Accettare?", Me.Text,
                  MessageBoxButtons.YesNo, MessageBoxIcon.Question) =
                  Windows.Forms.DialogResult.Yes Then
050.                'Ottiene l'oggetto TcpClient collegato al client
051.                Client = Listener.AcceptTcpClient()
052.                'Ferma il listener
053.                Listener.Stop()
054.                'Ottiene il network stream
055.                NetStream = Client.GetStream()
056.                'E attiva/disattiva i controlli per quando si è connessi
057.                EnableControls(True)
058.           Else
059.                Listener.Stop()
060.                Listener.Start()
061.                tmrConnections.Start()
062.           End If
063.       End If
064.   End Sub
065.
066.   Private Sub btnConnect_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
          Handles btnConnect.Click
067.        Dim IP As Net.IPAddress
068.
069.       'Prima esegue un controllo sull'indirizzo IP per
070.       'controllare che sia valido
071.       If Not Net.IPAddress.TryParse(txtIP.Text, IP) Then
072.           MessageBox.Show("IP non valido!", Me.Text, MessageBoxButtons.OK,
                  MessageBoxIcon.Exclamation)
073.           Exit Sub
074.       End If
075.
076.       'Quindi inizializza un client e tenta la connessione
077.       'al dato IP sulla porta 5000
078.       Client = New TcpClient()
079.       txtLog.AppendText("Client - tentativo di connessione..." & vbCrLf)
080.       Try
081.           Application.DoEvents()
082.           Client.Connect(IP, 5000)
083.       Catch Ex As Exception
084.
085.       End Try
086.
087.       'Se la connessione ha avuto successo, ottiene il network
088.
'stream e agisce sui controlli come nel codice precedente
 089.             If Client.Connected Then
 090.                  txtLog.AppendText("Tentativo di connessione riuscito!" & vbCrLf)
 091.                  NetStream = Client.GetStream()
 092.                  EnableControls(True)
 093.             Else
 094.                  txtLog.AppendText("Tentativo di connessione fallito..." & vbCrLf)
 095.             End If
 096.         End Sub
 097.
 098.         Private Sub tmrData_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs)
                 Handles tmrData.Tick
 099.              'Se ci sono dati disponibili
 100.              If Client.Available > 0 Then
 101.                  'Li legge dallo stream
 102.                  Dim Buffer(Client.Available - 1) As Byte
 103.                  NetStream.Read(Buffer, 0, Buffer.Length)
 104.
 105.                    'Li trasforma in una stringa
 106.                    Dim Msg As String = UTF8.GetString(Buffer)
 107.
 108.                    'Se il messaggio inizia con questa stringa
 109.                    'particolare signifia che l'altro utente ha chiuso
 110.                    'la connessione, quindi disconnette anche questo
 111.                    If Msg.StartsWith("close") Then
 112.                        btnDisconnect_Click(Me, EventArgs.Empty)
 113.                        Exit Sub
 114.                    End If
 115.
 116.                 'Altrimenti lo accoda alla textbox grande
 117.                 txtLog.AppendText("Ricevuto: ")
 118.                 txtLog.AppendText(Msg)
 119.                 txtLog.AppendText(Environment.NewLine)
 120.             End If
 121.         End Sub
 122.
 123.         Private Sub btnSend_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
                 Handles btnSend.Click
 124.              If Not String.IsNullOrEmpty(txtMessage.Text) Then
 125.                  Dim Buffer() As Byte = UTF8.GetBytes(txtMessage.Text)
 126.                  txtLog.AppendText("Inviato: " & txtMessage.Text & Environment.NewLine)
 127.                  NetStream.Write(Buffer, 0, Buffer.Length)
 128.                  txtMessage.Text = ""
 129.              End If
 130.         End Sub
 131.
 132.         Private Sub btnDisconnect_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
                 Handles btnDisconnect.Click
 133.              tmrData.Stop()
 134.
 135.         Dim Buffer() As Byte = UTF8.GetBytes("close")
 136.         NetStream.Write(Buffer, 0, Buffer.Length)
 137.
 138.         Client.Client.Close()
 139.         Client.Close()
 140.         Client = Nothing
 141.
 142.         If Listener IsNot Nothing Then
 143.             Listener.Server.Close()
 144.             Listener = Nothing
 145.         End If
 146.
 147.         EnableControls(False)
 148.         txtLog.AppendText("Disconnesso" & Environment.NewLine)
 149.     End Sub
 150. End Class

Come avete visto dal codice non c'è nulla di par ticolar mente complicato da capir e. Tuttavia, questo pr ogr amma è molto
semplice e per mette di gestir e solo una connessione (in ar r ivo o in uscita). Il Listener , anche se r iavviabile, continuer à
a dar e Pending = Tr ue almeno fino a che tutti i client r elativi alla connessione siano stati cor r ettamente chiusi, e
questo non si ver ifica se non alla fine del pr ogr amma, ossia quando uno dei due si disconnette. Per far la br eve, è
impossibile cr ear e un'applicazione di scambio messaggi multiutente con questi oggetti e queste modalità.
D5. I Socket - Parte II


Esempio: File Sender
Fino ad or a si è par lato di inviar e semplici messaggi sotto for ma di str inghe, ma come ci si dovr ebbe compor tar e nel
caso il contenuto da inviar e sia un file inter o o, per chè no?, molti files? Il pr ocedimento è lo stesso e con questo esempio
for nir ò una pr ova di come sia altr ettanto semplice questo compito. L'applicazione File Sender si basa su un semplice
scambio di inter r ogazioni tr a i due computer , al ter mine delle quali si inizia l'invio effettivo del file. Per pr ima cosa il
client comunica al ser ver che sta per cominciar e il flusso di dati; il ser ver deve per ciò r isponder e in caso affer mativo
se l'utente è disposto al tr asfer imento: in questo caso, r imanda indietr o un messaggio di confer ma, e apr e una nuova
por ta per i dati in ar r ivo; par allelamente, il client si connette alla por ta aper ta e inizia il tr asfer imento.




File Sender: server
Ho str uttur ato l'inter faccia del ser ver in questo modo:

         Label1 : una label esplicativo con il testo "Pr ogr esso:"
         pr gPr ogr ess : la bar r a del pr ogr esso
         cmdListen : il pulsante "Ascolta"
         str Status : la status str ip sul lato basso del for m
         lblStatus : la label contenuta in str Status, con il compito di infor mar e l'utente sullo stato dell'applicazione
         tmr Contr olConnection : timer con Inter val = 100 che ha il compito di contr ollar e se ci sono r ichieste in attesa
         tmr Contr olFile : timer con Inter val = 100 con il compito di contr ollar e se ci sono r ichieste in attesa sulla por ta
         1001, deputata in questo caso alla r icezione del file dal client
         tmr GetData : timer con Inter val = 100 con il compito di ottener e i messaggi inviati dal client e di r isponder vi
         bgReceiveFile : Backgr oundWr oker con Wr oker Repor tPr ogr ess = Tr ue che ha il compito di r icever e il file dal
         client

E si pr esenta gr aficamente così:




Ed ecco il codice:

  001.
Imports System.Net.Sockets
002. Imports System.Text.ASCIIEncoding
003.
004. Imports System.ComponentModel
005. Public Class Form1
006.     'Listener: attende una connessione sulla porta 25
007.     'FileListener: attende una connessione sulla porta 1001. Questa
008.     '   ha il compito di trasferire i bytes del file
009.
010.     Private Listener, FileListener As TcpListener
011.     'Client: l'oggetto che ha il compito di dialogare con
012.     '   il client e confermarne le operazioni
013.     'FileReceiver: l'oggetto che ha il compito di ricevere le
014.
015.     '   informazioni contenute nel file e scriverle sulla macchina
016.     '   in forma di file concreto
017.     Private Client, FileReceiver As TcpClient
018.     'NetStream: lo stream su cui si scrivono i dati di comunicazione
019.
020.     'NetFile: lo stream da cui si leggono i dati del file
021.     Private NetStream, NetFile As NetworkStream
022.     'Percorso su cui salvare il file
023.     Private FileName As String
024.
025.     'Dimensione del file
026.     Private FileSize As Int64
027.
028.     'I seguenti metodi semplificano le operazioni di invio e
029.     'ricezione di stringhe
030.
031.     'Invia un messaggio su uno stream di rete
032.     Private Sub Send(ByVal Msg As String, ByVal Stream As NetworkStream)
033.         'Se si può scrivere
034.
035.         If Stream.CanWrite Then
036.              'Converte il messaggio in binario
037.              Dim Bytes() As Byte = ASCII.GetBytes(Msg)
038.              'E lo scrive sul network stream
039.
040.              Stream.Write(Bytes, 0, Bytes.Length)
041.         End If
042.     End Sub
043.
044.     'Ottiene un messaggio dallo stream di rete
045.     Private Function GetMessage(ByVal Stream As NetworkStream) As String
046.
047.         'Se si può leggere
048.         If Stream.CanRead Then
049.              Dim Bytes(Client.ReceiveBufferSize) As Byte
050.
051.              Dim Msg As String
052.              'Legge i bytes arrivati
053.              Stream.Read(Bytes, 0, Bytes.Length)
054.              'Li converte in una stringa leggibile
055.              Msg = ASCII.GetString(Bytes)
056.              'E restituisce la stringa
057.
058.              Return Msg.Normalize
059.         Else
060.              Return Nothing
061.         End If
062.     End Function
063.
064.     Private Sub cmdListen_Click(ByVal sender As Object, _
065.         ByVal e As EventArgs) Handles cmdListen.Click
066.         If cmdListen.Text = "Ascolta" Then
067.
068.              'Inizia ad ascoltare sulla porta 25
069.              Listener = New TcpListener(25)
070.              Listener.Start()
071.              'Attiva il timer per controllare le richieste di connesione
072.              tmrControlConnection.Start()
073.
'Cambia il testo e la funzione del pulsante
074.              cmdListen.Text = "Stop"
075.       Else
076.
077.           'Ferma l'operazione di ascolto
078.           Listener.Stop()
079.           'Ripristina il testo
080.           cmdListen.Text = "Ascolta"
081.       End If
082.   End Sub
083.
084.   Private Sub tmrControlConnection_Tick(ByVal sender As Object, _
085.       ByVal e As EventArgs) Handles tmrControlConnection.Tick
086.       'Se ci sono connessioni in attesa...
087.
088.       If Listener.Pending Then
089.           'Ferma il timer per eseguire le operazioni
090.           tmrControlConnection.Stop()
091.           lblStatus.Text = "È stata ricevuta una richiesta"
092.           'Richiede all'utente se accettare la connessione
093.           If MessageBox.Show("È stata ricevuta una richiesta di connessione. Accettare?", _
094.               Me.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Question) = _
095.               Windows.Forms.DialogResult.Yes Then
096.
097.                     'Acceta la connessione
098.                     Client = Listener.AcceptTcpClient
099.                     'Apre lo stream di rete condiviso
100.                     NetStream = Client.GetStream
101.                     'Termina l'ascolto
102.                     Listener.Stop()
103.                     'Rende il pulsante cmdListen inutilizzabile, poiché
104.                     'una connessione è già stata aperta
105.
106.                     cmdListen.Enabled = False
107.                     'Inizia la ricezione di messaggi
108.                     tmrGetData.Start()
109.                     lblStatus.Text = "Connessione riuscita!"
110.              Else
111.                  'Altrimenti si rimette in attesa per altre connessioni
112.                  tmrControlConnection.Start()
113.                  lblStatus.Text = "In attesa di connessioni..."
114.              End If
115.
116.       End If
117.   End Sub
118.
119.   Private Sub tmrControlFile_Tick(ByVal sender As Object, _
120.       ByVal e As EventArgs) Handles tmrControlFile.Tick
121.       'Se c'è una richiesta, l'accetta subito
122.
123.       If FileListener.Pending Then
124.           tmrControlFile.Stop()
125.           FileReceiver = FileListener.AcceptTcpClient
126.           NetFile = FileReceiver.GetStream
127.           'Ferma il listener
128.           FileListener.Stop()
129.           lblStatus.Text = "Flusso di informazioni aperto"
130.           'Attiva la ricezione di dati attraverso un background worker
131.           bgReceiveFile.RunWorkerAsync()
132.       End If
133.
134.   End Sub
135.
136.   Private Sub tmrGetData_Tick(ByVal sender As Object, _
137.       ByVal e As EventArgs) Handles tmrGetData.Tick
138.       If Client.Connected And Client.Available Then
139.
140.              'Ferma il timer mentre si eseguono le operazioni
141.              tmrGetData.Stop()
142.              'Legge il messaggio
143.              Dim Msg As String = GetMessage(NetStream)
144.
145.
If Msg.StartsWith("ConfirmTransfer") Then
146.
147.               'Divide il messagio in parti in base al carattere pipe
148.               Dim Parts() As String = Msg.Split("|")
149.               'La prima parte è "ConfirmTransfer"
150.
151.               'La seconda è il percorso del file sull'altro computer
152.               Dim File As String = Parts(1)
153.               'La terza è la dimensione
154.
155.               Dim Size As Int64 = CType(Parts(2), Int64)
156.               'Ottiene solo il nome del file, senza percorso
157.               File = IO.Path.GetFileName(File)
158.               'Costruisce il percorso del file su questo computer,
159.               'salvandolo nella cartella del progetto (binDebug)
160.
161.               FileName = Application.StartupPath & "" & File
162.               'Imposta Size come variabile globale
163.               FileSize = Size
164.               'Richiede se accettare il trasferimento
165.               If MessageBox.Show(String.Format( _
166.               "È stata ricevuta una richiesta di trasferimento di {0} ({1} bytes).
                      Acettare?", _
167.               File, Size), Me.Text, MessageBoxButtons.YesNo, _
168.               MessageBoxIcon.Question) = Windows.Forms.DialogResult.Yes Then
169.
170.                      'Manda OK al client
171.                      Send("OK", NetStream)
172.                      'Intanto si mette in attesa sulla porta 1001 per
173.                      'l'invio dei bytes del file
174.                      FileListener = New TcpListener(1001)
175.                      FileListener.Start()
176.                      'E attiva il timer di controllo
177.
178.                      tmrControlFile.Start()
179.               Else
180.                   'Altrimenti, risponde di no
181.                   Send("NO", NetStream)
182.               End If
183.           End If
184.
185.           'Riprende il controllo
186.           tmrGetData.Start()
187.       End If
188.   End Sub
189.
190.   Private Sub bgReceiveFile_DoWork(ByVal sender As Object, _
191.       ByVal e As DoWorkEventArgs) Handles bgReceiveFile.DoWork
192.       'Apre un nuovo stream in base al percorso costruito
193.
194.       'nella procedura precedente
195.       Dim Stream As New IO.FileStream(FileName, IO.FileMode.Create)
196.       'Crea un indice che indica il progresso
197.       Dim Index As Int64 = 0
198.
199.       lblStatus.Text = "In ricezione..."
200.       Do
201.
202.           If FileReceiver.Available Then
203.               'Riceve i bytes necessari
204.               Dim Bytes(4096) As Byte
205.
206.               Dim Msg As String = ASCII.GetString(Bytes)
207.               'Se i bytes sono un messaggio stringa e contengono
208.               '"END", oppure la dimensione giusta è già stata
209.
210.               'raggiunta, allora si ferma
211.               If Msg.Contains("END") Or Index >= FileSize Then
212.                   Exit Do
213.
214.               End If
215.               'Preleva i bytes dallo stream di rete
216.
NetFile.Read(Bytes, 0, 4096)
  217.                         'E li scrive sul file fisico
  218.                         Stream.Write(Bytes, 0, 4096)
  219.                         'Incrementa l'indice di 4096
  220.
  221.                         Index += 4096
  222.                         'E notifica il progresso
  223.                         bgReceiveFile.ReportProgress(Index * 100 / FileSize)
  224.                     End If
  225.              Loop
  226.
  227.             lblStatus.Text = "File ricevuto!"
  228.             Stream.Close()
  229.             MessageBox.Show("File ricevuto con successo!", Me.Text, _
  230.                 MessageBoxButtons.OK, MessageBoxIcon.Information)
  231.         End Sub
  232.
  233.         Private Sub bgReceiveFile_ProgressChanged(ByVal sender As Object, _
  234.             ByVal e As ProgressChangedEventArgs) _
  235.             Handles bgReceiveFile.ProgressChanged
  236.             prgProgress.Value = e.ProgressPercentage
  237.         End Sub
  238.
  239. End     Class




File Sender: c lient
Ho str uttur a l'inter faccia del client in questo modo:

       gr pTr asnfer : un Gr oupBox con Tex t = "Tr asfer imento" che contiene tutti i contr olli sul tr asfer imento del file
       tx tFile : una Tex tBox che contiene il per cor so del file da inviar e
       cmdBr ow se : un pulsante con Tex t = "Sfoglia" per per metter e all'utente di selezionar e un file in manier a
       semplice
       cmdSend : un pulsante con Tex t = "Invia" che ha il compito di inoltr ar e la r ichiesta al ser ver
       pr gPr ogr ess : una bar r a di pr ogr esso
       cmdConnect : un pulsante con Tex t = "Connetti" con il compito di connetter si al ser ver
       str Status : una StatusStr ip nel lato infer ior e del for m
       lblStatus : la label con il compito di tener e l'utente al cor r ente dello stato dell'applicazione
       tmr GetData : un timer con Inter val = 100 per r icever e e inviar e messaggi al ser ver
       bgSendFile : un Backgr oundWr oker con Wr oker Repor tPr ogr ess = Tr ue che ha il compito di inviar e il file

L'inter faccia si pr esenta così:
E questo è il codice:

 001.    Imports System.Net.Sockets
 002.    Imports System.Text.ASCIIEncoding
 003.    Imports System.ComponentModel
 004.
 005.    Public Class Form1
 006.        'Client: il client che si dovrà connettere al server
 007.        'FileSender: il client che ha il compito di trasferire i
 008.        '   pacchetti di informazioni al server
 009.
 010.         Private Client, FileSender As TcpClient
 011.         'NetStream: lo stream su cui scrivere i dati di comunicazione
 012.         'NetFile: lo stream per inviare i dati da scrivere sul file
 013.         Private NetStream, NetFile As NetworkStream
 014.         'L'IP del server a cui connettersi
 015.
 016.         Private IP As String
 017.
 018.         'I seguenti metodi semplificano le operazioni di invio e
 019.         'ricezione di stringhe
 020.
 021.         'Invia un messaggio su uno stream di rete
 022.         Private Sub Send(ByVal Msg As String, ByVal Stream As NetworkStream)
 023.             'Se si può scrivere
 024.
 025.              If Stream.CanWrite Then
 026.                  'Converte il messaggio in binario
 027.                  Dim Bytes() As Byte = ASCII.GetBytes(Msg)
 028.                  'E lo scrive sul network stream
 029.
 030.                 Stream.Write(Bytes, 0, Bytes.Length)
 031.             End If
 032.         End Sub
 033.
 034.         'Ottiene un messaggio dallo stream di rete
 035.         Private Function GetMessage(ByVal Stream As NetworkStream) As String
 036.
 037.              'Se si può leggere
 038.              If Stream.CanRead Then
 039.                  Dim Bytes(Client.ReceiveBufferSize) As Byte
 040.
 041.                     Dim Msg As String
 042.                     'Legge i bytes arrivati
 043.                     Stream.Read(Bytes, 0, Bytes.Length)
 044.                     'Li converte in una stringa leggibile
 045.                     Msg = ASCII.GetString(Bytes)
 046.                     'E restituisce la stringa
 047.
 048.                     Return Msg.Normalize
 049.              Else
 050.                 Return Nothing
 051.             End If
 052.         End Function
 053.
 054.         Private Sub cmdConnect_Click(ByVal sender As Object, _
 055.             ByVal e As EventArgs) Handles cmdConnect.Click
 056.             'Ottiene l'IP del server
 057.
 058.              IP = InputBox("Inserire l'IP del server:", Me.Text)
 059.
 060.              'Controlla che l'IP non sia nullo o vuoto
 061.              If String.IsNullOrEmpty(IP) Then
 062.                  MessageBox.Show("Connessiona annullata!", Me.Text, _
 063.                      MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
 064.                  Exit Sub
 065.
 066.              End If
 067.
 068.              'Inizializza un nuovo client
 069.
Client = New TcpClient
070.       'E tenta la connessione all'IP dato, sulla porta 25
071.
072.       lblStatus.Text = "Connessione in corso..."
073.       Try
074.           Client.Connect(IP, 25)
075.       Catch SE As SocketException
076.           MessageBox.Show("Impossibile stabilire una connessione!", _
077.           Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
078.           Exit Sub
079.
080.       End Try
081.       'Se la connessione è riuscita, ottiene lo
082.       'stream condiviso di rete direttamente collegato con
083.       'il networkstream del server
084.
085.       If Client.Connected Then
086.           'Ora si è sicuri di essere connessi:
087.           'sblocca i comandi per il trasferimento
088.           NetStream = Client.GetStream
089.           grpTransfer.Enabled = True
090.           lblStatus.Text = "Connessione riuscita!"
091.       End If
092.
093.   End Sub
094.
095.   Private Sub cmdBrowse_Click(ByVal sender As Object, _
096.       ByVal e As EventArgs) Handles cmdBrowse.Click
097.       Dim Open As New OpenFileDialog
098.       Open.Filter = "Tutti i file|*.*"
099.       If Open.ShowDialog = Windows.Forms.DialogResult.OK Then
100.
101.           txtFile.Text = Open.FileName
102.       End If
103.   End Sub
104.
105.   Private Sub cmdSend_Click(ByVal sender As Object, _
106.       ByVal e As EventArgs) Handles cmdSend.Click
107.       'Controlla che il file esista
108.
109.       If Not IO.File.Exists(txtFile.Text) Then
110.           MessageBox.Show("Il file non esiste!", Me.Text, _
111.           MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
112.           Exit Sub
113.       End If
114.
115.       'Se si è connessi e si può scrivere
116.       'sullo stream di rete...
117.       If Client.Connected AndAlso NetStream.CanWrite Then
118.
119.             'Manda un messaggio al server, chiedendo
120.             'conferma del trasferimento. Nel messaggio immette anche
121.             'alcune informazioni riguardo il nome e la
122.             'dimensione del file
123.             Dim Msg As String = _
124.                 String.Format("ConfirmTransfer|{0}|{1}", txtFile.Text, _
125.                 FileLen(txtFile.Text))
126.             'Invia il messaggio con la procedura scritta sopra
127.
128.             Send(Msg, NetStream)
129.
130.           'Attiva il timer per controllare i dati arrivati
131.           tmrGetData.Start()
132.           'Disattiva il pulsante per evitare più azioni
133.           'contemporanee indesiderate
134.           cmdSend.Enabled = False
135.           lblStatus.Text = "In attesa di conferma dal server..."
136.       End If
137.
138.   End Sub
139.
140.   Private Sub tmrGetData_Tick(ByVal sender As Object, _
141.
ByVal e As EventArgs) Handles tmrGetData.Tick
142.       If Client.Connected AndAlso Client.Available Then
143.
144.             'Ferma il timer mentre si eseguono le operazioni
145.             tmrGetData.Stop()
146.             'Legge il messaggio
147.             Dim Msg As String = GetMessage(NetStream)
148.
149.             'Uso Contains per un semplice motivo. Quando si converte
150.
151.             'un array di bytes in una stringa, ci possono essere
152.             'caratteri speciali successivi a questa, come ad esempio
153.             'il NULL terminator (carattere 00), che ne compromettono
154.             'la struttura.
155.             If Msg.Contains("OK") Then
156.
157.                 'Termina questa connessione e si connette
158.                 'alla porta deputata alla ricezione dei file
159.                 FileSender = New TcpClient
160.                 FileSender.Connect(IP, 1001)
161.                 If FileSender.Connected Then
162.
163.                     'Ottiene lo stream associato a questa operaizone
164.                     NetFile = FileSender.GetStream
165.                     'E inizia la trasmissione dei dati
166.                     bgSendFile.RunWorkerAsync(txtFile.Text)
167.                 End If
168.             ElseIf Msg.Contains("NO") Then
169.
170.                 MessageBox.Show("Il server ha rifiutato il trasferimento!", _
171.                 Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
172.                 cmdSend.Enabled = True
173.             End If
174.
175.           'Riprende il controllo dei dati
176.           tmrGetData.Start()
177.       End If
178.
179.   End Sub
180.
181.   Private Sub bgSendFile_DoWork(ByVal sender As Object, _
182.       ByVal e As DoWorkEventArgs) Handles bgSendFile.DoWork
183.       'Ottiene il nome del file dall'argomento passato al metodo
184.
185.       'RunWorkerAsync nella procedura precedente
186.       Dim FileName As String = e.Argument
187.       'Crea un nuovo lettore del file a basso livello, così
188.       'da poter ottenere bytes di informazione anziché caratteri
189.
190.       'come nello StreamReader
191.       Dim Reader As New IO.FileStream(FileName, IO.FileMode.Open)
192.       'Calcola la grandezza del file, per poter poi tenere
193.       'l'utente al corrente della percentuale di completamento
194.
195.       Dim Size As Int64 = FileLen(FileName)
196.       'Un blocco di bytes da 4096 posti. Il file viene spedito in
197.       '"pacchettini" per evitare di sovraccaricare la connessione
198.       Dim Bytes(4095) As Byte
199.
200.       'Se il file è più grande di 4KiB, lo divide
201.       'in blocchi di dati da 4096 bytes
202.       If Size > 4096 Then
203.
204.             For Block As Int64 = 0 To Size Step 4096
205.                 'Se i bytes rimanenti sono più di 4096,
206.
207.                 'ne legge un blocco intero
208.                 If Size - Block >= 4096 Then
209.                      Reader.Read(Bytes, 0, 4096)
210.                 Else
211.                      'Altrimenti un blocco più piccolo
212.
213.
Reader.Read(Bytes, 0, Size - Block)
214.                         End If
215.                         'Scrive i dati prelevati sullo stream di rete,
216.                         'inviandoli così al server
217.                         NetFile.Write(Bytes, 0, 4096)
218.                         'Riporta la percentuale all'utente
219.
220.                         bgSendFile.ReportProgress(Block * 100 / Size)
221.                         'Smette per 30ms, così da dare tempo dal
222.                         'server di poter processare i pacchetti uno per
223.                         'uno, evitando confusione
224.                         Threading.Thread.Sleep(30)
225.                  Next
226.
227.           Else
228.               'Se il file è minore di 4KiB, lo invia tutto
229.               'direttamente dal server
230.               Reader.Read(Bytes, 0, Size)
231.               NetFile.Write(Bytes, 0, Size)
232.           End If
233.
234.           Reader.Close()
235.
236.           'Percentuale massima: lavoro terminato
237.           bgSendFile.ReportProgress(100)
238.           Threading.Thread.Sleep(100)
239.           'Comunica la fine delle operazioni
240.           NetFile.Write(ASCII.GetBytes("END"), 0, 3)
241.           MessageBox.Show("File inviato con successo!", Me.Text, _
242.
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net
 Vb.net

More Related Content

Similar to Vb.net (20)

PDF
Dialogare con le macchine in linguaggio naturale... Finalmente! Ma ci voleva ...
KEA s.r.l.
 
PDF
Object Oriented Programming
Massimiliano Brolli
 
PDF
Introduzione a Latex
Ivan Gualandri
 
PDF
Lezione01
robynho86
 
PDF
Lezione01
robynho86
 
PDF
Sviluppo di un Framework semantico per la contestualizzazione delle activity
Michele Palumbo
 
PPTX
Strumenti E Tecniche
lab13unisa
 
PDF
Progetto e sviluppo di un’applicazione per la gestione di un reagentario per ...
Marko Paliska
 
PPT
Corso Java
Giuseppe Dell'Abate
 
PDF
Elearning E Lms
FormaLms
 
PPT
Xml - progettazione unità didattica
Viola Anesin
 
PDF
2-Intro-Java.pdf
AntonioGeniola
 
PDF
Scelta Software Elearning
FormaLms
 
PDF
X-NERVal e Component NER
skasof
 
PPTX
Lezione 8 Il Web Semantico
Stefano Epifani
 
PDF
Latex
NaLUG
 
PPT
Capitolo 2 elementi di programmazione in vba
Giovanni Della Lunga
 
PDF
(E book ita) java introduzione alla programmazione orientata ad oggetti in ...
Raffaella D'angelo
 
PDF
Solution Manual for Computer Organization &amp; Architecture Themes and Varia...
salsagattauj
 
ODP
Gestione corsi con TYPO3
Ivano Luberti
 
Dialogare con le macchine in linguaggio naturale... Finalmente! Ma ci voleva ...
KEA s.r.l.
 
Object Oriented Programming
Massimiliano Brolli
 
Introduzione a Latex
Ivan Gualandri
 
Lezione01
robynho86
 
Lezione01
robynho86
 
Sviluppo di un Framework semantico per la contestualizzazione delle activity
Michele Palumbo
 
Strumenti E Tecniche
lab13unisa
 
Progetto e sviluppo di un’applicazione per la gestione di un reagentario per ...
Marko Paliska
 
Elearning E Lms
FormaLms
 
Xml - progettazione unità didattica
Viola Anesin
 
2-Intro-Java.pdf
AntonioGeniola
 
Scelta Software Elearning
FormaLms
 
X-NERVal e Component NER
skasof
 
Lezione 8 Il Web Semantico
Stefano Epifani
 
Latex
NaLUG
 
Capitolo 2 elementi di programmazione in vba
Giovanni Della Lunga
 
(E book ita) java introduzione alla programmazione orientata ad oggetti in ...
Raffaella D'angelo
 
Solution Manual for Computer Organization &amp; Architecture Themes and Varia...
salsagattauj
 
Gestione corsi con TYPO3
Ivano Luberti
 

Vb.net

  • 2. A1. Introduzione Benvenuti, aspir anti pr ogr ammator i! In questa guida dalla lunghezza chiolemtr ica impar er ete cosa significa e cosa compor ta pr ogr ammar e, e tutti i tr ucchi e gli espedienti per costr uir e solide e sicur e applicazioni. Una veloc e panoramic a sulla programmazione La pr ogr ammazione è quella disciplina dell'infor matica che si occupa di idear e, costr uir e e mantener e il softw ar e. Queste sono le tr e pr incipali divisioni che si possono oper ar e all'inter no di questa speciale e affascinante br anca dell'ingegner ia. Infatti, un buon pr ogr ammator e deve pr ima di tutto analizzar e il pr oblema, quindi pensar e a una possibile soluzione, se esiste, e costr uir e mentalmente un'ipotetica str uttur a del softw ar e che dovr à impegnar si a scr iver e: questa par te della pr ogettazione si chiama analisi. Successivamente, si viene alla fase più tecnica, e che implica una conoscenza dir etta del linguaggio di pr ogr ammazione usato: in questa guida, mi occuper ò di descr iver e il Visual Basic .NET. Una volta sviluppato il pr ogr amma, lo si deve testar e per tr ovar e eventuali malfunzionamenti (bugs) - che, per inciso, si manifestano solo quando non dovr ebber o - e, come ultima oper azione, bisogna attuar e una manutenzione per iodica dello stesso, od or ganizzar e un efficiente sistema di aggior namento. Inutile dir e che l'ultima fase è necessar ia solo nel caso di gr andi applicazioni commer ciali e non cer tamente nel contesto di piccoli pr ogr ammi amator iali. Pr ima di iniziar e, una br eve sintesi di alcuni dettagli tecnici: i ter mini da conoscer e, e gli ambienti di sviluppo da usar e. Alc uni termini da c onosc ere Co dice so r g ente o so r g ente: l'insieme di tutte le istr uzioni che il pr ogr ammator e scr ive e fa eseguir e al pr ogr amma. Il file testuale che contiene tali istr uzioni viene esso stesso chiamato sor gente Co m pilato r e: il softw ar e utilizzato per cr ear e il pr ogr amma finito (un eseguibile *.ex e) a par tir e dal solo codice sor gente Debug g er : il softw ar e usato per l'analisi e la r isoluzione degli er r or i (bugs) all'inter no di un pr ogr amma; Par o le r iser v ate o k eyw o r ds: di solito vengono evidenziate dai compilator i in un color e diver so e sono par ole pr edefinite intr inseche del linguaggio, che ser vono per scopi ben pr ecisi. Ambiente di sviluppo L'ambiente di sviluppo che pr ender ò come r ifer imento per questa guida è Visual Basic Ex pr ess 2008 (scar icabile dal Sito Ufficiale della M icr o so ft; se si ha un pr ofilo Passpor t.NET è possibile r egistr ar e il pr odotto e ottener e una ver sione completa). Potete comunque scar icar e Shar pDevelop da qui (vedi sezione dow nloads), un pr ogr amma gr atis e molto buono (tr over ete una r ecensione nella sezione Sofw tar e di Pier oTofy.it r edatta da me e HeDo qui). Dato che le ver sioni pr ecedenti della guida, dalle quali è r ipr esa la maggior anza dei sor genti pr oposti, sono state r edatte pr endendo come esempio Visual Basic Ex pr ess 2005, potete scar icar e anche quello da qui.
  • 3. A2. Classi, Moduli e Namespace Objec t Oriented Programming I linguaggi .NET sono orien tati agli oggetti e così lo è anche VB.NET. Questo appr occio alla pr ogr ammazione ha avuto molto successo negli ultimi anni e si basa fondamentalmente sui concetti di astr azione, oggetto e inter azione fr a oggetti. A lor o volta, questi ultimi costituiscono un potente str umento per la modellizzazione e un nuovo modo di avvicinar si alla r isoluzione dei pr oblemi. La par ticolar e mentalità che questa linea di sviluppo adotta è favor evole alla r appr esentazione dei dati in modo ger ar chico, e per questo motivo il suo par adig m a di pr ogr ammazione - ossia l'insieme degli str umenti concettuali messi a disposizione dal linguaggio e il modo in cui il pr ogr ammator e concepisce l'applicativo - è definito da tr e concetti car dine: l'er editar ietà, il po lim o r fism o e l'incapsulam ento . Molto pr esto ar r iver emo ad osser var e nel par ticolar e le car atter istiche di ognuno di essi, ma pr ima vediamo di iniziar e con l'intr odur r e l'entità fondamentale che si pone alla base di tutti questi str umenti: la classe. Le Classi Come dicevo, una car atter istica par ticolar e di questa categor ia di linguaggi è che essi sono basati su un unico impor tantissimo concetto fondamentale: gli o g g etti, i quali vengono r appr esentati da classi. Una classe non è altr o che la r appr esentazio ne - o v v iam ente astr atta - di qualco sa di co ncr eto , mentr e l'oggetto sar à una concr etizzazione di questa r appr esentazione (per una discussione più appr ofondita sulla differ enza tr a classe e oggetto, veder e capitolo A7). Ad esempio, in un pr ogr amma che deve gestir e una videoteca, ogni videocassetta o DVD è r appr esentato da una classe; in un pr ogr amma per la fattur azione dei clienti, ogni cliente e ogni fattur a vengono r appr esentati da una classe. Insomma, ogni cosa, ogni entità, ogni r elazione - per fino ogni er r or e - tr ova la sua r appr esentazione in una classe. Detto questo, viene spontaneo pensar e che, se ogni cosa è astr atta da una classe, questa classe dovr à anche contener e dei dati su quella cosa. Ad esempio, la classe Utente dovr à contener e infor mazioni sul nome dell'utente, sulla sua passw or d, sulla sua data di nascita e su molto altr o su cui si può sor volar e. Si dice che tutte queste infor mazioni sono espo ste dalla classe: ognuna di esse, inoltr e, è r appr esentata da quello che viene chiamato m em br o. I membr i di una classe sono tutti quei dati e quelle funzionalità che essa espone. Per esser e usabile, per ò, una classe deve venir e pr ima dichiar ata, mediante un pr eciso codice. L'atto di dichiar ar e una qualsiasi entità le per mette di iniziar e ad "esister e": il pr ogr ammator e deve infatti ser vir si di qualcosa che è già stato definito da qualche par te, e senza di quello non può costr uir e niente. Con la par ola "entità" mi r ifer isco a qualsiasi cosa si possa usar e in pr ogr ammazione: dato che le vostr e conoscenze sono limitate, non posso che usar e dei ter mini gener ici e piuttosto vaghi, ma in br eve il mio lessico si far à più pr eciso. Nella pr atica, una classe si dichiar a così: 1. Class [NomeClasse] 2. ... 3. End Class dove [NomeClasse] è un qualsiasi nome che potete decider e ar bitr ar iamente, a seconda di cosa debba esser e r appr esentato. Tutto il codice compr eso tr a le par ole sopr a citate è inter no alla classe e si chiama co r po ; tutte le entità esistenti nel cor po sono dei membr i. Ad esempio, se si volesse idealizzar e a livello di codice un tr iangolo, si scr iver ebbe questo: 1. Class Triangolo 2. ... 3. End Class Nel cor po di Tr iangolo si potr anno poi definir e tutte le infor mazioni che gli si possono attr ibuir e, come la lunghezza
  • 4. dei lati, la tipologia, l'ampiezza degli angoli, ecceter a... I Moduli Nonostante il nome, i moduli non sono niente altr o che dei tipi speciali di classi. La differ enza sostanziale tr a i due ter mini ver r à chiar ita molto più avanti nella guida, poiché le vostr e attuali competenze non sono sufficienti a un completo appr endimento. Tuttavia, i moduli sar anno la tipologia di classe più usata in tutta la sezione A. I Namespac e Possiamo definir e classi e moduli come un ità fun zion ali: essi r appr esentano qualcosa, possono esser e usate, manipolate, istanziate, dichiar ate, ecceter a... Sono quindi str umenti attivi di pr ogr ammazione, che ser vono a r ealizzar e concr etamente azioni e a pr odur r e r isultati. I namespace, invece, appar tengono a tutt'altr o gener e di categor ia: essi sono solo dei r aggr uppamenti "passivi" di classi o di moduli. Possiamo pensar e a un namespace come ad una car tella, entr o la quale possono star e files, ma anche altr e car telle, ognuna delle quali r aggr uppa un par ticolar e tipo di infor mazione. Ad esempio, volendo scr iver e un pr ogr amma che aiuti nel calcolo geometr ico di alcune figur e, si potr ebbe usar e un codice str uttur ate come segue: 01. Namespace Triangoli 02. Class Scaleno 03. '... 04. End Class 05. 06. Class Isoscele 07. '... 08. End Class 09. 10. Class Equilatero 11. '... 12. End Class 13. End Namespace 14. 15. Namespace Quadrilateri 16. Namespace Parallelogrammi 17. Class Parallelogramma 18. '... 19. End Class 20. 21. Namespace Rombi 22. Class Rombo 23. '... 24. End Class 25. 26. Class Quadrato 27. '... 28. End Class 29. End Namespace 30. End Namespace 31. End Namespace Come si vede, tutte le classi che r appr esentano tipologie di tr iangoli (Scaleno, Isoscele, Equilater o) sono all'inter no del namespace Tr iangoli; allo stesso modo esiste anche il namespace Quadr ilater i, che contiene al suo inter no un altr o namespace Par allelogr ammi, poiché tutti i par allelogr ammi sono quadr ilater i, per definizione. In quest'ultimo esiste la classe Par allelogr amma che r appr esenta una gener ica figur a di questo tipo, ma esiste ancor a un altr o namespace Rombi: come noto, infatti, tutti i r ombi sono anche par allelogr ammi. Dall'esempio si osser va che i namespace categor izzano le unità funzionali, dividendole in insiemi di per tinenza. Quando un namespace si tr ova all'inter no di un altr o namespace, lo si definisce nidificato: in questo caso, Par alleloogr ammi e Rombi sono namespace nidificati. Altr a cosa: al contr ar io della classi, gli spazi di nomi (italianizzazione dell'inglese name-space) non possiedono un "cor po", poiché questo ter mine si può usar e solo quando si par la di qualcosa di attivo;
  • 5. per lo stesso motivo, non si può neanche par lar e di membr i di un namespace.
  • 6. A3. Panoramica sul Framework .NET Come ho spiegato nel pr ecedente capitolo, il concetto più impor tante della pr ogr ammazione ad oggetti è la classe. Quindi, per scr iver e i nostr i pr ogr ammi, utilizzer emo sempr e, bene o male, queste entità. Ma non è possibile pensar e che si debba scr iver e tutto da zer o: per i pr ogr ammator i .NET, esiste un vastissimo inventar io di classi già pr onte, r aggr uppate sotto una tr entina di namespace fondamentali. L'insieme di tutti questi str umenti di pr ogr ammazione è il Fr am ew o r k .NET, l'ossatur a pr incipale su cui si r eggono tutti i linguaggi basati sulla tecnologia .NET (di cui Vb.NET è solo un esponente, accanto al più usato C# e agli altr i meno noti, come J#, F#, Delphi per .NET, ecceter a...). Sar ebbe tuttavia r iduttivo descr iver e tale piattafor ma come un semplice agglomer ato di libr er ie (vedi oltr e), quando essa contempla meccanismi assai più complessi, che sovr intendono alla gener ale esecuzione di tutte le applicazioni .NET. L'inter a str uttur a del Fr amew or k si pr esente come str atificata in diver si livelli: 1. Sistema operativo Il Fr amew or k .NET pr esenta una str uttur a str atificata, alla base della quale r isiede il sistema oper ativo, Window s. Più pr ecisamente, si consider a il sistema oper ativo e l'API (Application Pr ogr amming Inter face) di Window s, che espone tutti i metodi r esi disponibili al pr ogr ammator e per svolger e un dato compito. 2. Common Language Runtime Un gr adino più in su c'è il Common Language Runtime (CLR), r esponsabile dei ser vizi basilar i del Fr amew or k, quali la gestione della memor ia e la sua liber azione tr amite il meccanismo di Gar bage Collection (vedi capitolo r elativo), la gestione str uttur ata delle eccezioni (er r or i) e il multithr eading. Nessuna applicazione inter agisce mai dir ettamente con il CLR, ma tutte sono allo stesso modo contr ollate da esso, come se fosse il lor o super visor e. Pr opr io per questo si definisce il codice .NET M an aged o Safe ("Gestito" o "Sicur o"), poichè questo str ato del Fr amew or k gar antisce che non vengano mai eseguite istr uzioni dannose che possano mandar e in cr ash il pr ogr amma o il sistema oper ativo stesso. Al contr ar io, il codice Unmanaged o Unsafe può eseguir e oper azioni r ischiose per il computer : sor genti pr odotti in Vb6 o C++ possono pr odur r e tale tipo di codice. 3. Base Class Library Lo str ato successivo è denominato Base Class Libr ar y (BCL): questa par te contiene tutti i tipi e le classi disponibili nel Fr amew or k (il che cor r isponde in numer o a diver se migliaia di elementi), r aggr uppati in una tr entina di file pr incipali (assembly). In questi ultimi è compr esa la definizione della classe System.Object, dalla quale der iva pr essochè ogni altr a classe. I dati contenuti nella BCL per mettono di svolger e ogni oper azione possibile sulla macchina. 4. X ML Successivamente tr oviamo i dati, le r isor se. Per salvar e i dati viene usato quasi sempr e il for mato XM L (eXtensible Mar kup Language), che utilizza dei tag spesso nidificati per contener e i campi necessar i. La str uttur a di questo tipo di file, inoltr e, è adatta alla r appr esentazione ger ar chica, un metodo che nell'ambiente .net è impor tantissimo. I file di configur azione e quelli delle opzioni impostate dell'utente, ad esempio, vengono salvati in for mato XML. Anche la nuova tecnologia denominata Window s Pr esentation Foundation (WPF), intr odotta nella ver sione 3.5 del Fr amew or k, che per mette di cr ear e contr olli dalla gr afica accattivante e str avagante, si basa su un linguaggio di contr assegno (di mar kup) sur r ogato dell'XML.
  • 7. 5. W indow s Forms e ASP.NET Al livello super ior e tr oviamo ASP.NET e Window s For ms, ossia le inter facce gr afiche che r icopr ono il codice dell'applicazione ver a e pr opr ia. La pr ima è una tecnologia pensata per lo sviluppo sul Web, mentr e la seconda for nisce sostanzialmente la possibilità di cr ear e una inter faccia gr afica (Gr aphical User Inter face, GUI) in tutto e per tutto uguale a quella classica, a finestr e, dei sistemi oper ativi Window s. La costr uzione di una Window s For m (ossia una singola finestr a) è semplice e avviene come nel Vb classico, e chi sta leggendo questa guida per passar e dal VB6 al VB.NET lo sapr à bene: si pr endono uno o più contr olli e li si tr ascinano sulla super ficie della finestr a, dopodichè si scr ive il codice associato ad ognuno dei lor o eventi. 6. Common Language Spec ific ations Il penultimo stadio della str atificazione del Fr amew or k coincide con le Common Language Specifications (CLS), ossia un insieme di specifiche che definiscono i r equisiti minimi r ichiesti a un linguaggio di pr ogr ammazione per esser e qualificato come .NET. Un esempio di tali dir ettive: il linguaggio deve saper e gestir e tipi base come str inghe e numer i inter i, vettor i e collezioni a base zer o e deve saper pr ocessar e un'eccezione scatenata dal Fr amew or k. 7. Linguaggi .NET In cima alla str uttur a ci sono tutti i linguaggi .net: Vb, C#, J#, ecceter a. V ersioni del Framew ork Con il passar e degli anni, a par tir e dal 2002, Micr osoft ha r ilasciato ver sioni successive del Fr amew or k .NET e ognuna di queste r elease ha intr odotto nuovi concetti di pr ogr ammazione e nuove possibilità per lo sviluppator e. Par allelamente all'uscita di queste nuove ver sioni, sono state cr eate anche edizioni successive del linguaggio VB.NET, ognuna delle quali è stata natur almente accostata alla ver sione del Fr amew or k su cui si r eggeva. Ecco una r apida panor amica dell'evoluzione del linguaggio: VB2002: si basa sulla ver sione 1.0 del Fr amew or k VB2003: si basa sulla ver sione 1.1 del Fr amew or k VB2005: si basa sulla ver sione 2.0 del Fr amew or k. Questa è la ver sione maggior mente utilizzata in questa guida, sebbene cer ti capitoli si concentr er anno sull'intr oduzione di alcuni nuovi aspetti por tati da VB2008 VB2008: si basa sulla ver sione 3.5 del Fr amew or k. La ver sione 3.0 si fondava ancor a sulla 2.0 del CLR e per ciò le modifiche consistevano sostanzialmente nell'aggiunta di alcuni componenti e nell'appor to di diver se miglior ie e cor r ezioni VB2010: si basa sulla ver sione 4.0 del Fr amew or k
  • 8. A4. Utilizzo base dell'IDE IDE? Me lo sono dimentic ato a c asa... Non vi pr eoccupate: se avete seguito tutti i capitoli fino a questo punto, siete già un possesso di un IDE: Visual Basic 2005 (o 2008) Ex pr ess. L'acr onimo IDE significa Integr ated Development Envir onment ("ambiente di sviluppo integr ato") ed indica un softw ar e che aiuta il pr ogr ammator e nella stesur a del codice. Il softw ar e che vi ho consigliato for nisce, sebbene sia la ver sione fr ee, un numer o molto alto di str umenti e tools. In pr imis, contiene, ovviamente, un editor di codice sor gente, pr ogettato in modo da evidenziar e in modo differ ente le keyw or ds e da suppor tar e molte funzioni di r icer ca e r aggr uppamento che vedr emo in seguito. Accanto a questo, i pr incipali componenti che non possono mancar e in un IDE sono il compilator e ed il debugger , di cui ho dato una veloce definizione nel capitolo intr oduttivo. Il pr imo ha lo scopo di legger e il sor gente scr itto dal pr ogr ammator e e pr odur r e da questo un eseguibile: i passi che vengono por tati a ter mine dur ante un pr ocesso di compilazione sono in r ealtà più di uno (di solito compilazion e e lin kin g), ma molto spesso si semplifica il tutto par lando semplicemente di compilazione. Il secondo, invece, è il pr ogr amma che vi dar à più filo da tor cer e, anche se in r ealtà sar à il vostr o miglior e aiutante (diciamo che vi sfinir à a fin di bene): il debugger ha la funzione di analizzar e e segnalar e i bugs (bachi, er r or i) che si ver ificano dur ante l'esecuzione; assieme ad un r appor to dettagliato del tipo di er r or e ver ificatosi, segnala par allelamente anche il punto del codice che ha dato pr oblemi, in modo da r ender e molto più semplice individuar e e cor r egger e la falla. Funzionamento del c ompilatore .NET Il compilator e è, come già detto, quel softw ar e necessar io a "tr asfor mar e" il codice sor gente scr itto in un deter minato linguaggio in un pr ogr amma eseguibile. Nor malmente, un compilator e pr odur r ebbe un applicativo tr aducendo le istr uzioni testuali intr odotte dal pr ogr ammator e in linguaggio macchina, ossia una ser ie di bit univocamente inter pr etabile dal pr ocessor e. I compilator i .NET, invece, hanno un compor tamento differ ente, in quanto il lor o output non è un "nor male pr ogr amma" scr itto in linguaggio macchina, ma si tr atta di una ser ie di istr uzioni codificate in un altr o linguaggio speciale, chiamato IL (Inter mediate Language). Come sugger isce il nome, esso si tr ova ad un livello inter medio tr a la macchina e l'astr azione: è super ior e r ispetto al pur o codice binar io, ma allo stesso tempo è un gr adino più sotto r ispetto ai linguaggi .NET. Venendo a conoscenza di queste infor mazioni, dovr ebbe sor ger e spontaneamente una domanda: come fa allor a un pr ogr amma .NET ad esser e eseguito? La r isposta è semplice: è lo stesso Fr amew or k che si occupa di inter pr etar ne le istr uzioni e di eseguir le, sempr e sotto la super visione del CLR. Per questo motivo, si hanno tr e impor tanti conseguenze: Non è possibile far cor r er e un'applicazione .NET su una macchina spr ovvista del Fr amew or k; Il codice .NET è sempr e sicur o; Un pr ogr amma .NET è sempr e disassemblabile: su questo punto mi soffer mer ò in seguito. Creare una Console A pplic ation Nei pr ossimi capitoli inizer ò ad intr odur r e la sintassi del linguaggio, ossia le r egole da r ispettar e quando si scr ive un codice. Per tutti gli esempi della sezione A, far ò uso di applicazioni conso le (avete pr esente la finestr ella con lo sfondo ner o?), che lavor ano in DOS. Per cr ear e una Applicazione Console bisogna selezionar e dal menù File del compilator e, la voce New Pr oject, e quindi sceglier e il tipo di applicazione desider ata. Dopodichè, il compilator e scr iver à aumaticamente alcune r ighe di codice pr eimpostate, che possono esser e simili a queste: Module Module1
  • 9. Sub Main() End Sub End Module Nello scr eenshot pr oposto qui sopr a si possono veder e le tr e ar ee in cui è solitamente divisa l'inter faccia del compilator e: non vi pr eoccupate se la vostr a appar e differ ente, poiché, essendo modificabile a piacimento, la mia potr ebbe esser e diver sa dal layout pr eimpostato del compilator e. Per or a, le finestr e impor tanti sono due: quella del codice, dove andr emo a scr iver e le istr uzioni, e quella degli er r or i, dove potr ete tener e costantemente sott'occhio se avete commesso degli er r or i di sintassi. Nello scr eenshot la seconda di queste non è visibile, ma la si può por tar e in pr imo piano tenendo pr emuto Ctr l e digitando in successione "" ed "E". Per quanto r iguar da il codice che appar e, ho già specificato in pr ecedenza che i moduli sono dei tipi speciali di classe, e fin qui vi baster à saper e questo. Quello che potr este non conoscer e è la par te di sor gente in cui appaiono le par ole Sub ed End Sub: anche in questo caso, la tr attazione par ticolar e di queste keyw or ds sar à r imandata più in là. Per or a possiamo consider ar e la Sub Main() come il pr ogr amma inter o: ogni cosa che viene scr itta tr a "Sub Main()" ed "End Sub" ver r à eseguita quando si pr emer à il pulsante Star t (il tr iangolino ver de in alto sulla bar r a degli str umenti), o in alter nativa F5. Compilazione del programma finito Una volta finito di scr iver e il codice e di testar lo usando le funzioni dell'IDE (ivi compr esa l'esecuzione in modalità debug pr emendo F5), sar à necessar io cr ear e il pr ogr amma finito. Quello che avete eseguito fin'or a non er a altr o che una ver sione più lenta e meno ottimizzata del softw ar e scr itto, poiché c'er a bisogno di contr ollar e tutti gli er r or i e i bugs, impiegando tempo e spazio per memor izzar e le infor mazioni r elative al debug, appunto. Per cr ear e l'applicazione r eale finita, è necessar io compilar e il codice in modalità r elease. Apr ite la scheda delle pr opr ietà di pr ogetto, dal menù pr incipale Pr oject > [NomePr ogetto] Pr oper ties (l'ultima voce del sottomenù); selezionate la scheda Compile e cambiate il campo Configur ation su Release, quindi pr emete Build > Build Pr oject (Build è sempr e una voce del menù pr incipale).
  • 10. Tr over ete l'eseguibile compilato nella car tella DocumentiVisual Studio 2008Pr ojects[Nome pr ogetto]binRelease.
  • 11. A5. Variabili e costanti Le variabili Una var iabile è uno spazio di memor ia RAM (Random Access Memor y) in cui vengono allocati dei dati dal pr ogr amma, ed è possibile modificar ne od ottener ne il valor e facendo r ifer imento ad un nome che si definisce ar bitr ar iamente. Questo nome si dice anche iden tificatore (o, più r ar amente, mn emon ico), e può esser e costituito da un qualunque insieme di car atter i alfanumer ici e under scor e: l'unica condizione da r ispettar e per cr ear e un nome valido è che questo non può iniziar e con un numer o. Per esempio "Pippo", "_Pluto", "Mar io78" o anche "_12345" sono identificator i validi, mentr e "0Luigi" non lo è. Il pr incipale scopo di una var iabile è contener e dati utili al pr ogr amma; tali dati possono r isieder e in memor ia per un tempo più o meno lungo, a seconda di quando una var iabile viene cr eata o distr utta: ogni var iabile, comunque, cessa di esister e nel momento in cui il pr ogr amma viene chiuso. Essa, inoltr e, può contener e una gr andissima var ità di tipi di dato diver si: dai numer i alle str inghe (testo), dalle date ai valor i booleani, per allar gar si poi a tipi più ampi, in gr ado di r appr esentar e un inter o file. Ma pr ima di ar r ivar e a spiegar e tutto questo, bisogna analizzar e in che modo si dichiar a una var iabile. La dichiar azione, tanto di una costante quanto di una classe, è l'atto definitivo con cui si stabilisce l'esistenza di un'entità e la si r ende disponibile o accessibile alle altr i par ti del pr ogr amma. Ogni cosa, per esser e usata, deve pr ima esser e dichiar ata da qualche par te: questa oper azione equivale, ad esempio, a definir e un concetto in matematica: la definizione è impor tantissima. Ecco un semplice esempio: 01. Module Module1 02. Sub Main() 03. Dim Ciao As Int16 04. Ciao = 78 05. Ciao = Ciao + 2 06. Console.WriteLine(Ciao) 07. Console.Readkey() 08. End Sub 09. End Module Facendo cor r er e il pr ogr amma avr emo una scher mata ner a su cui viene visualizzato il numer o 80. Per chè? Or a vediamo. Come avr ete notato, le var iabili si dichiar ano in un modo specifico, usando le keyw or ds Dim e A s: 1. Dim [nome] As [tipo] Dove [nome] è l'identificator e con cui ci si r ifer isce ad una var iabile e [tipo] il tipo di dato contenuto nella var iabile. Esistono molteplici tipi di var iabile fr a cui è possibile sceglier e. Ecco un elenco dei tipi base (che sono consider ati keyw or ds): Byte: inter o a 8 bit che può assumer e valor i da 0 a 255; Char : valor e a 8 bit che può assumer e i valor i di ogni car atter e della tastier a (compr esi quelli speciali); Int16 o Sho r t: inter o a 16 bit che può assumer e valor i da -32768 a +32767; Int32 o Integ er : inter o a 32 bit da -2147483648 a +2147483647; Int64 o Lo ng : inter o a 64 bit da cir ca -922000000000000000 a +9220000000000000000; Sing le: decimale da cir ca -3,4e+38 a +3,4e+38, con un inter vallo minimo di cir ca 1,4e-45; Do uble: decimale da cir ca -1,79e+308 a +1,79e+308, con un inter vallo minimo di cir ca 4,9e-324; Bo o lean: dato a 4 bytes che può assumer e due valor i, Tr ue (ver o) e False (falso). Nonostante la limitatezza del suo campo di azione, che concettualmente potr ebbe r estr inger si ad un solo bit, il tipo Boolean occupa 32bit di memor ia: sono quindi da evitar e gr andi quantità di questo tipo; Str ing : valor e di minimo 10 bytes, composto da una sequenza di car atter i. Se vogliamo, possiamo assimilar lo ad
  • 12. un testo; Object: r appr esenta un qualsiasi tipo (ma non è un tipo base). I tipi base vengono detti anche ato m ici o pr im itiv i, poiché non possono esser e ulter ior mente scomposti. Esistono, quindi, anche tipi der iv ati, appar tenenti a svar iate tipologie che analizzer emo in seguito, fr a cui si annover ano anche le classi: ogni tipo der ivato è scomponibile in un insieme di tipi base. Or a, quindi, possiamo estr apolar e delle infor mazioni in più dal codice pr oposto: dato che segue la keyw or d Dim, "Ciao" è l'identificator e di una var iabile di tipo Int16 (infatti dopo As è stato specificato pr opr io Int16). Questo significa che "Ciao" può contener e solo numer i inter i che, in valor e assoluto, non super ino 32767. Ovviamente, la scelta di un tipo di dato piuttosto che un altr o var ia in funzione del compito che si intende svolger e: maggior e è la pr ecisione e l'or dine di gr andezza dei valor i coinvolti e maggior e sar à anche l'uso di memor ia che si dovr à sostener e. Continuando a legger e, si incontr a, nella r iga successiva, un'assegnazione, ossia una oper azione che pone nella var iabile un cer to valor e, in questo caso 78; l'assegnazione avviene mediante l'uso dell'oper ator e uguale "=". L'istr uzione successiva è simile a questa, ma con una sostanziale differ enza: il valor e assegnato alla var iabile è influenzato dalla var iabile stessa. Nell'esempio pr oposto, il codice: 1. Ciao = Ciao + 2 ha la funzione di incr ementar e di due unità il contenuto di Ciao. Questa istr uzione potr ebbe sembr ar e algebr icamente scor r etta, ma bisogna r icor dar e che si tr atta di un comando (e non di un'equazione): pr ima di scr iver e nella cella di memor ia associata alla var iabile il numer o che il pr ogr ammator e ha designato, il pr ogr amma r isolve l'espr essione a destr a dell'uguale sostituendo ad ogni var iabile il suo valor e, e ottenendo, quindi, 78 + 2 = 80. Le ultime due r ighe, invece, fanno visualizzar e a scher mo il contenuto di Ciao e fer mano il pr ogr amma, in attesa della pr essione di un pulsante. Come si è visto dall'esempio pr ecedente, con le var iabili di tipo numer ico si possono eseguir e oper azioni ar itmetiche. Gli oper ator i messi a disposizione dal Fr amew or k sono: + : addizione; - : sottr azione; * : pr odotto; / : divisione; : divisione tr a inter i (r estituisce come r isultato un numer o inter o a pr escinder e dal tipo degli oper andi, che possono anche esser e decimali); Mod : r estituisce il r esto di una divisione inter a; = : assegna alla var iabile posta a sinistr a dell'uguale il valor e posto dopo l'uguale; & : concatena una str inga con un numer o o una str inga con un'altr a str inga. 01. Module Module1 02. Sub Main() 03. 'Interi 04. Dim Intero, Ese As Int16 05. 'Decimale 06. Dim Decimale As Single 07. 'Booleano 08. Dim Vero As Boolean 09. 'Stringa 10. Dim Frase As String 11. 12. Intero = 90 13. Ese = Intero * 2 / 68 14. Intero = Ese - Intero * Intero 15. Decimale = 90.76 16. Decimale = Ese / Intero 17. Vero = True 18. Frase = "Ciao." 19. 'L'operatore "+" tra stringhe concatena due stringhe. Dopo la 20.
  • 13. 'prossima istruzione, la variabile Frase conterrà: 21. ' "Buon giornoCiao" 22. Frase = "Buon giorno" + "Ciao" 23. 'L'operatore "&" può concatenare qualsiasi dato e 24. 'restituisce una stringa. Dopo la prossima istruzione, la 25. 'variabile Frase conterrà: 26. ' "Il valore decimale è: -0,0003705076" 27. Frase = "Il valore decimale è: " & Decimale 28. End Sub 29. End Module Esistono poi degli speciali oper ator i di assegnamento, che velocizzano l'assegnazione di valor i, alcuni sono: 01. Module Module1 02. Sub Main() 03. Dim V, B As Int32 04. 05. V += B 'Equivale a V = V + B 06. B -= V 'Equivale a B = B - V 07. V *= B 'Equivale a V = V * B 08. B /= V 'Equivale a B = B / V 09. End Sub 10. End Module Le fr asi poste dopo un apice (') sono dette co m m enti e ser vono per spiegar e cosa viene scr itto nel codice. Il contenuto di un commento NON influisce in nessun modo su ciò che è scr itto nel sor gente, ma ha una funzione ESCLUSIVAMENTE esplicativa. Le c ostanti Abbiamo visto che il valor e delle var iabili può esser e modificato a piacimento. Ebbene quello delle costanti, come il nome sugger isce, no. Esistono per semplificar e le oper azioni. Per esempio, invece di digitar e 3,1415926535897932 per il Pi g r eco , è possibile dichiar ar e una costante di nome Pi che abbia quel valor e ed utilizzar la nelle espr essioni. La sintassi per dichiar ar e una costante è la seguente: 1. Const [nome] As [tipo] = [valore] Ci sono due lampanti differ enze r ispetto al codice usato per dichiar ar e una var iabile. La pr ima è, ovviamente, l'uso della keyw or d Con s t al posto di Dim; la seconda consiste nell'assegnazione posta subito dopo la dichiar azione. Infatti, una costante, per esser e tale, dev e contener e qualcosa: per questo motivo è o bblig ato r io specificar e sempr e, dopo la dichiar azione, il valor e che la costante assumer à. Questo valor e non potr à mai esser e modificato. Esempio: 01. Module Module1 02. Sub Main() 03. Const Pi As Single = 3.1415926535897932 04. Dim Raggio, Area As Double 05. 06. 'Questa istruzione scrive sul monitor il messaggio posto tra 07. 'virgolette nelle parentesi 08. Console.WriteLine("Inserire il raggio di un cerchio:") 09. 10. 'Questa istruzione leggè un valore immesso dalla tastiera e 11. 'lo deposita nella variabile Raggio 12. Raggio = Console.ReadLine 13. Area = Raggio * Raggio * Pi 14. 15. Console.WriteLine("L'Area è: " & Area) 16. 17. 'Questa istruzione ferma il programma in attesa della pressione 18. 'di un pulsante 19. Console.ReadKey() 20. End Sub 21. End Module
  • 14. N.B.: a causa della lor o stessa natur a, le costanti NON possono esser e inizializzate con un valor e che dipenda da una funzione. Scr ivo questo appunto per pur a utilità di consultazione: anche se or a potr à non r isultar e chiar o, vi capiter à più avanti di imbatter vi in er r or i del gener e: 1. Const Sqrt2 As Single = Math.Sqrt(2) Sqr t2 dovr ebbe esser e una costante numer ica decimale che contiene la r adice quadr ata di due. Sebbene il codice sembr i cor r etto, il compilator e segnaler à come er r or e l'espr essione Math.Sqr t(2), poiché essa è una funzione, mentr e dopo l'uguale è r ichiesto un valor e sempr e costante. Il codice cor r etto è 1. Const Sqrt2 As Single = 1.4142135 Le istruzioni Tutti i comandi che abbiamo impar tito al computer e che abbiamo gener icamente chiamato con il nome di istr uzioni (come Console.Wr iteLine()) hanno dei nomi più specifici: sono pr o cedur e o funzio ni, in sostanza sottopr ogr ammi già scr itti. Pr ocedur e e funzioni possono esser e globalmente indicate con la par ola m eto do . I metodi accettano dei par am etr i passatigli tr a par entesi: se i par ametr i sono di più di uno vengono separ ati da vir gole. I par ametr i ser vono per comunicar e al metodo i dati sui quali questo dovr à lavor ar e. La differ enza tr a una pr ocedur a e una funzione r isiede nel fatto che la pr ima fa semplicemente eseguir e istr uzioni al computer , mentr e la seconda r estituise un valor e. Ad esempio: 01. Module Module1 02. Sub Main() 03. Dim F As Double 04. 05. 'Questa è una funzione che restituisce la radice quadrata di 56 06. F = Math.Sqrt(56) 07. 08. 'Questa è una procedura che scrive a video un messaggio 09. Console.WriteLine("La radice di 56 è " & F) 10. Console.ReadKey() 11. End Sub 12. End Module Anche i metodi ver r anno tr attai successivamente in dettaglio.
  • 15. A6. Tipi Reference e tipi Value Tutti i tipi di var iabile che possono esser e cr eati si r aggr uppano sotto due gr andi categor ie: Refer ence e Value. I pr imi si compor tano come oggetti, mentr e i secondi r appr esentano tipi scalar i o numer ici, ma vediamo di metter e un po' or dine in tutti questi concetti. P.S.: per una miglior e compr esione di questo capitolo, consiglio solo a chi ha già esper ienza nel campo della pr ogr ammazione (in qualsiasi altr o linguaggio) di legger e questo ar tico lo sull'utilizzo della memor ia da par te di un pr ogr amma. Differenza tra Classi e Oggetti All'inizio della guida mi sono soffer mato ad elogiar e le classi e la lor o enor me impor tanza nell'ambiente .NET. Successivamente ho fatto menzione al tipo System.Object e al fatto che ogni cosa sia un oggetto. La differ enza tr a o g g etto e classe è di vitale impor tanza per capir e come vanno le cose nell'ambito della pr ogr ammazione OO. Una classe r appr esenta l'astr azione di qualcosa di concr eto; un oggetto, invece, è qualcosa di concr eto e viene r appr esentato da una classe. Per far e un esempio banale, sappiamo benissimo che esiste il concetto di "uomo", ma ogni individuo sul pianeta, pur mantenendo alcune car atter istiche simili e costanti, è differ ente r ispetto agli altr i. Facendo un par allelismo con la pr ogr ammazione, quindi, il singolo individuo, ad esempio io stesso, è un oggetto, mentr e il gener ale concetto di "uomo" che ognuno di noi conosce è la classe. Se qualcuno dei lettor i ha studiato filosofia, r iconoscer à in questa differ enza la stessa che Platone identificava nella discr epanza tr a mondo sensibile e Iper ur anio. Avendo ben chiar i questi concetti, si può or a intr odur r e un po' di ger go tecnico. Ogni oggetto è anche detto istanza della classe che lo r appr esenta (voi siete istanze della classe Uomo XD) e is tan ziare un oggetto significa cr ear lo. 1. 'New serve per creare fisicamente degli oggetti in memoria 2. Dim O1 As New Object 3. Dim O2 As New Object O1 e O2 sono entr ambe istanze della classe Object, ma sono diver si fr a di lor o: in comune hanno solo l'appar tenenza allo stesso tipo. N.B.: come si è notato, "tipo" e "classe" sono ter mini spesso equivalenti, ma non gener alizzate questa associazione. Tipi Referenc e Ogni cosa nel Fr amew or k è un oggetto e la maggior par te di essi sono tipi r efer ence. Si dicono tipi r efer ence tutti quei tipi che der ivano dir ettamente dalla classe System.Object (la "der ivazione" appar tiene a un concetto che spiegher ò più avanti): questa classe è dichiar ata all'inter no di una libr er ia della Base Class Libr ar y, ossia l'ar chivio di classi del Fr amew or k. Nel capitolo pr ecedente si è visto come sia possibile assegnar e un valor e ad una var iabile utilizzando l'oper ator e uguale "=". Con questo meccanismo, un deter minato valor e viene depositato nella casella di memor ia che la var iabile occupa. Ebbene, facendo uso dei tipi r efer ence, questo non avviene. Quando si utilizza l'uguale per assegnar e un valor e a tali var iabili, quello che effettivamente viene r iposto nella lor o par te di memor ia è un puntator e inter o a 32bit (su sistemi oper ativi a 32bit). Per chi non lo sapesse, un puntator e è una speciale var iabile che, invece di contener e un pr opr io valor e, contiene l'indir izzo di un'ar ea di memor ia contenente altr i dati. Il puntator e viene memor izzato come al solito sullo stack , mentr e il ver o oggetto viene cr eato e deposto in un'ar ea di memor ia differ ente, detta heap m anag ed, dove esiste sotto la super visione del CLR. Quando una var iabile di questo tipo viene impostata a Nothing (una costante che vedr emo tr a poco), la par te dell'heap managed che l'oggetto occupa viene r ilasciata dur ante il pr ocesso di g ar bag e co llectio n ("r accolta dei r ifiuti"). Tuttavia, ciò non avviene subito, poichè il meccanismo del Fr amew or k fa in modo di avviar e la gar bage collection solo quando è necessar io, quindi quando la
  • 16. memor ia comincia a scar seggiar e: supponendo che un pr ogr amma abbia r elativamente pochi oggetti, questi potr ebber o "viver e" indistur bati fino alla fine del pr ogr amma anche dopo esser e stati lo g icam ente distr utti, il che significa che è stato eliminato manualmente qualsiasi r ifer imento ad essi (vedi par agr afo successivo). Data l'impossibilità di deter minar e a pr ior i quando un oggetto ver r à distr utto, si ha un fenomeno che va sotto il nome di finalizzazio ne no n deter m inistica (il ter mine "finalizzazione" non è casule: veder e il capitolo sui distr uttor i per maggior i infor mazioni). Nothing Nothing è una costante di tipo r efer ence che r appr esenta l'assenza di un oggetto piuttosto che un oggetto nullo. Infatti, por r e una var iabile oggetto uguale a Nothing equivale a distr ugger la logicamente. 1. Dim O As New Object 'L'oggetto viene creato 2. O = Nothing 'L'oggetto viene logicamente distrutto La distr uzione logica non coincide con la distr uzione fisica dell'oggetto (ossia la sua r imzione dalla memor ia), poiché, come detto pr ima, il pr ocesso di liber azione della memor ia viene avviato solo quando è necessar io. Non è possibile assegnar e Nothing a un tipo value, ma è possibile usar e speciali tipi value che suppor tano tale valor e: per ulter ior i dettagli, veder e "Tipi Nullable". Tipi V alue Ogni tipo v alue der iva dalla classe System.ValueType, che der iva a sua volta da System.Object, ma ne r idefinisce i metodi. Ogni var iabile di questo tipo contiene effettivamente il pr opr io valor e e non un puntator e ad esso. Inoltr e, esse hanno dei vantaggi in ter mini di memor ia e velocità: occupano in gener e meno spazio; data la lor o posizione sullo stack non vi è bisogno di r efer enziar e un puntator e per ottener e o impostar ne i valor i (r efer enziar e un puntator e significa r ecar si all'indir izzo di memor ia puntato e legger ne il contenuto); non c'è necessità di occupar e spazio nello heap managed: se la var iabile viene distr utta, cessa di esister e all'istante e non si deve attuar e nessuna oper azione di r ilascio delle r isor se. Notar e che non è possibile distr ugger e logicamente una var iabile value, fatta eccezione per cer ti tipi der ivati. Is e = Nel lavor ar e con tipi r efer ence e value bisogna pr estar e molta attenzione a quando si utilizzano gli oper ator i di assegnamento. Come già detto, i r efer ence contengono un puntator e, per ciò se si scr ive questo codice: 1. Dim O1, O2 As Object 2. '... 3. O1 = O2 quello che O2 conter r à non sar à un valor e identico a O1, ma un puntator e alla stessa ar ea di memor ia di O1. Questo pr ovoca un fatto str ano, poichè sia O1 che O2 puntano alla stessa ar ea di memor ia: quindi O1 e O2 so no lo stesso o g g etto , soltanto r ifer ito con nomi difer si. In casi simili, si può utilizzar e l'oper ator e Is per ver ificar e che due var iabili puntino allo stesso oggetto: 1. 'Scrive a schermo se è vero oppure no che 2. 'O1 e O2 sono lo stesso oggetto 3. Console.WriteLine(O1 Is O2) La scr itta che appar ir à sullo scher mo sar à "Tr ue", ossia "Ver o". Utilizzar e Is per compar ar e un oggetto a Nothing equivale a ver ificar e che tale oggetto sia stato distr utto. Questo NON avviene per i tipi value: quando ad un tipo value si assegna un altr o valor e con l'oper ator e =, si passa
  • 17. effettivamente una co pia del valor e. Non è possibile utilizzar e Is con i tipi value poichè Is è definito solo per i r efer ence. Boxing e Unboxing Consider iamo il seguente codice: 1. Dim I As Int32 = 50 2. Dim O As Object 3. O = I I è un tipo value, mentr e O è un tipo r efer ence. Quello che succede dietr o le quinte è semplice: il .NET cr ea un nuovo oggetto, per ciò un tipo r efer ence, con il r ispettivo puntator e, e quindi gli assegna il valor e di I: quando il pr ocesso è finito assegna il puntator e al nuovo oggetto a O. Questa conver sione spr eca tempo e spazio nello heap managed e viene definita come bo xing . L'oper azione inver sa è l'unboxing e consiste nell'assegnar e un tipo r efer ence a un tipo value. Le oper azioni che si svolgono sono le stesse, ma al contr ar io: entr ambe spr ecano tempo e cpu, quindi sono da evitar e se non str ettamente necessar ie. Quando si può sceglier e, quindi, sono meglio di tipi value. Una pr ecisazione: in tutti i pr ossimi capitoli capiter à fr equentemente che io dica cose del tipo "la var iabile X è un oggetto di tipo Str ing" oppur e "le due var iabili sono lo stesso oggetto". Si tr atta solo di una via più br eve per evitar e il for malismo tecnico, poiché, se una var iabile è dichiar ata di tipo r efer ence, essa è pr opr iamente un riferimen to all'oggetto e non un oggetto. Gli oggetti "vivono" indistur bati nell'heap managed, quel magico posto che nessuno conosce: noi possiamo solo usar e r ifer imenti a tali oggetti, ossia possiamo solo indicar li ("eccolo là! guar da! l'hai visto? ma sì, pr opr io là! non lo vedi?").
  • 18. A7. Il costrutto If Capita spessissimo di dover eseguir e un contr ollo per ver ificar e se vigono cer te condizioni. È possibile attuar e tale oper azione tr amite un co str utto di co ntr o llo , la cui for ma più comune e diffusa è il costr utto If. Questo per mette di contr ollar e se una condizione è ver a. Ad esempio: in un pr ogr amma che calcoli l'ar ea di un quadr ato si deve impor r e di visualizzar e un messaggio di er r or e nel caso l'utente immetta una misur a negativa, poichè, come è noto, non esistono lati la cui misur a è un numer o negativo: 01. Module Module1 02. Sub Main() 03. Dim Lato As Single 04. 05. Console.WriteLine("Inserire il lato di un quadrato:") 06. Lato = Console.ReadLine 07. 08. If Lato < 0 Then 'Se Lato è minore di 0... 09. Console.WriteLine("Il lato non può avere una misura negativa!") 10. Else 'Altrimenti, se non lo è... 11. Console.WriteLine("L'area del quadrato è: " & Lato * Lato) 12. End If 'Fine controllo 13. 14. Console.ReadKey() 15. End Sub 16. End Module Come sicur amente avr ete intuito, questo contr ollo si può associar e al costr utto italiano "Se avviene qualcosa Allor a fai questo Altr imenti fai quell'altr o". Si può eseguir e qualsiasi tipo di compar azione tr a If e Then utilizzando i seguenti oper ator i di confr onto: > : maggior e < : minor e = : uguaglianza <> : diver so >= : maggior e o uguale <= : minor e o uguale Is : identicità (solo per tipi r efer ence) IsNot : negazione di Is (solo per tipi r efer ence) Ma l'impor tante è r icor dar si di attener si a questa sintassi: 1. If [Condizione] Then 2. [istruzioni] 3. Else 4. [istruzioni alternative] 5. End If If nidific ati Quando si tr ova un costr utto If all'inter no di un altr o costr utto If, si dice che si tr atta di un Co str utto If Nidificato . Questo avviene abbastanza spesso, specie se si ha bisogno di far e contr olli multipli: 01. Module Module1 02. Sub Main() 03. Dim Numero As Int16 04. 05.
  • 19. Console.WriteLine("Inserisci un numero:") 06. Numero = Console.ReadLine 07. 08. If Numero > 0 Then 09. If Numero < 5 Then 10. Console.WriteLine("Hai indovnato il numero!") 11. End If 12. Else 13. Console.WriteLine("Numero errato!") 14. End If 15. 16. Console.ReadKey() 17. End Sub 18. End Module Se il numer o inser ito da tastier a è compr eso fr a 0 e 5, estr emi esclusi, allor a l'utente ha indovinato il numer o, altr imenti no. Si può tr ovar e un numer o illimitato di If nidificati, ma è meglio limitar ne l'uso e, piuttosto, far e utilizzo di co nnettiv i lo g ici. I c onnettivi logic i I connettivi logici sono 4: And, Or , Xor e Not. Ser vono per costr uir e contr olli complessi. Di seguito un'illustr azione del lor o funzionamento: If A And B : la condizione r isulta ver ificata se sia A che B sono ver e co ntem po r aneam e nte If A Or B : la condizione r isulta ver ificata se è ver a alm eno una delle due condizioni If A Xor B: la condizione r isulta ver a se una so la delle due condizioni è ver a If Not A: la condizione r isulta ver ificata se è falsa Un esempio pr atico: 01. Module Module1 02. Sub Main() 03. Dim a, b As Double 04. 05. Console.WriteLine("Inserire i lati di un rettangolo:") 06. a = Console.ReadLine 07. b = Console.ReadLine 08. 09. 'Se tutti e due i lati sono maggiori di 0 10. If a > 0 And b > 0 Then 11. Console.WriteLine("L'area è: " & a * b) 12. Else 13. Console.WriteLine("Non esistono lati con misure negative!") 14. End If 15. Console.Readkey() 16. End Sub 17. End Module Continuare il c ontrollo: ElseIf Nei pr ecedenti esempi, la seconda par te del costr utto è sempr e stata Els e, una par ola r iser vata che indica cosa far e se n on si ver ifica la condizione pr oposta dalla pr ima par te. Il suo valor e è, quindi, di pur a alter nativa. Esiste, tuttavia, una var iante di Else che consente di continuar e con un altr o contr ollo senza dover r icor r er e ad If nidificati (a cui è sempr e meglio supplir e con qualcosa di più or dinato). Ammettiamo, ad esempio, di aver e un codice 'autocr itico' simile: 01. Module Module1 02. Sub Main() 03. Dim Voto As Single 04. 05. Console.WriteLine("Inserisci il tuo voto:") 06.
  • 20. Voto = Console.ReadLine 07. 08. If Voto < 3 Then 09. Console.WriteLine("Sei senza speranze!") 10. Else 11. If Voto < 5 Then 12. Console.WriteLine("Ancora un piccolo sforzo...") 13. Else 14. If Voto < 7 Then 15. Console.WriteLine("Stai andando discretamente") 16. Else 17. If Voto < 9 Then 18. Console.WriteLine("Molto bene, continua così") 19. Else 20. Console.WriteLine("Sei praticamente perfetto!") 21. End If 22. End If 23. End If 24. End If 25. 26. Console.ReadKey() 27. End Sub 28. End Module E' abbastanza disor dinato... La var iante ElseIf è molto utile per miglior e la leggibilità del codice: 01. Module Module1 02. Sub Main() 03. Dim Voto As Single 04. 05. Console.WriteLine("Inserisci il tuo voto:") 06. Voto = Console.ReadLine 07. 08. If Voto < 3 Then 09. Console.WriteLine("Sei senza speranze!") 10. ElseIf Voto < 5 Then 11. Console.WriteLine("Ancora un piccolo sforzo...") 12. ElseIf Voto < 7 Then 13. Console.WriteLine("Stai andando discretamente") 14. ElseIf Voto < 9 Then 15. Console.WriteLine("Molto bene, continua così") 16. Else 17. Console.WriteLine("Sei praticamente perfetto!") 18. End If 19. 20. Console.ReadKey() 21. End Sub 22. End Module Notate che tutti gli ElseIf fanno par te dello s tes s o costr utto: mentr e nell'esempio ogni If nidificato er a un blocco a sé stante, dotato infatti di un pr opr io End If, in questo caso ogni alter nativa-selettiva fa comunque par te dell'unico If iniziale, pr otr atto solamente un poco più a lungo. Bloc c hi di istruzioni Fino a questo punto, gli esempi pr oposti non hanno mai dichiar ato una var iabile dentr o un costr utto If, ma solo all'inizio del pr ogr amma, dopo Sub Main(). È possibile dichiar ar e var iabili in altr i punti del codice che non siano all'inizio della Sub? Cer tamente sì. A differ enza di altr i, i linguaggi .NET per mettono di dichiar ar e var iabili in qualunque punto del sor gente, dove occor r e, evitando un gigantesco agglomer ato di dichiar azioni iniziali, for temente disper sive per chi legge. Questo è un gr ande vantaggio, ma bisogna far e attenzione ai blocchi di codice. Con questo ter mine ci si r ifer isce a par ti del sor gente compr ese tr a due par ole r iser vate, che in VB di solito sono accoppiate in questo modo: 1. [Keyword] 2. 'Blocco di codice 3. End [Keyword]
  • 21. Ad esempio, tutto il codice compr eso tr a Sub ed End Sub costituisce un blocco, così come lo costituisce quello compr eso tr a If ed End If (se non vi è un Else), tr a If ed Else o addir ttur a tr a Module ed End Module. Facendo questa distinzione sar à facile intuir e che una var iabile dichiar ata in un blocco no n è v isibile al di fuor i di esso. Con questo voglio dir e che la sua dichiar azione vale solo all'inter no di quel blocco. Ecco una dimostr azione: 01. Module Module1 02. Sub Main() 03. 'a, b e c fanno parte del blocco delimitato da Sub ... 04. 'End Sub 05. Dim a, b, c As Single 06. 07. 'Semplice esempio di risoluzione di equazione di 08. 'secondo grado 09. Console.WriteLine("Equazione: ax2 + bx + c = 0") 10. Console.WriteLine("Inserisci, in ordine, a, b e c:") 11. a = Console.ReadLine 12. b = Console.ReadLine 13. c = Console.ReadLine 14. 15. If a = 0 Then 16. Console.WriteLine("L'equazione si abbassa di grado") 17. Console.ReadKey() 18. 'Con Exit Sub si esce dalla Sub, che in questo caso 19. 'coincide con il programma. Equivale a terminare 20. 'il programma stesso 21. Exit Sub 22. End If 23. 24. 'Anche delta fa parte del blocco delimitato da Sub ... 25. 'End Sub 26. Dim delta As Single = b ^ 2 - 4 * a * c 27. 28. 'Esistono due soluzioni distinte 29. If delta > 0 Then 30. 'Queste variabili fanno parte del blocco di If ... 31. 'ElseIf 32. Dim x1, x2 As Single 33. 'È possibile accedere senza problemi alla variabile 34. 'delta, poiché questo blocco è a sua volta 35. 'all'interno del blocco in cui è dichiarato delta 36. x1 = (-b + Math.Sqrt(delta)) / (2 * a) 37. x2 = (-b - Math.Sqrt(delta)) / (2 * a) 38. Console.WriteLine("Soluzioni: ") 39. Console.WriteLine("x1 = " & x1) 40. Console.WriteLine("x2 = " & x2) 41. 42. 'Esiste una soluzione doppia 43. ElseIf delta = 0 Then 44. 'Questa variabile fa parte del blocco ElseIf ... Else 45. Dim x As Single 46. x = -b / (2 * a) 47. Console.WriteLine("Soluzione doppia: ") 48. Console.WriteLine("x = " & x) 49. 50. 'Non esistono soluzioni in R 51. Else 52. Console.WriteLine("Non esistono soluzioni in R") 53. End If 54. 55. Console.ReadKey() 56. End Sub 57. End Module Se in questo codice, pr ima del Console.ReadKey(), finale pr ovassimo a usar e una fr a le var iabili x , x 1 o x 2, otter r emmo un er r or e:
  • 22. Questo succede per chè nessuna var iabile dichiar ata all'inter no di un blocco è accessibile al di fuor i di esso. Con questo schemino r udimentale sar à più facile capir e: Le fr ecce ver di indicano che un codice può acceder e a cer te var iabili, mentr e quelle r osse indicano che non vi può acceder e. Come salta subito agli occhi, sono per messe tutte le r ichieste che vanno dall'inter no di un blocco ver so l'ester no, mentr e sono pr oibite tutte quelle che vanno dall'ester no ver so l'inter no. Questa r egola vale sempr e, in qualsiasi cir costanza e per qualsiasi tipo di blocco: non ci sono eccezioni.
  • 23. A8. Il costrutto Select Case Abbiamo visto nel capitolo pr ecedente come si possa far pr ocessar e al computer un contr ollo per ver ificar e cer te condizioni. Supponiamo, or a, di aver e 20 contr olli di uguaglianza del tipo: 01. '... 02. If A = 1 Then 03. 'istruzioni 04. End If 05. If A = 2 Then 06. 'istruzioni 07. End If 08. If A = 3 Then 09. 'istruzioni 10. End If 11. 'eccetera In questo caso il costr utto If diventa non solo noioso, ma anche ingombr ante e disor dinato. Per eseguir e questo tipo di contr olli multipli esiste un costr utto apposito, Select Case, che ha questa sintassi: 01. '... 02. Select Case [Nome variabile da analizzare] 03. Case [valore1] 04. 'istruzioni 05. Case [valore2] 06. 'istruzioni 07. Case [valore3] 08. 'istruzioni 09. End Select Questo tipo di contr ollo r ende molto più linear e, semplice e veloce il codice sor gente. Un esempio: 01. Module Module 1 02. Sub Main() 03. Dim a, b As Double 04. Dim C As Byte 05. 06. Console.WriteLine("Inserire due numeri: ") 07. a = Console.ReadLine 08. b = Console.ReadLine 09. Console.WriteLine("Inserire 1 per calcolare la somma, 2 per la differenza, 3 per il prodotto, 4 per il quoziente:") 10. C = Console.ReadLine 11. 12. Select Case C 13. Case 1 14. Console.WriteLine(a + b) 15. Case 2 16. Console.WriteLine(a - b) 17. Case 3 18. Console.WriteLine(a * b) 19. Case 4 20. Console.WriteLine(a / b) 21. End Select 22. 23. Console.ReadKey() 24. End Sub 25. End Module Molto semplice, ma anche molto efficace, specialmente utile nei pr ogr ammi in cui bisogna consider ar e par ecchi valor i. Anche se nell'esempio ho utilizzato solamente numer i, è possibile consider ar e var iabili di qualsiasi tipo, sia base (str inghe, date), sia der ivato (str uttur e, classi). Ad esempio: 1.
  • 24. Dim S As String 2. '... 3. Select Case S 4. Case "ciao" 5. '... 6. Case "buongiorno" 7. '... 8. End Select V arianti del c ostrutto Anche in questo caso, esistono numer ose var ianti, che per mettono non solo di ver ificar e uguaglianze come nei casi pr ecedenti, ma anche di contr ollar e disuguaglianze e analizzar e insiemi di valor i. Ecco una lista delle possibilità: Uso della v ir g o la La vir gola per mette di definir e non solo uno, ma molti valor i possibili in un solo Case. Ad esempio: 01. Dim A As Int32 02. '... 03. Select Case A 04. Case 1, 2, 3 05. 'Questo codice viene eseguito solo se A 06. 'contiene un valore pari a 1, 2 o 3 07. Case 4, 6, 9 08. 'Questo codice viene eseguito solo se A 09. 'contiene un valore pari a 4, 6 o 9 10. End Select Il codice sopr a pr oposto con Select equivale ad un If scr itto come segue: 1. If A = 1 Or A = 2 Or A = 3 Then 2. '... 3. ElseIf A = 4 Or A = 6 Or A = 9 Then 4. '... 5. End If Uso di To Al contr ar io, la keyw or d To per mette di definir e un ran ge di valor i, ossia un inter vallo di valor i, per il quale la condizione r isulta ver ificata se la var iabile in analisi r icade in tale inter vallo. 1. Select Case A 2. Case 67 To 90 3. 'Questo codice viene eseguito solo se A 4. 'contiene un valore compreso tra 67 e 90 (estremi inclusi) 5. Case 91 To 191 6. 'Questo codice viene eseguito solo se A 7. 'contiene un valore compreso tra 91 e 191 8. End Select Questo cor r isponde ad un If scr itto come segue: 1. If A >= 67 And A <= 90 Then 2. '... 3. ElseIf A >= 91 And A <= 191 Then 4. '... 5. End If Uso di Is Is è usato in questo contesto per ver ificar e delle condizioni facendo uso di nor mali oper ator i di confr onto (meggior e, minor e, diver so, ecceter a...). L'Is usato nel costr utto Select Case non ha assolutamente niente a che veder e con quello usato per ver ificar e l'identicità di due oggetti: ha lo stesso nome, ma la funzione è completamente differ ente. 01.
  • 25. Select Case A 02. Case Is >= 6 03. 'Questo codice viene eseguito solo se A 04. 'contiene un valore maggiore o uguale di 6 05. Case Is > 1 06. 'Questo codice viene eseguito solo se A 07. 'contiene un valore maggiore di 1 (e minore di 6, 08. 'dato che, se si è arrivati a questo Case, 09. 'significa che la condizione del Case precedente non 10. 'è stata soddisfatta) 11. End Select Il suo equivalente If: 1. If A >= 6 Then 2. '... 3. ElseIf A > 1 Then 4. '... 5. End If Uso di Else Anche nel Select è lecito usar e Else: il Case che include questa istr uzione è solitamente l'ultimo di tutte le alter native possibili e pr escr ive di eseguir e il codice che segue solo se tutte le altr e condizioni non sono state soddisfatte: 01. Select Case A 02. Case 1, 4 03. 'Questo codice viene eseguito solo se A 04. 'contiene 1 o 4 05. Case 9 To 12 06. 'Questo codice viene eseguito solo se A 07. 'contiene un valore compreso tra 9 e 12 08. Case Else 09. 'Questo codice viene eseguito solo se A 10. 'contiene un valore minore di 9 o maggiore di 12, 11. 'ma diverso da 1 e 4 12. End Select Uso delle pr ecedenti alter nativ e in co m binazione Tutti i modi illustr ati fino ad or a possono esser e uniti in un solo Case per ottener e potenti condizioni di contr ollo: 1. Select Case A 2. Case 7, 9, 10 To 15, Is >= 90 3. 'Questo codice viene eseguito solo se A 4. 'contiene 7 o 9 o un valore compreso tra 10 e 15 5. 'oppure un valore maggiore o uguale di 90 6. Case Else 7. '... 8. End Select
  • 26. A9. I costrutti iterativi: Do Loop Abbiamo visto che esistono costr utti per ver ificar e condizioni, o anche per ver ificar e in modo semplice e veloce molte ugualiglianze. Or a vedr emo i cicli o costr utti iter ativi (dal latino iter , itiner is = "viaggio", ma anche "per la seconda volta"). Essi hanno il compito di r ipeter e un blocco di istr uzioni un numer o deter minato o indeter minato di volte. Il pr imo che analizzer emo è, appunto, il costr utto Do Loop, di cui esistono molte var ianti. La più semplice è ha questa sintassi: 1. Do 2. 'istruzioni 3. Loop Il suo compito consiste nel r ipete delle istr uzioni compr ese tr a Do e Loop un numer o infinito di volte: l'unico modo per uscir e dal ciclo è usar e una speciale istr uzione: "Ex it Do", la quale ha la capacità di inter r omper e il ciclo all'istante ed uscir e da esso. Questa semplice var iante viene usata in un numer o r idotto di casi, che si possono r icondur r e sostanzialmente a due: quando si lavor a con la gr afica e le libr er ie Dir ectX, per disegnar e a scher mo i costanti cambiamenti del mondo 2D o 3D; quando è necessar io ver ificar e le condizioni di uscita dal ciclo all'inter no del suo blocco di codice. Ecco un esempio di questo secondo caso: 01. Module Module1 02. 03. Sub Main() 04. Dim a, b As Single 05. 06. Do 07. 'Pulisce lo schermo 08. Console.Clear() 09. 'L'underscore serve per andare a capo nel codice 10. Console.WriteLine("Inserire le misure di base e altezza " & _ 11. "di un rettangolo:") 12. a = Console.ReadLine 13. b = Console.ReadLine 14. 15. 'Controlla che a e b non siano nulli. In quel caso, esce 16. 'dal ciclo. Se non ci fosse questo If in mezzo al codice, 17. 'verrebbe scritto a schermo il messaggio: 18. ' "L'area del rettangolo è: 0" 19. 'cosa che noi vogliamo evitare. Se si usasse un'altra 20. 'variante di Do Loop, questo succederebbe sempre. Ecco 21. 'perchè, in questa situazione, è meglio 22. 'servirsi del semplice Do Loop 23. If a = 0 Or b = 0 Then 24. Exit Do 25. End If 26. 27. Console.WriteLine("L'area del rettangolo è: " & (a * b)) 28. Console.ReadKey() 29. Loop 30. End Sub 31. 32. End Module Le altr e ver sioni del costr utto, invece, sono le seguenti: 1. Do 2. 'istruzioni 3. Loop While [condizione] Esegue le istr uzioni specificate fintanto che una condizione r imane valida, ma tutte le istr uzioni vengono eseguite almeno una volta, poichè While si tr ova dopo Do. Esempio:
  • 27. 01. Module Module1 02. Sub Main() 03. Dim a As Int32 = 0 04. 05. Do 06. a += 1 07. Loop While (a < 2) And (a > 0) 08. Console.WriteLine(a) 09. 10. Console.ReadKey() 11. End Sub 12. End Module Il codice scr iver à a scher mo "2". 1. Do While [condizione] 2. 'istruzioni 3. Loop Esegue le istr uzioni specificate fintanto che una condizione r imane valida, ma se la condizione non è valida all'inizio, non viene eseguita nessuna istr uzione nel blocco. Esempio: 01. Module Module1 02. Sub Main() 03. Dim a As Int32 = 0 04. 05. Do While (a < 2) And (a > 0) 06. a += 1 07. Loop 08. Console.WriteLine(a) 09. 10. Console.ReadKey() 11. End Sub 12. End Module Il codice scr iver à a scher mo "0". Bisogna notar e come le stesse condizioni del caso pr ecedente, spostate da dopo Loop a dopo Do, cambino il r isultato di tutto l'algor itmo. In questo caso, il codice nel ciclo non viene neppur e eseguito per chè la condizione nel While diventa subito falsa (in quanto a = 0, e la pr oposizione "a < 0" r isulta falsa). Nel caso pr ecedente, invece, il blocco veniva eseguito almeno una volta poiché la condizione di contr ollo si tr ovava dopo di esso: in quel caso, a er a or mai stato incr ementato di 1 e per ciò soddisfaceva la condizione affinché il ciclo continuasse (fino ad ar r ivar e ad a = 2, che er a il r isultato visualizzato). 1. Do 2. 'istruzioni 3. Loop Until [condizione] Esegue le istr uzioni specificate fino a che non viene ver ificata la condizione, ma tutte le istr uzioni vengono eseguite almeno una volta, poichè Until si tr ova dopo Do. Esempio: 01. Module Module1 02. Sub Main() 03. Dim a As Int32 = 0 04. 05. Do 06. a += 1 07. Loop Until (a <> 1) 08. Console.WriteLine(a) 09. 10. Console.ReadKey() 11. End Sub 12. End Module A scher mo appar ir à "2". 1. Do Until [condizione] 2. 'istruzioni 3.
  • 28. Loop Esegue le istr uzioni specificate fino a che non viene soddisfatta la condizione, ma se la condizione è valida all'inizio, non viene eseguita nessuna istr uzione del blocco. Esempio: 01. Module Module1 02. Sub Main() 03. Dim a As Int32 = 0 04. 05. Do Until (a <> 1) 06. a += 1 07. Loop 08. Console.WriteLine(a) 09. 10. Console.ReadKey() 11. End Sub 12. End Module A scher mo appar ir à "0". Un piccolo esempio finale: 01. Module Module1 02. Sub Main() 03. Dim a, b, c As Int32 04. Dim n As Int32 05. 06. Console.WriteLine("-- Successione di Fibonacci --") 07. Console.WriteLine("Inserire un numero oltre il quale terminare:") 08. n = Console.ReadLine 09. 10. If n = 0 Then 11. Console.WriteLine("Nessun numero della successione") 12. Console.ReadKey() 13. Exit Sub 14. End If 15. 16. a = 1 17. b = 1 18. Console.WriteLine(a) 19. Console.WriteLine(b) 20. Do While c < n 21. c = a + b 22. b = a 23. a = c 24. Console.WriteLine(c) 25. Loop 26. 27. Console.ReadKey() 28. End Sub 29. End Module Suggerimen to Per impostar e il valor e di Default (ossia il valor e pr edefinito) di una var iabile si può usar e questa sintassi: 1. Dim [nome] As [tipo] = [valore] Funziona solo per una var iabile alla volta. Questo tipo di istr uzione si chiama in izializzazion e in -lin e.
  • 29. A10. I costrutti iterativi: For Dopo aver visto costr utti iter ativi che eseguono un ciclo un numer o indeter minato di volte, è ar r ivato il momento di analizzar ne uno che, al contr ar io, esegue un deter minato numer o di iter azioni. La sintassi è la seguente: 1. Dim I As Int32 2. 3. For I = 0 To [numero] 4. 'istruzioni 5. Next La var iabile I, usata in questo esempio, viene definita co ntator e e, ad ogni step, ossia ogni volta che il blocco di istr uzioni si r ipete, viene automaticamente incr ementata di 1, sicchè la si può usar e all'inter no delle istr uzioni come un ver o e pr opr io indice, per r ender e conto del punto al quale l'iter azione del For è ar r ivata. Bisogna far notar e che il tipo usato per la var iabile contator e non deve sempr e esser e Int32, ma può var iar e, spaziando tr a la vasta gamma di numer i inter i, con segno e senza segno, fino anche ai numer i decimali. Un esempio: 01. Module Module1 02. Sub Main() 03. Dim a As Int32 04. 05. 'Scrive 46 volte (da 0 a 45, 0 compreso, sono 46 numeri) 06. 'a schermo 'ciao' 07. For a = 0 To 45 08. Console.WriteLine("ciao") 09. Next 10. 11. Console.ReadKey() 12. End Sub 13. End Module Ovviamente il valor e di par tenza r imane del tutto ar bitr ar io e può esser e deciso ed inizializzato ad un qualsiasi valor e: 01. Module Module1 02. Sub Main() 03. Dim a, b As Int32 04. 05. Console.WriteLine("Inserisci un numero pari") 06. b = Console.ReadLine 07. 08. 'Se b non è pari, ossia se il resto della divisione 09. 'b/2 è diverso da 0 10. If b Mod 2 <> 0 Then 11. 'Lo fa diventare un numero pari, aggiungendo 1 12. b += 1 13. End If 14. 15. 'Scrive tutti i numeri da b a b+20 16. For a = b To b + 20 17. Console.WriteLine(a) 18. Next 19. 20. Console.ReadKey() 21. End Sub 22. End Module Intr oduciamo or a una piccola var iante del pr ogr amma pr ecedente, nella quale si devono scr iver e solo i numer i par i da b a b+20. Esistono due modi per r ealizzar e quanto detto. Il pr imo è abbastanza intuitivo, ma meno r affinato, e consiste nel contr ollar e ad ogni iter azione la par ità del contator e: 1.
  • 30. For a = b To b + 20 2. If a Mod 2 = 0 Then 3. Console.WriteLine(a) 4. End If 5. Next Il secondo, invece, è più elegante e usa una ver sione "ar r icchita" della str uttur a iter ativa For , nella quale viene specificato che l'incr emento del contator e non deve più esser e 1, ma bensì 2: 1. For a = b To b + 20 Step 2 2. Console.WriteLine(a) 3. Next Infatti, la par ola r iser vata Step posta dopo il numer o a cui ar r ivar e (in questo caso b+20) indica di quanto deve esser e aumentata la var iabile contator e del ciclo (in questo caso a) ad ogni step. L'incr emento può esser e un valor e inter o, decimale, positivo o negativo, ma, cosa impor tante, deve sempr e appar tener e al r aggio d'azione del tipo del contator e: ed esempio, non si può dichiar ar e una var iabile contator e di tipo Byte e un incr emento di -1, poichè Byte compr ende solo numer i positivi (invece è possibile far lo con SByte, che va da -127 a 128). Allo stesso modo non si dovr ebber o specificar e incr ementi decimali con contator i inter i. Suggerimen to Se non si vuole cr ear e una var iabile apposta per esser e contator e di un ciclo for , si può inzializzar e dir ettamente una var iabile al suo inter no in questo modo: 1. For [variabile] As [tipo] = [valore] To [numero] 2. 'istruzioni 3. Next 4. 'Che, se volessimo descrivere con un esempio, diverrebbe così: 5. For H As Int16 = 78 To 108 6. 'istruzioni 7. Next
  • 31. A11. Gli Array - Parte I Array a una dimensione Fino a questo momento abbiamo avuto a che far e con var iabili "singole". Con questo voglio dir e che ogni identificator e dichiar ato puntava ad una cella di memor ia dove er a contenuto un solo valor e, leggibile e modificabile usando il nome specificato nella dichiar azione della var iabile. L'esempio classico che si fa in questo contesto è quello della scatola, dove una var iabile viene, appunto, assimilata ad una scatola, il cui contenuto può esser e pr eso, modificato e r eimmesso senza pr oblemi. Allo stesso modo, un ar r ay è un insieme di scatole, tutte una vicina all'altr a (tanto nell'esempio quando nella posizione fisica all'inter no della memor ia), a for mar e un'unica fila che per comodità si indica con un solo nome. Per distinguer e ogni "scompar to" si fa uso di un numer o inter o (che per convenzione è un inter o a 32 bit, ossia Integer ), detto indice. Tutti i linguaggi .NET utilizzano sempr e un indice a base 0: ciò significa che si inizia a contar e da 0 anziché da 1: La sintassi usata per dichiar ar e un ar r ay è simile a quella usata per dichiar ar e una singola var iabile: 1. Dim [nome]([numero elementi - 1]) As [tipo] La differ enza tr a le due r isiede nelle par entesi tonde che vengono poste dopo il nome della var iabile. Tr a queste
  • 32. par entesi può anche esser e specificato un numer o (sempr e inter o, ovviamente) che indica l'indice massimo a cui si può ar r ivar e: dato che, come abbiamo visto, gli indici sono sempr e a base 0, il numer o effettivo di elementi pr esenti nella collezione sar à di un'unità super ior e r ispetto all'indice massimo. Ad esempio, con questo codice: 1. Dim A(5) As String il pr ogr ammator e indica al pr ogr amma che la var iabile A è un ar r ay contenente questi elementi: 1. A(0), A(1), A(2), A(3), A(4), A(5) che sono per la pr ecisione 6 elementi. Ecco un listato che esemplifica i concetti fin'or a chiar iti: 01. Module Module1 02. Sub Main() 03. 'Array che contiene 10 valori decimali, rappresentanti voti 04. Dim Marks(9) As Single 05. 'Questa variabile terrà traccia di quanti voti 06. 'l'utente avrà immesso da tastiera e permetterà di 07. 'calcolarne una media 08. Dim Index As Int32 = 0 09. 10. 'Mark conterrà il valore temporaneo immesso 11. 'da tastiera dall'utente 12. Dim Mark As Single 13. Console.WriteLine("Inserisci un altro voto (0 per terminare):") 14. Mark = Console.ReadLine 15. 16. 'Il ciclo finisce quando l'utente immette 0 oppure quando 17. 'si è raggiunto l'indice massimo che è 18. 'possibile usare per identificare una cella dell'array 19. Do While (Mark > 0) And (Index < 10) 20. 'Se il voto immesso è maggiore di 0, lo memorizza 21. 'nell'array e incrementa l'indice di 1, così da 22. 'poter immagazzinare correttamente il prossimo voto nell'array 23. Marks(Index) = Mark 24. Index += 1 25. 26. Console.WriteLine("Inserisci un altro voto (0 per terminare):") 27. Mark = Console.ReadLine 28. Loop 29. 'Decrementa l'indice di 1, poiché anche se l'utente 30. 'ha immesso 0, nel ciclo precedente, l'indice era stato 31. 'incrementato prevedendo un'ulteriore immissione, che, 32. 'invece, non c'è stata 33. Index -= 1 34. 35. 'Totale dei voti 36. Dim Total As Single = 0 37. 'Usa un ciclo For per scorrere tutte le celle dell'array 38. 'e sommarne i valori 39. For I As Int32 = 0 To Index 40. Total += Marks(I) 41. Next 42. 43. 'Mostra la media 44. Console.WriteLine("La tua media è: " & (Total / (Index + 1))) 45. Console.ReadKey() 46. End Sub 47. End Module Il codice potr ebbe non appar ir e subito chiar o a pr ima vista, ma attr aver so uno sguar do più attento, tutto si far à più limpido. Di seguito è scr itto il flusso di elabor azione del pr ogr amma ammettendo che l'utente immetta due voti: Richiede un voto da tastier a: l'utente immette 5 (Mar k = 5) Mar k è maggior e di 0 Inser isce il voto nell'ar r ay: Mar ks(Index ) = Mar ks(0) = 5 Incr ementa Index di 1: Index = 1
  • 33. Entr ambe le condizioni non sono ver ificate: Mar k <> 0 e Index < 9. Il ciclo continua Richiede un voto da tastier a: l'utente immette 10 (Mar k = 10) Mar k è maggior e di 0 Inser isce il voto nell'ar r ay: Mar ks(Index ) = Mar ks(1) = 10 Incr ementa Index di 1: Index = 2 Entr ambe le condizioni non sono ver ificate: Mar k <> 0 e Index < 9. Il ciclo continua Richiede un voto da tastier a: l'utente immette 0 (Mar k = 0) Mar k è uguale a 0: il codice dentr o if non viene eseguito Una delle condizioni di ar r esto è ver ificata: Mar k = 0. Il ciclo ter mina Decr ementa Index di 1: Index = 1 Somma tutti i valor i in Mar ks da 0 a Index (=1): Total = Mar ks(0) + Mar ks(1) = 5 + 10 Visualizza la media: Total / (Index + 1) = 15 / (1 + 1) = 15 / 2 = 7.5 Attende la pr essione di un tasto per uscir e È anche possibile dichiar ar e ed inizializzar e (ossia r iempir e) un ar r ay in una sola r iga di codice. La sintassi usata è la seguente: 1. Dim [nome]() As [tipo] = {elementi dell'array separati da virgole} Ad esempio: 1. Dim Parole() As String = {"ciao", "mouse", "penna"} Questa sintassi br eve equivale a questo codice: 1. Dim Parole(2) As String 2. Parole(0) = "ciao" 3. Parole(1) = "mouse" 4. Parole(2) = "penna" Un'ulter ior e sintassi usata per dichiar ar e un ar r ay è la seguente: 1. Dim [nome] As [tipo]() Quest'ultima, come vedr emo, sar à par ticolar mente utile nel gestir e il tipo r estituito da una funzione. Array a più dimensioni Gli ar r ay a una dimensione sono contr addistinti da un singolo indice: se volessimo par agonar li ad un ente geometr ico, sar ebber o assimilabili ad una r etta, estesa in una sola dimensione, in cui ogni punto r appr esenta una cella dell'ar r ay. Gli ar r ay a più dimensioni, invece, sono contr addistinti da più di un indice: il numer o di indici che identifica univocamente un elemento dell'ar r ay di dice r ang o . Un ar r ay di r ango 2 (a 2 dimensioni) potr à, quindi, esser e par agonato a un piano, o ad una gr iglia di scatole estesa in lunghezza e in lar ghezza. La sintassi usata è: 1. Dim [nome]( , ) As [tipo] 'array di rango 2 2. Dim [nome]( , , ) As [tipo] 'array di rango 3 Ecco un esempio che consider a un ar r ay di r ango 2 come una matr ice quadr ata: 01. Module Module1 02. Sub Main() 03. 'Dichiara e inizializza un array di rango 2. Dato che 04. 'in questo caso abbiamo due dimensioni, e non una sola, 05. 'non si può specificare una semplice lista di 06. 'valori, ma una specie di "tabella" a due entrate. 07. 'Nell'esempio che segue, ho creato una semplice 08. 'tabella a due righe e due colonne, in cui ogni cella 09. 'è 0. 10.
  • 34. Dim M(,) As Single = _ 11. {{0, 0}, _ 12. {0, 0}} 13. 'Bisogna notare il particolare uso delle graffe: si 14. 'considera l'array di rango 2 come un array i cui 15. 'elementi sono altri array 16. 17. Console.WriteLine("Inserire gli elementi della matrice:") 18. For I As Int32 = 0 To 1 19. For J As Int32 = 0 To 1 20. Console.Write("Inserire l'elemento (" & I & ", " & J & "): ") 21. M(I, J) = Console.ReadLine 22. Next 23. Next 24. 25. Dim Det As Single 26. Det = M(0, 0) * M(1, 1) - M(0, 1) * M(1, 0) 27. Console.WriteLine("Il determinante della matrice è: " & Det) 28. 29. Console.ReadKey() 30. End Sub 31. End Module Rappr esentando gr aficamente l'ar r ay M, potr emmo disegnar lo così: Ma il computer lo può anche veder e in questo modo, come un ar r ay di ar r ay:
  • 35. Come si vede dal codice di inizializzazione, seppur concettualmente diver si, i due modi di veder e un ar r ay sono compatibili. Tuttavia, bisogna chiar ir e che so lo e so ltanto in questo caso, le due visioni sono conciliabili, poiché un ar r ay di r ango 2 e un ar r ay di ar r ay sono, dopo tutto, due entità ben distinte. Infatti, esiste un modo per dichiar ar e ar r ay di ar r ay, come segue: 1. Dim [nome]()() As [tipo] 'array di array E se si pr ova a far e una cosa del gener e: 1. Dim A(,) As Int32 2. Dim B()() As Int32 3. '... 4. A = B Si r iceve un er r or e esplicito da par te del compilator e. Ridimensionare un array Può capitar e di dover modificar e la lunghezza di un ar r ay r ispetto alla dichiar azione iniziale. Per far e questo, si usa la par ola r iser vata ReDim, da non confonder e con la keyw or d Dim: hanno due funzioni totalmente differ enti. Quando si r idimensiona un ar r ay, tutto il suo contenuto viene cancellato: per evitar e questo inconveniente, si deve usar e l'istr uzione ReDim Pres erve, che tuttavia ha pr estazioni molto scar se a causa dell'eccessiva lentezza. Entr ambe le istr uzioni der ivano dal Visual Basic classico e non fanno par te, per tanto, della sintassi .NET, sebbene continuino ad esser e molto usate, sia per comodità, sia per abitudine. Il metodo più cor r etto da adottar e consiste nell'usar e la pr ocedur a Ar r ay.Resize. Eccone un esempio: 01. Module Module1 02. Sub Main() 03. Dim A() As Int32 04. Dim n As Int32 05. 06. Console.WriteLine("Inserisci un numero") 07. n = Console.ReadLine 08. 09. 'Reimposta la lunghezza di a ad n elementi 10. Array.Resize(A, n) 11. 12. 'Calcola e memorizza i primi n numeri pari (zero compreso) 13. For I As Int16 = 0 To n - 1 14. A(I) = I * 2 15. Next 16. 17. Console.ReadKey() 18. End Sub 19. End Module La r iga Ar r ay.Resize(A, n) equivale, usando ReDim a: 1. ReDim A(n - 1) Per r idimensionar e un ar r ay a più dimensioni, la faccenda si fa abbastanza complessa. Per pr ima cosa, non si può utilizzar e Ar r ay.Resize a meno che non si utilizzi un ar r ay di ar r ay, ma anche in quel caso le cose non sono semplici. Infatti, è possibile stabilir e la lunghezza di una sola dimensione alla volta. Ad esempio, avendo un ar r ay M di r ango 2 con nove elementi, r aggr uppati in 3 r ighe e 3 colonne, non si può semplicemente scr iver e: 1. ReDim M(2, 2)
  • 36. per chè, così facendo, solo la r iga 2 ver r à r idimensionata a 3 elementi, mentr e la 0 e la 1 sar anno vuote. Il codice da usar e, quindi, è: 1. ReDim M(0, 2) 2. ReDim M(1, 2) 3. ReDim M(2, 2) In questo modo, ogni "r iga" viene aggiustata alla lunghezza giusta.
  • 37. A12. Gli Array - Parte II Il c ostrutto iterativo For Eac h Questo costr utto iter ativo è simile al nor male For , ma, invece di aver e una var iabile contator e numer ica, ha una var iabile contator e di var io tipo. In sostanza, questo ciclo iter a attr aver so una ar r ay o una collezione di altr o gener e, selezionando, di volta in volta, l'elemento che si tr ova alla posizione cor r ente nell'ar r ay. Il suo funzionamento intr inseco è tr oppo complesso da spiegar e or a, quindi lo affr onter ò solamente nei capitoli dedicati alle inter facce, in par ticolar e par lando dell'inter faccia IEnumer able. La sintassi è la seguente: 1. Dim A As [tipo] 2. For Each A In [array/collezione] 3. 'istruzioni 4. Next Ovviamente anche in questo caso, come nel nor male For , è possibile inizializzar e una var iabile contator e all'inter no del costr utto: 1. For Each A As [tipo] in [array/collezione] ... Esempio: 01. Module Module1 02. Sub Main() 03. Dim Words() As String = {"Questo", "è", "un", "array", "di", "stringhe"} 04. 05. For Each Str As String In Words 06. Console.Write(Str & " ") 07. Next 08. 09. 'A schermo apparirà la frase: 10. ' "Questo è un array di stringhe " 11. 12. Console.ReadKey() 13. End Sub 14. End Module Per aver e un ter mine di par agone, il semplicissimo codice pr oposto equivale, usando un for nor male, a questo: 1. 'Words.Length restituisce il numero di elementi 2. 'presenti nell'array Words 3. For I As Int32 = 0 To Words.Length - 1 4. Console.Write(Words(I) & " ") 5. Next Gli array sono un tipo referenc e Diver samente da come accade in altr i linguaggi, gli ar r ay sono un tipo r efer ence, indipendentemente dal tipo di dati da essi contenuto. Ciò significa che si compor tano come ho spiegato nel capitolo "Tipi r efer ence e tipi value": l'ar ea di memor ia ad essi associata non contiene il lor o valor e, ma un puntator e alla lor o posizione nell'heap managed. Questo significa che l'oper ator e = tr a due ar r ay non copia il contenuto di uno nell'altr o, ma li r ende identici, ossia lo stesso oggetto. Per lo stesso motivo, è anche lecito distr ugger e logicamente un ar r ay ponendolo uguale a Nothing: questa oper azione può salvar e un discr eto ammontar e di memor ia, ad esempio quando si usano gr andi ar r ay per la lettur a di file binar i, ed è sempr e bene annullar e un ar r ay dopo aver lo usato. 01. Module Module1 02. Sub Main() 03.
  • 38. 'A e B sono due array di interi 04. Dim A() As Int32 = {1, 2, 3} 05. Dim B() As Int32 = {4, 5, 6} 06. 07. 'Ora A e B sono due oggetti diversi e contengono 08. 'numeri diversi. Questa riga stamperà sullo 09. 'schermo "False", infatti A Is B = False 10. Console.WriteLine(A Is B) 11. 'Adesso poniamo A uguale a B. Dato che gli array 12. 'sono un tipo reference, da ora in poi, entrambi 13. 'saranno lo stesso oggetto 14. A = B 15. 'Infatti questa istruzione stamperà a schermo 16. ''"True", poiché A Is B = True 17. Console.WriteLine(A Is B) 18. 'Dato che A e B sono lo stesso oggetto, se modifichiamo 19. 'un valore dell'array riferendoci ad esso con il nome 20. 'B, anche richiamandolo con A, esso mostrerà 21. 'che l'ultimo elemento è lo stesso 22. B(2) = 90 23. 'Su schermo apparirà 90 24. Console.WriteLine(A(2)) 25. 26. Dim C() As Int32 = {7, 8, 9} 27. B = C 28. 'Ora cosa succede? 29. 30. Console.ReadKey() 31. End Sub 32. End Module Ecco come appar e la memor ia dopo l'assegnazione A = B: Ed ecco come appar e dopo l'assegnazione B = C:
  • 39. Come si vede, le var iabili contengono solo l'indir izzo degli oggetti effettivi, per ciò ogni singola var iabile (A, B o C) può puntar e allo stesso oggetto ma anche a oggetti diver si: se A = B e B = C, non è ver o che A = C, come si vede dal gr afico. L'indir izzo di memor ia contenuto in A non cambia se non si usa esplicitamente un oper ator e di assegnamento. Se state leggendo la guida un capitolo alla volta, potete fer mar vi qui: il pr ossimo par agr afo è utile solo per consultazione. Manipolazione di array La classe System.Ar r ay contiene molti metodi statici utili per la manipolazione degli ar r ay. I più usati sono: Clear (A, I, L) : cancella L elementi a par tir e dalla posizione I nell'ar r ay A Clone() : cr ea una coppia esatta dell'ar r ay Constr ainedCopy(A1, I1, A2, I2, L) : copia L elementi dall'ar r ay A1 a par tir e dall'indice I1 nell'ar r ay A2, a par tir e dall'indice I2; se la copia non ha successo, ogni cambiamento sar à annullato e l'ar r ay di destinazione non subir à alcun danno Copy(A1, A2, L) / CopyTo(A1, A2) : il pr imo metodo copia L elementi da A1 a A2 a par tir e dal pr imo, mentr e il secondo fa una copia totale dell'ar r ay A1 e la deposita in A2 Find / FindLast (A, P(Of T)) As T : cer ca il pr imo elemento dell'ar r ay A per il quale la funzione gener ic Of T assegnata al delegate P r estituisce un valor e Tr ue, e ne r itor na il valor e Find(A, P(Of T)) As T() : cer ca tutti gli elementi dell'ar r ay A per i quali la funzione gener ic Of T assegnata al delegate P r estituisce un valor e Tr ue FindIndex / FindLastIndex (A, P(Of T)) As Int32 : cer ca il pr imo o l'ultimo elemento dell'ar r ay A per il quale la funzione gener ic Of T assegnata al delegate P r estituisce un valor e Tr ue, e ne r itor na l'indice For Each(A(Of T)) : esegue un'azione A deter minata da un delegate Sub per ogni elemento dell'ar r ay GetLength(A) : r estituisce la dimensione dell'ar r ay Index Of(A, T) / LastIndex Of(A, T) : r estituisce il pr imo o l'ultimo indice dell'oggetto T nell'ar r ay A Rever se(A) : inver te l'or dine di tutti gli elementi nell'ar r ay A Sor t(A) : or dina alfabeticamente l'ar r ay A. Esistono 16 ver sioni di questa pr ocedur a, tr a le quali una accetta
  • 40. come secondo par ametr o un oggetto che implementa un'inter faccia ICompar er che per mette di decider e come or dinar e l'ar r ay Molti di questi metodi, come si è visto, compr endono ar gomenti molto avanzati: quando sar ete in gr ado di compr ender e i Gener ics e i Delegate, r itor nate a far e un salto in questo capitolo: scopr ir ete la potenza di questi metodi.
  • 41. A13. I Metodi - Parte I Anatomia di un metodo Il Fr amew or k .NET mette a disposizione dello sviluppator e un enor me numer o di classi contenenti metodi davver o utili, già scr itti e pr onti all'uso, ma solo in pochi casi questi bastano a cr ear e un'applicazione ben str uttur ata ed elegante. Per questo motivo, è possibile cr ear e nuovi metodi - pr ocedur e o funzioni che siano - ed usar li comodamente nel pr ogr amma. Per lo più, si cr ea un metodo per separ ar e logicamente una cer ta par te di codice dal r esto del sor gente: questo ser ve in pr imis a r ender e il listato più leggibile, più consultabile e meno pr olisso, ed inoltr e ha la funzione di r acchiuder e sotto un unico nome (il nome del metodo) una ser ie più o meno gr ande di istr uzioni. Un metodo è costituito essenzialmente da tr e par ti: Nome : un identificator e che si può usar e in altr e par ti del pr ogr amma per in vocare il metodo, ossia per eseguir e le istr uzioni di cui esso consta; Elenco dei par ametr i : un elenco di var iabili attr aver so i quali il metodo può scambiar e dati con il pr ogr amma; Cor po : contiene il codice effettivo associato al metodo, quindi tutte le istr uzioni e le oper azioni che esso deve eseguir e Ma or a scendiamo un po' più nello specifico... Proc edure senza parametri Il caso più semplice di metodo consiste in una pr ocedur a senza par ametr i: essa costituisce, gr osso modo, un sottopr ogr amma a sè stante, che può esser e r ichiamato semplicemente scr ivendone il nome. La sua sintassi è molto semplice: 1. Sub [nome]() 2. 'istruzioni 3. End Sub Cr edo che vi sia subito balzato agli occhi che questo è esattamente lo stesso modo in cui viene dichiar ata la Sub Main: per tanto, or a posso dir lo, Main è un metodo e, nella maggior par te dei casi, una pr ocedur a senza par ametr i (ma si tr atta solo di un caso par ticolar e, come vedr emo fr a poco). Quando il pr ogr amma inizia, Main è il pr imo metodo eseguito: al suo inter no, ossia nel suo cor po, r isiede il codice del pr ogr amma. Inoltr e, poiché facenti par ti del nover o delle entità pr esenti in una classe, i metodi sono membr i di classe: devono, per ciò, esser e dichiar ati a livello di clas s e. Con questa locuzione abbastanza comune nell'ambito della pr ogr ammazione si intende l'atto di dichiar ar e qualcosa all'inter no del cor po di una classe, ma fuor i dal cor po di un qualsiasi suo membr o. Ad esempio, la dichiar azione seguente è cor r etta: 01. Module Module1 02. Sub Esempio() 03. 'istruzioni 04. End Sub 05. 06. Sub Main() 07. 'istruzioni 08. End Sub 09. End Module mentr e la pr ossima è SBAGLI ATA: 1. Module Module1 2. Sub Main() 3.
  • 42. Sub Esempio() 4. 'istruzioni 5. End Sub 6. 'istruzioni 7. End Sub 8. End Module Allo stesso modo, i metodi sono l'unica categor ia, oltr e alle pr opr ietà e agli oper ator i, a poter contener e delle istr uzioni: sono str umenti "attivi" di pr ogr ammazione e solo lor o possono eseguir e istr uzioni. Quindi astenetevi dallo scr iver e un abominio del gener e: 1. Module Module1 2. Sub Main() 3. 'istruzioni 4. End Sub 5. Console.WriteLine() 6. End Sub E' totalmente e concettualmente sbagliato. Ma or a veniamo al dunque con un esempio: 01. Module Module1 02. 'Dichiarazione di una procedura: il suo nome è "FindDay", il 03. 'suo elenco di parametri è vuoto, e il suo corpo è 04. 'rappresentato da tutto il codice compreso tra "Sub FindDay()" 05. 'ed "End Sub". 06. Sub FindDay() 07. Dim StrDate As String 08. Console.Write("Inserisci giorno (dd/mm/yyyy): ") 09. StrDate = Console.ReadLine 10. 11. Dim D As Date 12. 'La funzione Date.TryParse tenta di convertire la stringa 13. 'StrDate in una variabile di tipo Date (che è un tipo 14. 'base). Se ci riesce, ossia non ci sono errori nella 15. 'data digitata, restituisce True e deposita il valore 16. 'ottenuto in D; se, al contrario, non ci riesce, 17. 'restituisce False e D resta vuota. 18. 'Quando una data non viene inizializzata, dato che è un 19. 'tipo value, contiene un valore predefinito, il primo 20. 'Gennaio dell'anno 1 d.C. a mezzogiorno in punto. 21. If Date.TryParse(StrDate, D) Then 22. 'D.DayOfWeek contiene il giorno della settimana di D 23. '(lunedì, martedì, eccetera...), ma in un 24. 'formato speciale, l'Enumeratore, che vedremo nei 25. 'prossimi capitoli. 26. 'Il ".ToString()" converte questo valore in una 27. 'stringa, ossia in un testo leggibile: i giorni della 28. 'settimana, però, sono in inglese 29. Console.WriteLine(D.DayOfWeek.ToString()) 30. Else 31. Console.WriteLine(StrDate & " non è una data valida!") 32. End If 33. End Sub 34. 35. 'Altra procedura, simile alla prima 36. Sub CalculateDaysDifference() 37. Dim StrDate1, StrDate2 As String 38. Console.Write("Inserisci il primo giorno (dd/mm/yyyy): ") 39. StrDate1 = Console.ReadLine 40. Console.Write("Inserisci il secondo giorno (dd/mm/yyyy): ") 41. StrDate2 = Console.ReadLine 42. 43. Dim Date1, Date2 As Date 44. 45. If Date.TryParse(StrDate1, Date1) And _ 46. Date.TryParse(StrDate2, Date2) Then 47. 'La differenza tra due date restituisce il tempo 48. 'trascorso tra l'una e l'altra. In questo caso noi 49. 'prendiamo solo i giorni 50. Console.WriteLine((Date2 - Date1).Days) 51.
  • 43. Else 52. Console.WriteLine("Inserire due date valide!") 53. End If 54. End Sub 55. 56. Sub Main() 57. 'Command è una variabile di tipo char (carattere) che 58. 'conterrà una lettera indicante quale compito eseguire 59. Dim Command As Char 60. 61. Do 62. Console.Clear() 63. Console.WriteLine("Qualche operazione con le date:") 64. Console.WriteLine("- Premere F per sapere in che giorno " & _ 65. "della settimana cade una certa data;") 66. Console.WriteLine("- Premere D per calcolare la differenza tra due date;") 67. Console.WriteLine("- Premere E per uscire.") 68. 'Console.ReadKey() è la funzione che abbiamo sempre 69. 'usato fin'ora per fermare il programma in attesa della 70. 'pressione di un pulsante. Come vedremo fra breve, non 71. 'è necessario usare il valore restituito da una 72. 'funzione, ma in questo caso ci serve. Ciò che 73. 'ReadKey restituisce è qualcosa che non ho ancora 74. 'trattato. Per ora basti sapere che 75. 'Console.ReadKey().KeyChar contiene l'ultimo carattere 76. 'premuto sulla tastiera 77. Command = Console.ReadKey().KeyChar 78. 'Analizza il valore di Command 79. Select Case Command 80. Case "f" 81. 'Invoca la procedura FindDay() 82. FindDay() 83. Case "d" 84. 'Invoca la procedura CalculateDaysDifference() 85. CalculateDaysDifference() 86. Case "e" 87. 'Esce dal ciclo 88. Exit Do 89. Case Else 90. Console.WriteLine("Comando non riconosciuto!") 91. End Select 92. Console.ReadKey() 93. Loop 94. End Sub 95. End Module In questo pr imo caso, le due pr ocedur e dichiar ate sono effettivamente sottopr ogr ammi a sé stanti: non hanno nulla in comune con il modulo (eccetto il semplice fatto di esser ne membr i), né con Main, ossia non scambiano alcun tipo di infor mazione con essi; sono come degli ingr anaggi sigillati all'inter no di una scatola chiusa. A questo r iguar do, bisogna inser ir e una pr ecisazione sulle var iabili dichiar ate ed usate all'inter no di un metodo, qualsiasi esso sia. Esse si dicono lo cali o tem po r anee, poiché esistono solo all'inter no del metodo e vengono distr utte quando il flusso di elabor azione ne r aggiunge la fine. Anche sotto questo aspetto, si può notar e come le pr ocedur e appena stilate siano par ticolar mente chiuse e r estr ittive. Tuttavia, si può benissimo far inter agir e un metodo con oggetti ed entità ester ne, e questo appr occio è decisamente più utile che non il semplice impacchettar e ed etichettar e blocchi di istr uzioni in locazioni distinte. Nel pr ossimo esempio, la pr ocedur a attinge dati dal modulo, poiché in esso è dichiar ata una var iabile a livello di classe. 01. Module Module1 02. 'Questa variabile è dichiarata a livello di classe 03. '(o di modulo, in questo caso), perciò è accessibile 04. 'a tutti i membri del modulo, sempre seguendo il discorso 05. 'dei blocchi di codice fatto in precedenza 06. Dim Total As Single = 0 07. 08. 'Legge un numero da tastiera e lo somma al totale 09. Sub Sum() 10. Dim Number As Single = Console.ReadLine 11.
  • 44. Total += Number 12. End Sub 13. 14. 'Legge un numero da tastiera e lo sottrae al totale 15. Sub Subtract() 16. Dim Number As Single = Console.ReadLine 17. Total -= Number 18. End Sub 19. 20. 'Legge un numero da tastiera e divide il totale per 21. 'tale numero 22. Sub Divide() 23. Dim Number As Single = Console.ReadLine 24. Total /= Number 25. End Sub 26. 27. 'Legge un numero da tastiera e moltiplica il totale 28. 'per tale numero 29. Sub Multiply() 30. Dim Number As Single = Console.ReadLine 31. Total *= Number 32. End Sub 33. 34. Sub Main() 35. 'Questa variabile conterrà il simbolo matematico 36. 'dell'operazione da eseguire 37. Dim Operation As Char 38. Do 39. Console.Clear() 40. Console.WriteLine("Risultato attuale: " & Total) 41. Operation = Console.ReadKey().KeyChar 42. Select Case Operation 43. Case "+" 44. Sum() 45. Case "-" 46. Subtract() 47. Case "*" 48. Multiply() 49. Case "/" 50. Divide() 51. Case "e" 52. Exit Do 53. Case Else 54. Console.WriteLine("Operatore non riconosciuto") 55. Console.ReadKey() 56. End Select 57. Loop 58. End Sub 59. End Module Proc edure c on parametri Avviandoci ver so l'inter azione sempr e maggior e del metodo con l'ambiente in cui esso esiste, tr oviamo le pr ocedur e con par ametr i. Al contr ar io delle pr ecedenti, esse possono r icever e e scambiar e dati con il chiaman te: con quest'ultimo ter mine ci si r ifer isce alla gener ica entità all'inter no della quale il metodo in questione è stato invocato. I par ametr i sono come delle var iabili locali fittizie: esistono solo all'inter no del cor po, ma non sono dichiar ate in esso, bensì nell'elenco dei par ametr i. Tale elenco deve esser e specificato dopo il nome del metodo, r acchiuso da una coppia di par entesi tonde, e ogni suo elemento deve esser e separ ato dagli altr i da vir gole. 1. Sub [nome](ByVal [parametro1] As [tipo], ByVal [parametro2] As [tipo], ...) 2. 'istruzioni 3. End Sub Come si vede, anche la dichiar azione è abbastanza simile a quella di una var iabile, fatta eccezione per la par ola r iser vata ByVal, di cui tr a poco vedr emo l'utilià. Per intr odur r e semplicemente l'ar gomento, facciamo subito un
  • 45. esempio, r iscr ivendo l'ultimo codice pr oposto nel par agr afo pr ecedente con l'aggiunta dei par ametr i: 01. Module Module1 02. Dim Total As Single = 0 03. 04. Sub Sum(ByVal Number As Single) 05. Total += Number 06. End Sub 07. 08. Sub Subtract(ByVal Number As Single) 09. Total -= Number 10. End Sub 11. 12. Sub Divide(ByVal Number As Single) 13. Total /= Number 14. End Sub 15. 16. Sub Multiply(ByVal Number As Single) 17. Total *= Number 18. End Sub 19. 20. Sub Main() 21. 'Questa variabile conterrà il simbolo matematico 22. 'dell'operazione da eseguire 23. Dim Operation As Char 24. Do 25. Console.Clear() 26. Console.WriteLine("Risultato attuale: " & Total) 27. Operation = Console.ReadKey().KeyChar 28. Select Case Operation 29. 'Se si tratta di simboli accettabili 30. Case "+", "-", "*", "/" 31. 'Legge un numero da tastiera 32. Dim N As Single = Console.ReadLine 33. 'E a seconda dell'operazione, utilizza una 34. 'procedura piuttosto che un'altra 35. Select Case Operation 36. Case "+" 37. Sum(N) 38. Case "-" 39. Subtract(N) 40. Case "*" 41. Multiply(N) 42. Case "/" 43. Divide(N) 44. End Select 45. Case "e" 46. Exit Do 47. Case Else 48. Console.WriteLine("Operatore non riconosciuto") 49. Console.ReadKey() 50. End Select 51. Loop 52. End Sub 53. End Module Richiamando, ad esempio, Sum(N) si invoca la pr ocedur a Sum e si assegna al par ametr o Number il valor e di N: quindi, Number viene sommato a Total e il ciclo continua. Number , per ciò, è un "segnaposto", che r iceve solo dur ante l'esecuzione un valor e pr eciso, che può anche esser e, come in questo caso, il contenuto di un'altr a var iabile. Nel ger go tecnico, Number - ossia, più in gener ale, l'identificator e dichiar ato nell'elenco dei par ametr i - si dice par am etr o for m ale, mentr e N - ossia ciò che viene concr etamente pas s ato al metodo - si dice par am etr o attuale. Non ho volutamente assegnato al par ametr o attuale lo stesso nome di quello for male, anche se è del tutto lecito far lo: ho agito in questo modo per far capir e che non è necessar io nessun legame par ticolar e tr a i due; l'unico vincolo che deve sussister e r isiede nel fatto che par ametr o for male ed attuale abbiano lo stesso tipo. Quest'ultima asser zione, del r esto, è abbastanza ovvia: se r ichiamassimo Sum("ciao") come far ebbe il pr ogr amma a sommar e una str inga ("ciao") ad un numer o (Total)?
  • 46. Or a pr oviamo a modificar e il codice pr ecedente r iassumendo tutte le oper azioni in una sola pr ocedur a, a cui, per ò, vengono passati due par ametr i: il numer o e l'oper ator e da usar e. 01. Module Module1 02. Dim Total As Single = 0 03. 04. Sub DoOperation(ByVal Number As Single, ByVal Op As Char) 05. Select Case Op 06. Case "+" 07. Total += Number 08. Case "-" 09. Total -= Number 10. Case "*" 11. Total *= Number 12. Case "/" 13. Total /= Number 14. End Select 15. End Sub 16. 17. Sub Main() 18. Dim Operation As Char 19. Do 20. Console.Clear() 21. Console.WriteLine("Risultato attuale: " & Total) 22. Operation = Console.ReadKey().KeyChar 23. Select Case Operation 24. Case "+", "-", "*", "/" 25. Dim N As Single = Console.ReadLine 26. 'A questa procedura vengono passati due 27. 'parametri: il primo è il numero da 28. 'aggiungere/sottrarre/moltiplicare/dividere 29. 'a Total; il secondo è il simbolo 30. 'matematico che rappresenta l'operazione 31. 'da eseguire 32. DoOperation(N, Operation) 33. Case "e" 34. Exit Do 35. Case Else 36. Console.WriteLine("Operatore non riconosciuto") 37. Console.ReadKey() 38. End Select 39. Loop 40. End Sub 41. End Module Passare parametri al programma da riga di c omando Come avevo accennato in pr ecedenza, non è sempr e ver o che Main è una pr ocedur a senza par ametr i. Infatti, è possibile dichiar ar e Main in un altr o modo, che le consente di ottener e infor mazioni quando il pr ogr amma viene eseguito da r ig a di co m ando . In quest'ultimo caso, Main viene dichiar ata con un solo ar gomento, un ar r ay di str inghe: 01. Module Module1 02. Sub Main(ByVal Args() As String) 03. If Args.Length = 0 Then 04. 'Se la lunghezza dell'array è 0, significa che è vuoto 05. 'e quindi non è stato passato nessun parametro a riga 06. 'di comando. Scrive a schermo come utilizzare 07. 'il programma 08. Console.WriteLine("Utilizzo: nomeprogramma.exe tuonome") 09. Else 10. 'Args ha almeno un elemento. Potrebbe anche averne di 11. 'più, ma a noi interessa solo il primo. 12. 'Saluta l'utente con il nome passato da riga di comando 13. Console.WriteLine("Ciao " & Args(0) & "!") 14. End If 15. Console.ReadKey() 16. End Sub 17.
  • 47. End Module Per pr ovar lo, potete usar e cmd.ex e, il pr ompt dei comandi. Io ho digitato: 1. CD "C:UsersTotemDocumentsVisual Studio 2008ProjectsConsoleApplication2binDebug" 2. ConsoleApplication2.exe Totem La pr ima istr uzione per cambiar e la dir ector y di lavor o, la seconda l'invocazione ver a e pr opr ia del pr ogr amma, dove "Totem" è l'unico ar gomento passatogli: una volta pr emuto invio, appar ir à il messaggio "Ciao Totem!". In alter nativa, è possibile specificar e gli ar gomenti passati nella casella di testo "Command line ar guments" pr esente nella scheda Debug delle pr opr ietà di pr ogetto. Per acceder e alle pr opr ietà di pr ogetto, cliccate col pulsante destr o sul nome del pr ogetto nella finestr a a destr a, quindi scegliete Pr oper ties e r ecatevi alla tabella Debug:
  • 48. A14. I Metodi - Parte II By V al e By Ref Nel capitolo pr ecedente, tutti i par ametr i sono stati dichiar anti anteponendo al lor o nome la keyw or d ByVal. Essa ha il compito di comunicar e al pr ogr amma che al par ametr o for male deve esser e passata una co pia del par ametr o attuale. Questo significa che qualsiasi codice sia scr itto entr o il cor po del metodo, ogni manipolazione e ogni oper azione eseguita su quel par ametr o agisce, di fatto, su un 'altra variabile, tempor anea, e no n sul par ametr o attuale for nito. Ecco un esempio: 01. Module Module1 02. Sub Change(ByVal N As Int32) 03. N = 30 04. End Sub 05. 06. Sub Main() 07. Dim A As Int32 = 56 08. Change(A) 09. Console.WriteLine(A) 10. Console.ReadKey() 11. End Sub 12. End Module A scher mo appar ir à la scr itta "56": A è una var iabile di Main, che viene passata come par ametr o attuale alla pr ocedur a Change. In quest'ultima, N costituisce il par ametr o for male - il segnaposto - a cui, dur ante il passaggio dei par ametr i, viene attr ibuita un copia del valor e di A. In definitiva, per N viene cr eata un'altr a ar ea di memor ia, totalmente distinta, e per questo motivo ogni oper azione eseguita su quest'ultima non cambia il valor e di A. Di fatto, ByVal indica di tr attar e il par ametr o come un tipo value (pas s aggio per valore). Al contr ar io, ByRef indica di tr attar e il par ametr o come un tipo r efer ence (pas s aggio per in dirizzo). Questo significa che, dur ante il passaggio dei par ametr i, al par ametr o for male non viene attr ibuito come valor e una coppia di quello attuale, ma bensì viene for zato a puntar e alla sua stessa cella di memor ia. In questa situazione, quindi, anche i tipi value come numer i, date e valor i logici, si compor tano come se fosser o oggetti. Ecco lo stesso esempio di pr ima, con una piccola modifica: 01. Module Module1 02. Sub Change(ByRef N As Int32) 03. N = 30 04. End Sub 05. 06. Sub Main() 07. Dim A As Int32 = 56 08. Change(A) 09. Console.WriteLine(A) 10. Console.ReadKey() 11. End Sub 12. End Module Nel codice, la sola differ enza consiste nella keyw or d ByRef, la quale, tuttavia, cambia r adicalmente il r isultato. Infatti, a scher mo appar ir à "30" e non "56". Dato che è stata applicata la clausola ByRef, N punta alla stessa ar ea di memor ia di A, quindi ogni alter azione per petr ata nel cor po del metodo sul par ametr o for male si r iper cuote su quello attuale. A questo punto è molto impor tante sottolinear e che i tipi r efer ence si compor tano SEM PRE allo stesso modo, anche se vengono inser iti nell'elenco dei par ametr i accompagnati da ByVal. Eccone una dimostr azione: 01. Module Module1 02. Dim A As New Object 03. 04. Sub Test(ByVal N As Object) 05. Console.WriteLine(N Is A) 06.
  • 49. End Sub 07. 08. Sub Main() 09. Test(A) 10. Console.ReadKey() 11. End Sub 12. End Module Se ByVal modificasse il compor tamento degli oggetti, allor a N conter r ebbe una copia di A, ossia un altr o oggetto semplicemente uguale, ma non identico. Invece, a scher mo appar e la scr itta "Tr ue", che significa "Ver o", per ciò N e A sono lo stesso oggetto, anche se N er a pr eceduto da ByVal. Le funzioni Le funzioni sono simili alle pr ocedur e, ma possiedono qualche car atter istica in più. La lor o sintassi è la seguente: 1. Function [name]([elenco parametri]) As [tipo] 2. '... 3. Return [risultato] 4. End Function La pr ima differ enza che salta all'occhio è l'As che segue l'elenco dei par ametr i, come a sugger ir e che la funzione sia di un cer to tipo. Ad esser e pr ecisi, quell'As non indica il tipo della funzione, ma piuttosto quello del suo r isultato. Infatti, le funzioni r estituiscono qualcosa alla fine del lor o ciclo di elabor azione. Per questo motivo, pr ima del ter mine del suo cor po, deve esser e posta almeno un'istr uzione Retur n, seguita da un qualsiasi dato, la quale for nisce al chiamante il ver o r isultato di tutte le oper azioni eseguite. Non è un er r or e scr iver e funzioni pr ive dell'istr uzione Retur n, ma non avr ebbe comunque senso: si dovr ebbe usar e una pr ocedur a in quel caso. Ecco un esempio di funzione: 01. Module Module1 02. 'Questa funzione calcola la media di un insieme 03. 'di numeri decimali passati come array 04. Function Average(ByVal Values() As Single) As Single 05. 'Total conterrà la somma totale di tutti 06. 'gli elementi di Values 07. Dim Total As Single = 0 08. 'Usa un For Each per ottenere direttamente i valori 09. 'presenti nell'array piuttosto che enumerarli 10. 'attraverso un indice mediante un For normale 11. For Each Value As Single In Values 12. Total += Value 13. Next 14. 'Restituisce la media aritmetica, ossia il rapporto 15. 'tra la somma totale e il numero di elementi 16. Return (Total / Values.Length) 17. End Function 18. 19. Sub Main(ByVal Args() As String) 20. Dim Values() As Single = {1.1, 5.2, 9, 4, 8.34} 21. 'Notare che in questo caso ho usato lo stesso nome 22. 'per il parametro formale e attuale 23. Console.WriteLine("Media: " & Average(Values)) 24. Console.ReadKey() 25. End Sub 26. End Module E un altr o esempio in cui ci sono più Retur n: 01. Module Module1 02. Function Potenza(ByVal Base As Single, ByVal Esponente As Byte) As Double 03. Dim X As Double = 1 04. 05. If Esponente = 0 Then 06. Return 1 07. Else 08. If Esponente = 1 Then 09.
  • 50. Return Base 10. Else 11. For i As Byte = 1 To Esponente 12. X *= Base 13. Next 14. Return X 15. End If 16. End If 17. End Function 18. 19. Sub Main() 20. Dim F As Double 21. Dim b As Single 22. Dim e As Byte 23. 24. Console.WriteLine("Inserire base ed esponente:") 25. b = Console.ReadLine 26. e = Console.ReadLine 27. F = Potenza(b, e) 28. Console.WriteLine(b & " elevato a " & e & " vale " & F) 29. Console.ReadKey() 30. End Sub 31. End Module In quest'ultimo esempio, il cor po della funzione contiene ben tr e Retur n, ma ognuno appar tiene a un path di co dice differ ente. Path significa "per cor so" e la locuzione appena usata indica il flusso di elabor azione seguito dal pr ogr amma per deter minati valor i di Base ed Esponente. Disegnando un diagr amma di flusso della funzione, sar à facile capir e come ci siano tr e per cor si differ enti, ossia quando l'esponente vale 0, quando vale 1 e quando è maggior e di 1. È sintatticamente lecito usar e due Retur n nello stesso path, o addir ittur a uno dopo l'altr o, ma non ha nessun senso logico: Retur n, infatti, non solo r estituisce un r isultato al chiamante, ma ter mina anche l'esecuzione della funzione. A questo pr oposito, bisogna dir e che esiste anche lo statement (=istr uzione) Exit Fun ction , che for za il pr ogr amma ad uscir e immediatamente dal cor po della funzione: inutile dir e che è abbastanza per icoloso da usar e, poiché si cor r e il r ischio di non r estituir e alcun r isultato al chiamante, il che può tr adur si in un er r or e in fase di esecuzione. Come ultima postilla vor r ei aggiunger e che, come per le var ibili, non è str ettamente necessar io specificar e il tipo del valor e r estituito dalla funzione, anche se è for temente consigliato: in questo caso, il pr ogr amma suppor r à che si tr atti del tipo Object. Usi partic olari delle funzioni Ci sono cer te cir costanze in cui le funzioni possono differ ir e legger mente dal lor o uso e dalla lor o for ma consueti. Di seguito sono elencati alcuni casi: Quando una funzione si tr ova a destr a dell'uguale, in qualsiasi punto di un'espr essione dur ante un assegnamento, ed essa non pr esenta un elenco di par ametr i, la si può invocar e senza usar e la coppia di par entesi. L'esempio classico è la funzione Console.Readline. L'uso più cor r etto sar ebbe: 1. a = Console.ReadLine() ma è possibile scr iver e, come abbiamo fatto fin'or a: 1. a = Console.ReadLine
  • 51. Non è obbligator io usar e il valor e r estituito da una funzione: nei casi in cui esso viene tr alasciato, la si tr atta come se fosse una pr ocedur a. Ne è un esempio la funzione Console.ReadKey(). A noi ser ve per fer mar e il pr ogr amma in attesa della pr essione di un pulsante, ma essa non si limita a questo: r estituisce anche infor mazioni dettagliate sulle condizioni di pr essione e sul codice del car atter e inviato dalla tastier a. Tuttavia, a noi non inter essava usar e queste infor mazioni; così, invece di scr iver e un codice come questo: 1. Dim b = Console.ReadKey() ci siamo limitati a: 1. Console.ReadKey() Questa ver satilità può, in cer ti casi, cr ear e pr oblemi, poiché si usa una funzione convinti che sia una pr ocedur a, mentr e il valor e r estituito è impor tante per evitar e l'insor ger e di er r or i. Ne è un esempio la funzione IO.File.Cr eate, che vedr emo molto più in là, nella sezione E della guida. V ariabili Static Le var iabili Static sono una par ticolar e eccezione alle var iabili locali/tempor anee. Avevo chiar amente scr itto pochi par agr afi fa che queste ultime esistono solo nel cor po del metodo, vengono cr eate al momento dell'invocazione e distr utte al ter mine dell'esecuzione. Le Static, invece, possiedono soltanto le pr ime due car atter istiche: non vengono distr utte alla fine del cor po, ma il lor o valor e si conser va in memor ia e r imane tale anche quando il flusso entr a una seconda volta nel metodo. Ecco un esempio: 01. Module Module1 02. Sub Test() 03. Static B As Int32 = 0 04. B += 1 05. Console.WriteLine(B) 06. End Sub 07. 08. Sub Main(ByVal Args() As String) 09. For I As Int16 = 1 To 6 10. Test() 11. Next 12. Console.ReadKey() 13. End Sub 14. End Module Il pr ogr amma stamper à a scher mo, in successione, 1, 2, 3, 4, 5 e 6. Come volevasi dimostr ar e, nonostante B sia tempor anea, mantiene il suo valor e tr a una chiamata e la successiva.
  • 52. A15. I Metodi - Parte III Parametri opzionali Come sugger isce il nome stesso, i par ametr i opzionali sono speciali par ametr i che non è obbligator io specificar e quando si invoca un metodo. Li si dichiar a facendo pr eceder e la clausola ByVal o ByRef dalla keyw or d Optional: inoltr e, dato che un par ametr o del gener e può anche esser e omesso, bisogna necessar iamente indicar e un valor e pr edefinito che esso possa assumer e. Tale valor e pr edefinito deve esser e una costante e, per questo motivo, se r icor date il discor so pr ecedentemente fatto sull'assegnamento delle costanti, i par ametr i opzionali possono esser e solo di tipo base. Ecco un esempio: 01. Module Module1 02. 'Disegna una barra "di caricamento" animata con dei trattini 03. 'e dei pipe (|). Length indica la sua lunghezza, ossia quanti 04. 'caratterei debbano essere stampati a schermo. AnimationSpeed 05. 'è la velocità dell'animazione, di default 1 06. Sub DrawBar(ByVal Length As Int32, _ 07. Optional ByVal AnimationSpeed As Single = 1) 08. 'La variabile static tiene conto del punto a cui si è 09. 'arrivati al caricamento 10. Static Index As Int32 = 1 11. 12. 'Disegna la barra 13. For I As Int32 = 1 To Length 14. If I > Index Then 15. Console.Write("-") 16. Else 17. Console.Write("|") 18. End If 19. Next 20. 21. 'Aumenta l'indice di uno. Notare il particolare 22. 'assegnamento che utilizza l'operatore Mod. Finché 23. 'Index è minore di Length, questa espressione equivale 24. 'banalmente a Index + 1, poiché a Mod b = a se a < b. 25. 'Quando Index supera il valore di Length, allora l'operatore 26. 'Mod cambia le cose: infatti, se Index = Length + 1, 27. 'l'espressione restituisce 0, che, sommato a 1, dà 1. 28. 'Il risultato che otteniamo è che Index reinizia 29. 'da capo, da 1 fino a Length. 30. Index = (Index Mod (Length + 1)) + 1 31. 'Il metodo Sleep, che vedremo approfonditamente solo nella 32. 'sezione B, fa attendere al programma un certo numero di 33. 'millisecondi. 34. '1000 / AnimationSpeed provoca una diminuzione del tempo 35. 'di attesa all'aumentare della velocità 36. Threading.Thread.CurrentThread.Sleep(1000 / AnimationSpeed) 37. End Sub 38. 39. Sub Main() 40. 'Disegna la barra con un ciclo infinito. Potete invocare 41. 'DrawBar(20) tralasciando l'ultimo argomento e l'animazione 42. 'sarà lenta poiché la velocità di default è 1 43. Do 44. Console.Clear() 45. DrawBar(20, 5) 46. Loop 47. End Sub 48. End Module Parametri indefiniti
  • 53. Questo par ticolar e tipo di par ametr i non r appr esenta un solo elemento, ma bensì una collezione di elementi: infatti, si specifica un par ametr o come indefinito quando non si sa a pr ior i quanti par ametr i il metodo r ichieder à. A sostegno di questo fatto, i par ametr i indefiniti sono dichiar ati come ar r ay, usando la keyw or d Par amAr r ay inter posta tr a la clausola ByVal o ByRef e il nome del par ametr o. 01. Module Module1 02. 'Somma tutti i valori passati come parametri. 03. Function Sum(ByVal ParamArray Values() As Single) As Single 04. Dim Result As Single = 0 05. 06. For I As Int32 = 0 To Values.Length - 1 07. Result += Values(I) 08. Next 09. 10. Return Result 11. End Function 12. 13. Sub Main() 14. Dim S As Single 15. 16. 'Somma due valori 17. S = Sum(1, 2) 18. 'Somma quattro valori 19. S = Sum(1.1, 5.6, 98.2, 23) 20. 'Somma un array di valori 21. Dim V() As Single = {1, 8, 3.4} 22. S = Sum(V) 23. End Sub 24. End Module Come si vede, mediante Par amAr r ay, la funzione diventa capace si accettar e sia una lista di valor i specificata dal pr ogr ammator e si un ar r ay di valor i, dato che il par ametr o indefinito, in fondo, è pur sempr e un ar r ay. N.B.: può esister e uno e un solo par ametr o dichiar ato con Par amAr r ay per ciascun metodo, ed esso deve sempr e esser e posto alla fine dell'elenco dei par ametr i. Esempio: 01. Module Module1 02. 'Questa funzione calcola un prezzo includendovi anche 03. 'il pagamento di alcune tasse (non sono un esperto di 04. 'economia, perciò mi mantengono piuttosto sul vago XD). 05. 'Il primo parametro rappresenta il prezzo originale, mentre 06. 'il secondo è un parametro indefinito che 07. 'raggruppa tutte le varie tasse vigenti sul prodotto 08. 'da acquistare che devono essere aggiunte all'importo 09. 'iniziale (espresse come percentuali) 10. Function ApplyTaxes(ByVal OriginalPrice As Single, _ 11. ByVal ParamArray Taxes() As Single) As Single 12. Dim Result As Single = OriginalPrice 13. For Each Tax As Single In Taxes 14. Result += OriginalPrice * Tax / 100 15. Next 16. Return Result 17. End Function 18. 19. Sub Main() 20. Dim Price As Single = 120 21. 22. 'Aggiunge una tassa del 5% a Price 23. Dim Price2 As Single = _ 24. ApplyTaxes(Price, 5) 25. 26. 'Aggiunge una tassa del 5%, una del 12.5% e una 27. 'dell'1% a Price 28. Dim Price3 As Single = _ 29. ApplyTaxes(Price, 5, 12.5, 1) 30. 31. Console.WriteLine("Prezzo originale: " & Price) 32. Console.WriteLine("Presso con tassa 1: " & Price2) 33. Console.WriteLine("Prezzo con tassa 1, 2 e 3: " & Price3) 34.
  • 54. 35. Console.ReadKey() 36. End Sub 37. End Module Ric orsione Si ha una situazione di r icor sione quando un metodo invoca se stesso: in questi casi, il metodo viene detto r icor sivo. Tale tecnica possiede pr egi e difetti: il pr egio pr incipale consiste nella r iduzione dr astica del codice scr itto, con un conseguente aumento della leggibilità; il difetto più r ilevante è l'uso spr opositato di memor ia, per evitar e il quale è necessar io adottar e alcune tecniche di pr ogr ammazione dinamica. La r icor sione, se male usata, inoltr e, può facilmente pr ovocar e il cr ash di un'applicazione a causa di un over flow dello stack. Infatti, se un metodo continua indiscr iminatamente a invocar e se stesso, senza alcun contr ollo per poter si fer mar e (o con costr utti di contr ollo contenenti er r or i logici), continua anche a r ichieder e nuova memor ia per il passaggio dei par ametr i e per le var iabili locali, oltr e che per l'invocazione stessa: tutte queste r ichieste finiscono per sovr accar icar e la memor ia tempor anea, che, non r iuscendo più a soddisfar le, le deve r ifiutar e, pr ovocando il suddetto cr ash. Ma for se sono tr oppo pessimista: non vor r ei che r inunciaste ad usar e la r icor sione per paur a di incor r er e in tutti questi spaur acchi: ci sono cer ti casi in cui è davver o utile. Come esempio non posso che pr esentar e il classico calcolo del fatto r iale: 01. Module Module1 02. 'Notare che il parametro è di tipo Byte perchè il 03. 'fattoriale cresce in modo abnorme e già a 170! Double non 04. 'basta più a contenere il risultato 05. Function Factorial(ByVal N As Byte) As Double 06. If N <= 1 Then 07. Return 1 08. Else 09. Return N * Factorial(N - 1) 10. End If 11. End Function 12. 13. Sub Main() 14. Dim Number As Byte 15. 16. Console.WriteLine("Inserisci un numero (0 <= x < 256):") 17. Number = Console.ReadLine 18. Console.WriteLine(Number & "! = " & Factorial(Number)) 19. 20. Console.ReadKey() 21. End Sub 22. End Module
  • 55. A16. Gli Enumeratori Gli enumer ator i sono tipi value par ticolar i, che per mettono di r aggr uppar e sotto un unico nome più costanti. Essi vengono utilizzati sopr attutto per r appr esentar e opzioni, attr ibuti, car atter istiche o valor i pr edefiniti, o, più in gener ale, qualsiasi dato che si possa "sceglier e" in un insieme finito di possibilità. Alcuni esempi di enumer ator e potr ebber o esser e lo stato di un computer (acceso, spento, standby, iber nazione, ...) o magar i gli attr ibuti di un file (nascosto, ar chivio, di sistema, sola lettur a, ...): non a caso, per quest'ultimo, il .NET impiega ver amente un enumer ator e. Ma pr ima di andar e oltr e, ecco la sintassi da usar e nella dichiar azione: 1. Enum [Nome] 2. [Nome valore 1] 3. [Nome valore 2] 4. ... 5. End Enum Ad esempio: 01. Module Module1 02. 'A seconda di come sono configurati i suoi caratteri, una 03. 'stringa può possedere diverse denominazioni, chiamate 04. 'Case. Se è costituita solo da caratteri minuscoli 05. '(es.: "stringa di esempio") si dice che è in Lower 06. 'Case; al contrario se contiene solo maiuscole (es.: "STRINGA 07. 'DI ESEMPIO") sarà Upper Case. Se, invece, ogni 08. 'parola ha l'iniziale maiuscola e tutte le altre lettere 09. 'minuscole si indica con Proper Case (es.: "Stringa Di Esempio"). 10. 'In ultimo, se solo la prima parola ha l'iniziale 11. 'maiuscola e il resto della stringa è tutto minuscolo 12. 'e questa termina con un punto, si ha Sentence Case 13. '(es.: "Stringa di esempio."). 14. 'Questo enumeratore indica questi casi 15. Enum StringCase 16. Lower 17. Upper 18. Sentence 19. Proper 20. End Enum 21. 22. 'Questa funzione converte una stringa in uno dei Case 23. 'disponibili, indicati dall'enumeratore. Il secondo parametro 24. 'è specificato fra parentesi quadre solamente perchè 25. 'Case è una keyword, ma noi la vogliamo usare come 26. 'identificatore. 27. Function ToCase(ByVal Str As String, ByVal [Case] As StringCase) As String 28. 'Le funzioni per convertire in Lower e Upper 29. 'case sono già definite. E' sufficiente 30. 'indicare un punto dopo il nome della variabile 31. 'stringa, seguito a ToLower e ToUpper 32. Select Case [Case] 33. Case StringCase.Lower 34. Return Str.ToLower() 35. Case StringCase.Upper 36. Return Str.ToUpper() 37. Case StringCase.Proper 38. 'Consideriamo la stringa come array di 39. 'caratteri: 40. Dim Chars() As Char = Str.ToLower() 41. 'Iteriamo lungo tutta la lunghezza della 42. 'stringa, dove Str.Length restituisce appunto 43. 'tale lunghezza 44. For I As Int32 = 0 To Str.Length - 1 45. 'Se questo carattere è uno spazio oppure 46. 'è il primo di tutta la stringa, il 47. 'prossimo indicherà l'inizio di una nuova 48.
  • 56. 'parola e dovrà essere maiuscolo. 49. If I = 0 Then 50. Chars(I) = Char.ToUpper(Chars(I)) 51. End If 52. If Chars(I) = " " And I < Str.Length - 1 Then 53. 'Char.ToUpper rende maiuscolo un carattere 54. 'passato come parametro e lo restituisce 55. Chars(I + 1) = Char.ToUpper(Chars(I + 1)) 56. End If 57. Next 58. 'Restituisce l'array modificato (un array di caratteri 59. 'e una stringa sono equivalenti) 60. Return Chars 61. Case StringCase.Sentence 62. 'Riduce tutta la stringa a Lower Case 63. Str = Str.ToLower() 64. 'Imposta il primo carattere come maiuscolo 65. Dim Chars() As Char = Str 66. Chars(0) = Char.ToUpper(Chars(0)) 67. Str = Chars 68. 'La chiude con un punto 69. Str = Str & "." 70. Return Str 71. End Select 72. End Function 73. 74. Sub Main() 75. Dim Str As String = "QuEstA è una stRingA DI prova" 76. 77. 'Per usare i valori di un enumeratore bisogna sempre scrivere 78. 'il nome dell'enumeratore seguito dal punto 79. Console.WriteLine(ToCase(Str, StringCase.Lower)) 80. Console.WriteLine(ToCase(Str, StringCase.Upper)) 81. Console.WriteLine(ToCase(Str, StringCase.Proper)) 82. Console.WriteLine(ToCase(Str, StringCase.Sentence)) 83. 84. Console.ReadKey() 85. End Sub 86. End Module L'enumer ator e Str ingCase offr e quattr o possibilità: Low er , Upper , Pr oper e Sentence. Chi usa la funzione è invitato a sceglier e una fr a queste costanti, ed in questo modo non si r ischia di dimenticar e il significato di un codice. Notar e che ho scr itto "invitato", ma non "obbligato", poichè l'Enumer ator e è soltanto un mezzo attr aver so il quale il pr ogr ammator e dà nomi significativi a costanti, che sono pur sempr e dei numer i. A pr ima vista non si dir ebbe, vedendo la dichiar azione, ma ad ogni nome indicato come campo dell'enumer ator e viene associato un numer o (sempr e inter o e di solito a 32 bit). Per saper e quale valor e ciascun identificator e indica, basta scr iver e un codice di pr ova come questo: 1. Console.WriteLine(StringCase.Lower) 2. Console.WriteLine(StringCase.Upper) 3. Console.WriteLine(StringCase.Sentence) 4. Console.WriteLine(StringCase.Proper) A scher mo appar ir à 1. 0 2. 1 3. 2 4. 3 Come si vede, le costanti assegnate par tono da 0 per il pr imo campo e vengono incr ementate di 1 via via che si pr ocede a indicar e nuovi campi. È anche possibile deter minar e esplicitamente il valor e di ogni identificator e: 1. Enum StringCase 2. Lower = 5 3. Upper = 10 4. Sentence = 20 5.
  • 57. Proper = 40 6. End Enum Se ad un nome non viene assegnato valor e, esso assumer à il valor e del suo pr ecedente, aumentato di 1: 1. Enum StringCase 2. Lower = 5 3. Upper '= 6 4. Sentence = 20 5. Proper '= 21 6. End Enum Gli enumer ator i possono assumer e solo valor i inter i, e sono, a dir la ver ità, dir ettamente der ivati dai tipi numer ici di base. È, infatti, per fettamente lecito usar e una costante numer ica al posto di un enumer ator e e vicever sa. Ecco un esempio lampante in cui utilizzo un enumer ator e indicante le note musicali da cui r icavo la fr equenza delle suddette: 01. Module Module1 02. 'Usa i nomi inglesi delle note. L'enumerazione inizia 03. 'da -9 poiché il Do centrale si trova 9 semitoni 04. 'sotto il La centrale 05. Enum Note 06. C = -9 07. CSharp 08. D 09. DSharp 10. E 11. F 12. FSharp 13. G 14. GSharp 15. A 16. ASharp 17. B 18. End Enum 19. 20. 'Restituisce la frequenza di una nota. N, in concreto, 21. 'rappresenta la differenza, in semitoni, di quella nota 22. 'dal La centrale. Ecco l'utilittà degli enumeratori, 23. 'che danno un nome reale a ciò che un dato indica 24. 'indirettamente 25. Function GetFrequency(ByVal N As Note) As Single 26. Return 440 * 2 ^ (N / 12) 27. End Function 28. 29. 'Per ora prendete per buona questa funzione che restituisce 30. 'il nome della costante di un enumeratore a partire dal 31. 'suo valore. Avremo modo di approfondire nei capitoli 32. 'sulla Reflection 33. Function GetName(ByVal N As Note) As String 34. Return [Enum].GetName(GetType(Note), N) 35. End Function 36. 37. Sub Main() 38. 'Possiamo anche iterare usando gli enumeratori, poiché 39. 'si tratta pur sempre di semplici numeri 40. For I As Int32 = Note.C To Note.B 41. Console.WriteLine("La nota " & GetName(I) & _ 42. " risuona a una frequenza di " & GetFrequency(I) & "Hz") 43. Next 44. 45. Console.ReadKey() 46. End Sub 47. End Module È anche possibile specificar e il tipo di inter o di un enumer ator e (se Byte, Int16, Int32, Int64 o SByte, UInt16, UInt32, UInt64) apponendo dopo il nome la clausola As seguita dal tipo: 1. Enum StringCase As Byte 2. Lower = 5 3.
  • 58. Upper = 10 4. Sentence = 20 5. Proper = 40 6. End Enum Questa par ticolar ità si r ivela molto utile quando bisogna scr iver e enumer ator i su file in modalità binar ia. In questi casi, essi r appr esentano solitamente un campo detto Flags, di cui mi occuper ò nel pr ossimo par agr afo. Campi c odific ati a bit (Flags) Chi non conosca il codice binar io può legger e un ar ticolo su di esso nella sezione FFS. I campi codificati a bit sono enumer ator i che per mettono di immagazzinar e numer ose infor mazioni in pochissimo spazio, anche in un solo byte! Di solito, tuttavia, si utilizzano tipi Int32 per chè si ha bisogno di un numer o maggior e di infor mazioni. Il meccanismo è molto semplice. Ogni opzione deve poter assumer e due valor i, Ver o o Falso: questi vengono quindi codificati da un solo bit (0 o 1), ad esempio: 1. 00001101 Rappr esenta un inter o senza segno a un byte, ossia il tipo Byte: in esso si possono immagazzinar e 8 campi (uno per ogni bit), ognuno dei quali può esser e acceso o spento. In questo caso, sono attivi solo il pr imo, il ter zo e il quar to valor e. Per por tar e a ter mine con successo le oper azioni con enumer ator i pr ogettati per codificar e a bit, è necessar io che ogni valor e dell'enumer ator e sia una potenza di 2, da 0 fino al numer o che ci inter essa. Il motivo è molto semplice: dato che ogni potenza di due occupa un singolo spazio nel byte, non c'è per icolo che alcuna opzione si sovr apponga. Per unir e insieme più opzioni bisogna usar e l'oper ator e logico Or . Un esempio: 01. Module Module1 02. 'È convenzione che gli enumeratori che codificano a bit 03. 'abbiano un nome al plurale 04. 'Questo enumeratore definisce alcuni tipi di file 05. Public Enum FileAttributes As Byte 06. '1 = 2 ^ 0 07. 'In binario: 08. '00000001 09. Normal = 1 10. 11. '2 = 2 ^ 1 12. '00000010 13. Hidden = 2 14. 15. '4 = 2 ^ 2 16. '00000100 17. System = 4 18. 19. '8 = 2 ^ 3 20. '00001000 21. Archive = 8 22. End Enum 23. 24. Sub Main() 25. Dim F As FileAttributes 26. 'F all'inizio è 0, non contiene niente: 27. '00000000 28. 29. F = FileAttributes.Normal 30. 'Ora F è 1, ossia Normal 31. '00000001 32. 33. F = FileAttributes.Hidden Or FileAttributes.System 34. 'La situazione diventa complessa: 35. 'Il primo valore è 2: 000000010 36. 'Il secondo valore è 4: 000000100 37. 'Abbiamo già visto l'operatore Or: restituisce True se 38. 'almeno una delle condizioni è vera: qui True è 39. '1 e False è 0: 40.
  • 59. '000000010 Or 41. '000000100 = 42. '000000110 43. 'Come si vede, ora ci sono due campi attivi: 4 e 2, che 44. 'corrispondono a Hidden e System. Abbiamo fuso insieme due 45. 'attributi con Or 46. 47. F = FileAttributes.Archive Or FileAttributes.System Or _ 48. FileAttributes.Hidden 49. 'La stessa cosa: 50. '00001000 Or 51. '00000100 Or 52. '00000010 = 53. '00001110 54. End Sub 55. End Module Or a sappiamo come immagazzinar e i campi, ma come si fa a legger li? Nel pr ocedimento inver so si una invece un And: 01. Module Module1 02. Sub Main() 03. Dim F As FileAttributes 04. 05. F = FileAttributes.Archive Or FileAttributes.System Or _ 06. FileAttributes.Hidden 07. 08. 'Ora F è 00001110 e bisogna eseguire un'operazione di And 09. 'sui bit, confrontando questo valore con Archive, che è 8. 10. 'And restituisce Vero solo quando entrambe le condizioni 11. 'sono vere: 12. '00001110 And 13. '00001000 = 14. '00001000, ossia Archive! 15. If F And FileAttributes.Archive = FileAttributes.Archive Then 16. Console.WriteLine("Il file è marcato come 'Archive'") 17. End If 18. Console.ReadKey() 19. End Sub 20. End Module In definitiva, per immagazzinar e più dati in poco spazio occor r e un enumer ator e contenente solo valor i che sono potenze di due; con Or si uniscono più campi; con And si ver ifica che un campo sia attivo.
  • 60. A17. Le Strutture Nel capitolo pr ecedente ci siamo soffer mati ad analizzar e una par ticolar e categor ia di tipi di dato, gli enumer ator i, str umenti capaci di r appr esentar e tr amite costanti numer iche possibilità, scelte, opzioni, flags e in gener e valor i che si possano sceglier e in un insieme finito di elementi. Le str uttur e, invece, appar tengono ad un'altr a categor ia. Anch'esse r appr esentano un tipo di dato der ivato, o complesso, poiché non r ientr a fr a i tipi base (di cui ho già par lato) ma è da essi composto. Le str uttur e ci per mettono di cr ear e nuovi tipi di dato che possano adattar si in modo miglior e alla logica dell'applicazione che si sta scr ivendo: in r ealtà, quello che per mettono di far e è una specie di "collage" di var iabili. Ad esempio, ammettiamo di voler scr iver e una r ubr ica, in gr ado di memor izzar e nome, cognome e numer o di telefono dei nostr i pr incipali amici e conoscenti. Ovviamente, dato che si tr atta di tan te per sone, avr emo bisogno di ar r ay per contener e tutti i dati, ma in che modo li potr emmo immagazzinar e? Per quello che ho illustr ato fino a questo punto, la soluzione più lampante sar ebbe quella di dichiar ar e tr e ar r ay, uno per i nomi, uno per i cognomi e uno per i numer i telefonici. 1. Dim Names() As String 2. Dim Surnames() As String 3. Dim PhoneNumbers() As String Inutile dir e che seguendo questo appr occio il codice r isulter ebbe molto confusionar io e poco aggior nabile: se si volesse aggiunger e, ad esempio, un altr o dato, "data di nascita", si dovr ebbe dichiar ar e un altr o ar r ay e modificar e pr essoché tutte le par ti del listato. Usando una str uttur a, invece, potr emmo cr ear e un nuovo tipo di dato che contenga al suo inter no tutti i campi necessar i: 1. Structure Contact 2. Dim Name, Surname, PhoneNumber As String 3. End Structure 4. 5. '... 6. 7. 'Un array di conttati, ognuno rappresentato dalla struttura Contact 8. Dim Contacts() As Contact Come si vede dall'esempio, la sintassi usata per dichiar ar e una str uttur a è la seguente: 1. Structure [Nome] 2. Dim [Campo1] As [Tipo] 3. Dim [Campo2] As [Tipo] 4. '... 5. End Structure Una volta dichiar ata la str uttur a e una var iabile di quel tipo, per ò, come si fa ad acceder e ai campi in essa pr esenti? Si usa l'oper ator e punto ".", posto dopo il nome della var iabile: 01. Module Module1 02. Structure Contact 03. Dim Name, Surname, PhoneNumber As String 04. End Structure 05. 06. Sub Main() 07. Dim A As Contact 08. 09. A.Name = "Mario" 10. A.Surname = "Rossi" 11. A.PhoneNumber = "333 33 33 333" 12. End Sub 13. End Module [Ricor date che le dichiar azioni di nuovi tipi di dato (fino ad or a quelli che abbiamo analizzato sono enumer ator i e
  • 61. str uttur e, e le classi solo come intr oduzione) possono esser e fatte solo a livello di classe o di namespace, e m ai dentr o ad un metodo.] Una str uttur a, volendo ben veder e, non è altr o che un agglomer ato di più var iabili di tipo base e, cosa molto impor tante, è un tipo value, quindi si compor ta esattamente come Integer , Shor t, Date, ecceter a... e viene memor izzata dir ettamente sullo stack, senza uso di puntator i. Ac robazie c on le strutture Ma or a veniamo al codice ver o e pr opr io. Vogliamo scr iver e quella r ubr ica di cui avevo par lato pr ima, ecco un inizio: 01. Module Module1 02. Structure Contact 03. Dim Name, Surname, PhoneNumber As String 04. End Structure 05. 06. Sub Main() 07. 'Contacts(-1) inizializza un array vuoto, 08. 'ossia con 0 elementi 09. Dim Contacts(-1) As Contact 10. Dim Command As Char 11. 12. Do 13. Console.Clear() 14. Console.WriteLine("Rubrica -----") 15. Console.WriteLine("Selezionare l'azione desiderata:") 16. Console.WriteLine("N - Nuovo contatto;") 17. Console.WriteLine("T - Trova contatto;") 18. Console.WriteLine("E - Esci.") 19. Command = Char.ToUpper(Console.ReadKey().KeyChar) 20. Console.Clear() 21. 22. Select Case Command 23. Case "N" 24. 'Usa ReDim Preserve per aumentare le dimensioni 25. 'dell'array mentenendo i dati già presenti. 26. 'L'uso di array e di redim, in questo caso, è 27. 'sconsigliato, a favore delle più versatili 28. 'Liste, che però non ho ancora introdotto. 29. 'Ricordate che il valore specificato tra 30. 'parentesi indica l'indice massimo e non 31. 'il numero di elementi. 32. 'Se, all'inizio, Contacts.Length è 0, 33. 'richiamando ReDim Contacts(0), si aumenta 34. 'la lunghezza dell'array a uno, poiché 35. 'in questo caso l'indice massimo è 0, 36. 'ossia quello che indica il primo e 37. 'l'unico elemento 38. ReDim Preserve Contacts(Contacts.Length) 39. 40. Dim N As Contact 41. Console.Write("Nome: ") 42. N.Name = Console.ReadLine 43. Console.Write("Cognome: ") 44. N.Surname = Console.ReadLine 45. Console.Write("Numero di telefono: ") 46. N.PhoneNumber = Console.ReadLine 47. 48. 'Inserisce nell'ultima cella dell'array 49. 'l'elemento appena creato 50. Contacts(Contacts.Length - 1) = N 51. 52. Case "T" 53. Dim Part As String 54. 55. Console.WriteLine("Inserire nome o cognome del " & _ 56. "contatto da trovare:") 57. Part = Console.ReadLine 58. 59.
  • 62. For Each C As Contact In Contacts 60. 'Il confronto avviene in modalità 61. 'case-insensitive: sia il nome/cognome 62. 'che la stringa immessa vengono 63. 'ridotti a Lower Case, così da 64. 'ignorare la differenza tra 65. 'minuscole e maiuscole, qualora presente 66. If (C.Name.ToLower() = Part.ToLower()) Or _ 67. (C.Surname.ToLower() = Part.ToLower()) Then 68. Console.WriteLine("Nome: " & C.Name) 69. Console.WriteLine("Cognome: " & C.Surname) 70. Console.WriteLine("Numero di telefono: " & C.PhoneNumber) 71. Console.WriteLine() 72. End If 73. Next 74. 75. Case "E" 76. Exit Do 77. 78. Case Else 79. Console.WriteLine("Comando sconosciuto!") 80. End Select 81. Console.ReadKey() 82. Loop 83. End Sub 84. End Module Or a ammettiamo di voler modificar e il codice per per metter e l'inser imento di più numer i di telefono: 01. Module Module1 02. Structure Contact 03. Dim Name, Surname As String 04. 'Importante: NON è possibile specificare le dimensioni 05. 'di un array dentro la dichiarazione di una struttura. 06. 'Risulta chiaro il motivo se ci si pensa un attimo. 07. 'Noi stiamo dichiarando quali sono i campi della struttura 08. 'e quale è il loro tipo. Quindi specifichiamo che 09. 'PhoneNumbers è un array di stringhe, punto. Se scrivessimo 10. 'esplicitamente le sue dimensioni lo staremmo creando 11. 'fisicamente nella memoria, ma questa è una 12. 'dichiarazione, come detto prima, e non una 13. 'inizializzazione. Vedremo in seguito che questa 14. 'differenza è molto importante per i tipi reference 15. '(ricordate, infatti, che gli array sono tipi reference). 16. Dim PhoneNumbers() As String 17. End Structure 18. 19. Sub Main() 20. Dim Contacts(-1) As Contact 21. Dim Command As Char 22. 23. Do 24. Console.Clear() 25. Console.WriteLine("Rubrica -----") 26. Console.WriteLine("Selezionare l'azione desiderata:") 27. Console.WriteLine("N - Nuovo contatto;") 28. Console.WriteLine("T - Trova contatto;") 29. Console.WriteLine("E - Esci.") 30. Command = Char.ToUpper(Console.ReadKey().KeyChar) 31. Console.Clear() 32. 33. Select Case Command 34. Case "N" 35. ReDim Preserve Contacts(Contacts.Length) 36. 37. Dim N As Contact 38. Console.Write("Nome: ") 39. N.Name = Console.ReadLine 40. Console.Write("Cognome: ") 41. N.Surname = Console.ReadLine 42. 43. 'Ricordate che le dimensioni dell'array non 44.
  • 63. 'sono ancora state impostate: 45. ReDim N.PhoneNumbers(-1) 46. 47. 'Continua a chiedere numeri di telefono finché 48. 'non si introduce più nulla 49. Do 50. ReDim Preserve N.PhoneNumbers(N.PhoneNumbers.Length) 51. Console.Write("Numero di telefono " & N.PhoneNumbers.Length & ": ") 52. N.PhoneNumbers(N.PhoneNumbers.Length - 1) = Console.ReadLine 53. Loop Until N.PhoneNumbers(N.PhoneNumbers.Length - 1) = "" 54. 'Ora l'ultimo elemento dell'array è sicuramente 55. 'vuoto, lo si dovrebbe togliere. 56. 57. Contacts(Contacts.Length - 1) = N 58. 59. Case "T" 60. Dim Part As String 61. 62. Console.WriteLine("Inserire nome o cognome del " & _ 63. "contatto da trovare:") 64. Part = Console.ReadLine 65. 66. For Each C As Contact In Contacts 67. If (C.Name.ToLower() = Part.ToLower()) Or _ 68. (C.Surname.ToLower() = Part.ToLower()) Then 69. Console.WriteLine("Nome: " & C.Name) 70. Console.WriteLine("Cognome: " & C.Surname) 71. Console.WriteLine("Numeri di telefono: ") 72. For Each N As String In C.PhoneNumbers 73. Console.WriteLine(" - " & N) 74. Next 75. Console.WriteLine() 76. End If 77. Next 78. 79. Case "E" 80. Exit Do 81. 82. Case Else 83. Console.WriteLine("Comando sconosciuto!") 84. End Select 85. Console.ReadKey() 86. Loop 87. End Sub 88. End Module In questi esempi ho cer cato di pr opor r e i casi più comuni di str uttur a, almeno per quanto si è visto fino ad adesso: una str uttur a for mata da campi di tipo base e una composta dagli stessi campi, con l'aggiunta di un tipo a sua volta der ivato, l'ar r ay. Fino ad or a, infatti, ho sempr e detto che la str uttur a per mette di r aggr uppar e più membr i di tipo base, ma sar ebbe r iduttivo r estr inger e il suo ambito di competenza solo a questo. In r ealtà può contener e var iabili di qualsiasi tipo, compr ese altr e str uttur e. Ad esempio, un contatto avr ebbe potuto anche contener e l'indir izzo di r esidenza, il quale avr ebbe potuto esser e stato r appr esentato a sua volta da un'ulter ior e str uttur a: 01. Structure Address 02. Dim State, Town As String 03. Dim Street, CivicNumber As String 04. Dim Cap As String 05. End Structure 06. 07. Structure Contact 08. Dim Name, Surname As String 09. Dim PhoneNumbers() As String 10. Dim Home As Address 11. End Structure Per acceder e ai campi di Home si sar ebbe utilizzato un ulter ior e punto: 01. Dim A As Contact 02. 03.
  • 64. A.Name = "Mario" 04. A.Surname = "Rossi" 05. ReDim A.PhoneNumbers(0) 06. A.PhoneNumbers(0) = "124 90 87 111" 07. A.Home.State = "Italy" 08. A.Home.Town = "Pavia" 09. A.Home.Street = "Corso Napoleone" 10. A.Home.CivicNumber = "96/B" 11. A.Home.Cap = "27010"
  • 65. A18. Le Classi Bene bene. Eccoci ar r ivati al sugo della questione. Le classi, entità alla base di tutto l'edificio del .NET. Già nei pr imi capitoli di questa guida ho accennato alle classi, alla lor o sintassi e al modo di dichiar ar le. Per chi non si r icor dasse (o non avesse voglia di lasciar e questa magnifica pagina per r itor nar e indietr o nei capitoli), una classe si dichiar a semplicemente così: 1. Class [Nome Classe] 2. '... 3. End Class Con l'atto della dichiar azione, la classe inizia ad esister e all'inter no del codice sor gente, cosicchè il pr ogr ammator e la può usar e in altr e par ti del listato per gli scopi a causa dei quali è stata cr eata. Or a che ci stiamo avvicinando sempr e più all'usar e le classi nei pr ossimi pr ogr ammi, tuttavia, è dover oso r icor dar e ancor a una volta la sostanziale differ enza tr a dichiar azione e inizializzazione, tr a classe e oggetto, giusto per r infr escar e le memor ie più fr agili e, lungi dal far vi odiar e questo concetto, per far e in modo che il messaggio penetr i: 01. Module Module1 02. 'Classe che rappresenta un cubo. 03. 'Segue la dichiarazione della classe. Da questo momento 04. 'in poi, potremo usare Cube come tipo per le nostre variabili. 05. 'Notare che una classe si dichiara e basta, non si 06. '"inizializza", perchè non è qualcosa di concreto, 07. 'è un'astrazione, c'è, esiste in generale. 08. Class Cube 09. 'Variabile che contiene la lunghezza del lato 10. Dim SideLength As Single 11. 'Variabile che contiene la densità del cubo, e quindi 12. 'ci dice di che materiale è composto 13. Dim Density As Single 14. 15. 'Questa procedura imposta i valori del lato e 16. 'della densità 17. Sub SetData(ByVal SideLengthValue As Single, ByVal DensityValue As Single) 18. SideLength = SideLengthValue 19. Density = DensityValue 20. End Sub 21. 22. 'Questa funzione restituisce l'area di una faccia 23. Function GetSurfaceArea() As Single 24. Return (SideLength ^ 2) 25. End Function 26. 27. 'Questa funzione restituisce il volume del cubo 28. Function GetVolume() As Single 29. Return (SideLength ^ 3) 30. End Function 31. 32. 'Questa funzione restituisce la massa del cubo 33. Function GetMass() As Single 34. Return (Density * GetVolume()) 35. End Function 36. End Class 37. 38. Sub Main() 39. 'Variabile di tipo Cube, che rappresenta uno specifico cubo 40. 'La riga di codice che segue contiene la dichiarazione 41. 'della variabile A. La dichiarazione di una variabile 42. 'fa sapere al compilatore, ad esempio, di che tipo 43. 'sarà, in quale blocco di codice sarà 44. 'visibile, ma nulla di più. 45. 'Non esiste ancora un oggetto Cube collegato ad A, ma 46. 'potrebbe essere creato in un immediato futuro. 47.
  • 66. 'N.B.: quando si dichiara una variabile di tipo reference, 48. 'viene comunque allocata memoria sullo stack; viene 49. 'infatti creato un puntatore, che punta all'oggetto 50. 'Nothing, il cui valore simbolico è stato 51. 'spiegato precedentemente. 52. Dim A As Cube 53. 54. 'Ed ecco l'immediato futuro: con la prossima linea di 55. 'codice, creiamo l'oggetto di tipo Cube che verrà 56. 'posto nella variabile A. 57. A = New Cube 58. 'Quando New è seguito dal nome di una classe, si crea un 59. 'oggetto di quel tipo. Nella fattispecie, in questo momento 60. 'il programma si preoccuperà di richiedere della 61. 'memoria sull'heap managed per allocare i dati relativi 62. 'all'oggetto e di creare un puntatore sullo stack che 63. 'punti a tale oggetto. Esso, inoltre, eseguirà 64. 'il codice contenuto nel costruttore. New, infatti, 65. 'è uno speciale tipo di procedura, detta 66. 'Costruttore, di cui parlerò approfonditamente 67. 'in seguito 68. 69. 'Come per le strutture, i membri di classe sono accessibili 70. 'tramite l'operatore punto ".". Ora imposto le variabili 71. 'contenute in A per rappresentare un cubo di alluminio 72. '(densità 2700 Kg/m<sup>3</sup>) di 1.5m di lato 73. A.SetData(1.5, 2700) 74. 75. Console.WriteLine("Superficie faccia: " & A.GetSurfaceArea() & " m2") 76. Console.WriteLine("Volume: " & A.GetVolume() & " m3") 77. Console.WriteLine("Massa: " & A.GetMass() & " Kg") 78. 'It's Over 9000!!!! 79. 80. Console.ReadKey() 81. End Sub 82. End Module In questo esempio ho usato una semplice classe che r appr esenta un cubo di una cer ta dimensione e di un cer to mater iale. Tale classe espone quattr o funzioni, che ser vono per ottener e infor mazioni o impostar e valor i. C'è un pr eciso motivo per cui non ho usato dir ettamente le due var iabili accedendovi con l'oper ator e punto, e lo spiegher ò a br eve nella pr ossima lezione. Quindi, tali funzioni sono membr i di classe e, sopr attutto, funzioni di istanza. Questo lemma non dovr ebbe suonar vi nuovo: gli oggetti, infatti, sono istanze (copie mater iali, concr ete) di classi (astr azioni). Anche questo concetto è molto impor tante: il fatto che siano "di istanza" significa che possono esser e r ichiamate ed usate solo da un oggetto. Per far vi capir e, non si possono invocar e con questa sintassi: 1. Cube.GetVolume() ma solo passando attr aver so un'istanza: 1. Dim B As New Cube 2. '... 3. B.GetVolume() E questo, tr a l'altr o, è abbastanza banale: infatti, come sar ebbe possibile calcolar e ar ea, volume e massa se non si disponesse della misur a della lunghezza del lato e quella della densità? È ovvio che ogni cubo ha le sue pr opr ie misur e, e il concetto gener ale di "cubo" non ci dice niente su queste infor mazioni. Un semplic e c ostruttore Anche se entr er emo nel dettaglio solo più in là, è necessar io per i pr ossimi esempi che sappiate come funziona un costr uttor e, anche molto semplice. Esso viene dichiar ato come una nor male pr ocedur a, ma si deve sempr e usar e come nome "New ": 1.
  • 67. Sub New([parametri]) 2. 'codice 3. End Sub Qualor a non si specificasse nessun costr uttor e, il compilator e ne cr eer à uno nuovo senza par ametr i, che equivale al seguente: 1. Sub New() 2. End Sub Il codice pr esente nel cor po del costr uttor e viene eseguito in una delle pr ime fasi della cr eazione dell'oggetto, appena dopo che questo è statao fisicamente collocato nella memor ia (ma, badate bene, non è la pr ima istr uzione ad esser e eseguita dopo la cr eazione). Lo scopo di tale codice consiste nell'inizializzar e var iabili di tipo r efer ence pr ima solo dichiar ate, attr ibuir e valor i alle var iabili value, eseguir e oper azioni di pr epar azione all'uso di r isor se ester ne, ecceter a... Insomma, ser ve a spianar e la str ada all'uso della classe. In questo caso, l'uso che ne far emo è molto r idotto e, non vor r ei dir lo, quasi mar ginale, ma è l'unico compito possibile e utile in questo contesto: dar emo al costr uttor e il compito di inizializzar e SideLength e Density. 01. Module Module1 02. Class Cube 03. Dim SideLength As Single 04. Dim Density As Single 05. 06. 'Quasi uguale a SetData 07. Sub New(ByVal SideLengthValue As Single, ByVal DensityValue As Single) 08. SideLength = SideLengthValue 09. Density = DensityValue 10. End Sub 11. 12. Function GetSurfaceArea() As Single 13. Return (SideLength ^ 2) 14. End Function 15. 16. Function GetVolume() As Single 17. Return (SideLength ^ 3) 18. End Function 19. 20. Function GetMass() As Single 21. Return (Density * GetVolume()) 22. End Function 23. End Class 24. 25. Sub Main() 26. 'Questa è una sintassi più concisa che equivale a: 27. 'Dim A As Cube 28. 'A = New Cube(2700, 1.5) 29. 'Tra parentesi vanno passati i parametri richiesti dal 30. 'costruttore 31. Dim A As New Cube(2700, 1.5) 32. 33. Console.WriteLine("Superficie faccia: " & A.GetSurfaceArea() & " m<sup>2</sup>") 34. Console.WriteLine("Volume: " & A.GetVolume() & " m<sup>3</sup>") 35. Console.WriteLine("Massa: " & A.GetMass() & " Kg") 36. 37. Console.ReadKey() 38. End Sub 39. End Module Una nota sulle Strutture Anche le str uttur e, come le classi, possono espor r e pr ocedur e e funzioni, e questo non è str ano. Esse, inoltr e, possono espor r e anche costr uttor i... e questo dovr ebbe appar ir vi str ano. Infatti, ho appena illustr ato l'impor tanza dei costr uttor i nell'istanziar e oggetti, quindi tipi r efer ence, mentr e le str uttur e sono palesemente tipi value. Il conflitto si
  • 68. r isolve con una soluzione molto semplice: i costr uttor i dichiar ati nelle str uttur e possono esser e usati esattamente come per le classi, ma il lor o compito è solo quello di inizializzar e campi e r ichiamar e r isor se, poiché una var iabile di tipo str uttur ato viene cr eata sullo stack all'atto della sua dichiar azione. 01. Module Module1 02. Structure Cube 03. Dim SideLength As Single 04. Dim Density As Single 05. 06. Sub New(ByVal SideLengthValue As Single, ByVal DensityValue As Single) 07. SideLength = SideLengthValue 08. Density = DensityValue 09. End Sub 10. 11. Function GetSurfaceArea() As Single 12. Return (SideLength ^ 2) 13. End Function 14. 15. Function GetVolume() As Single 16. Return (SideLength ^ 3) 17. End Function 18. 19. Function GetMass() As Single 20. Return (Density * GetVolume()) 21. End Function 22. End Structure 23. 24. Sub Main() 25. 'Questo codice 26. Dim A As New Cube(2700, 1.5) 27. 28. 'Equivale a questo 29. Dim B As Cube 30. B.SideLength = 1.5 31. B.Density = 2700 32. 33. 'A e B sono uguali 34. 35. Console.ReadKey() 36. End Sub 37. End Module
  • 69. A19. Le Classi - Specificatori di accesso Le classi possono posseder e molti membr i, di svar iate categor ie, e ognuno di questi è sempr e contr addistinto da un liv ello di accesso . Esso specifica "chi" può acceder e a quali membr i, e da quale par te del codice. Molto spesso, infatti, è necessar io pr ecluder e l'accesso a cer te par ti del codice da par te di fr uitor i ester ni: fate bene attenzione, non sto par lando di pr otezione del codice, di sicur ezza, intendiamoci bene; mi sto r ifer endo, invece, a chi user à il nostr o codice (fossimo anche noi stessi). I motivi sono dispar ati, ma molto spesso si vuole evitar e che vengano modificate var iabili che ser vono per calcoli, oper azioni su file, r isor se, ecceter a. Al contr ar io, è anche possibile espander e l'accesso ad un membr o a chiunque. Con questi due esempi intr oduttivi, apr iamo la str ada agli specificato r i di accesso , par ole chiave anteposte alla dichiar azione di un membr o che ne deter minano il livello di accesso. Ecco una lista degli specificator i di accesso esistenti, di cui pr ender ò or a in esame solo i pr imi due: Pr ivate: un membr o pr ivato è accessibile solo all'inter no della classe in cui è stato dichiar ato; Public: un membr o pubblico è accessibile da qualsiasi par te del codice (dalla stessa classe, dalle sottoclassi, da classi ester ne, per fino da pr ogr ammi ester ni); Fr iend Pr otected Pr otected Fr iend (esiste solo in VB.NET) Un esempio pratic o Ripr endiamo il codice della classe Cube r ipr oposto nel capitolo pr ecedente. Pr oviamo a scr iver e nella Sub Main questo codice: 1. Sub Main() 2. Dim A As New Cube(2700, 1.5) 3. A.SideLength = 3 4. End Sub La r iga "A.SideLength = 3" ver r à sottolineata e appar ir à il seguente er r or e nel log degli er r or i: 1. ConsoleApplication2.Module1.Cube.SideLength' is not accessible in this 2. context because it is 'Private'. Questo è il motivo per cui ho usato una pr ocedur a per impostar e i valor i: l'accesso al membr o (in questo caso "campo", in quanto si tr atta di una var iabile) SideLength ci è pr ecluso se tentiamo di acceder vi da un codice ester no alla classe, poiché, di default, nelle classi, Dim equivale a Pr ivate. Dichiar andolo esplicitamente, il codice di Cube sar ebbe stato così: 01. Module Module1 02. Class Cube 03. 'Quando gli specificatori di accesso sono anteposti alla 04. 'dichiarazione di una variabile, si toglie il "Dim" 05. Private SideLength As Single 06. Private Density As Single 07. 08. Sub New(ByVal SideLengthValue As Single, ByVal DensityValue As Single) 09. SideLength = SideLengthValue 10. Density = DensityValue 11. End Sub 12. 13. Function GetSurfaceArea() As Single 14. Return (SideLength ^ 2) 15. End Function 16.
  • 70. 17. Function GetVolume() As Single 18. Return (SideLength ^ 3) 19. End Function 20. 21. Function GetMass() As Single 22. Return (Density * GetVolume()) 23. End Function 24. End Class 25. '... 26. End Module In questo specifico caso, sar ebbe stato meglio impostar e tali var iabili come Public, poiché nel lor o scope (= livello di accesso) attuale non ser vono a molto e, anzi, r ichiedono molto più codice di gestione. Ma immaginate una classe che compia oper azioni cr ittogr afiche sui dati che gli sono passati in input, usando var iabili d'istanza per i suoi calcoli: se tali var iabili fosser o accessibili al di fuor i della classe, lo sviluppator e che non sapesse esattamente cosa far ci potr ebbe compr ometter e ser iamente il r isultato di tali oper azioni, e quindi danneggiar e i pr otocolli di sicur ezza usati dall'applicazione. Etichettar e un membr o come pr ivate equivar r ebbe scher zosamente a por vi sopr a un gr ande car tello con scr itto "NON TOCCARE". Ma veniamo invece a Public, uno degli scope più usati nella scr ittur a di una classe. Di solito, tutti i membr i che devono esser e r esi disponibili per altr e par ti del pr ogr amma o anche per altr i pr ogr ammator i (ad esempio, se si sta scr ivendo una libr er ia che sar à usata successivamente da altr e per sone) sono dichiar ati come Public, ossia sempr e accessibili, senza nessun per messo di sor ta. E che dir e, allor a, dei membr i senza specificator e di accesso? Non esistono, a dir la tutta. Anche quelli che nel codice non vengono esplicitamente mar cati dal pr ogr ammator e con una delle keyw or d sopr a elencate hanno uno scope pr edefinito: si tr atta di Fr iend. Esso ha un compito par ticolar e che potr ete capir e meglio quando affr onter emo la scr ittur a di una libr er ia di classi: per or a vi baster à saper e che, all'inter no di uno stesso pr ogetto, equivale a Public. Un'altr a cosa impor tante: anche le classi (e i moduli) sono contr addistinte da un livello di accesso, che segue esattamente le stesse r egole sopr a esposte. Ecco un esempio: 01. Public Class Classe1 02. Private Class Classe2 03. '... 04. End Class 05. 06. Class Classe3 07. '... 08. End Class 09. End Class 10. 11. Class Classe4 12. Public Class Classe5 13. Private Class Classe6 14. '... 15.
  • 71. End Class 16. End Class 17. End Class 18. 19. Module Module1 20. Sub Main() 21. '... 22. End Sub 23. End Module Il codice contenuto in Main può acceder e a: Classe1, per chè è Public Classe3, per chè è Fr iend, ed è possibile acceder e al suo contenitor e Classe1 Classe4, per chè è Fr iend Classe5, per chè è Public, ed è possibile acceder e al suo contenitor e Classe4 mentr e non può acceder e a: Classe2, per chè è Pr ivate Classe6, per chè è Pr ivate d'altr a par te, il codice di Classe2 può acceder e a tutto tr anne a Classe6 e vicever sa. N.B.: Una classe può esser e dichiar ata Pr ivate solo quando si tr ova all'inter no di un'altr a classe (altr imenti non sar ebbe mai accessibile, e quindi inutile). Spec ific atori di ac c esso nelle Strutture Anche per i membr i di una str uttur a, così come per quelli di una classe, è possibile specificar e tutti gli scope esistenti. C'è solo una differ enza: quando si omette lo scope e si lascia una var iabile dichiar ata solo con Dim, essa è automaticamente impostata a Public. Per questo motivo ci er a possibile acceder e ai campi della str uttur a Contact, ad esempio: 1. Structure Contact 2. Dim Name, Surname, PhoneNumber As String 3. End Structure che equivale a: 1. Structure Contact 2. Public Name, Surname, PhoneNumber As String 3. End Structure Ovviamente, anche le str uttur e stesse hanno sempr e uno scope, così come qualsiasi altr a entità del .NET. Un esempio intelligente Ecco un esempio di classe scr itta utilizzando gli specificator i di accesso per limitar e l'accesso ai membr i da par te del codice di Main (e quindi da chi usa la classe, poiché l'utente finale può anche esser e un altr o pr ogr ammator e). Oltr e a questo tr over ete anche un esempio di un diffuso e semplice algor itmo di or dinamento, 2 in 1! 001. Module Module1 002. 'Dato che usiamo la classe solo in questo programma, possiamo 003. 'evitare di dichiararla Public, cosa che sarebbe ideale in 004. 'una libreria 005. Class BubbleSorter 006. 'Enumeratore pubblico: sarà accessibile da tutti. In questo 007. 'caso è impossibile dichiararlo come Private, poiché 008. 'uno dei prossimi metodi richiede come parametro una 009.
  • 72. 'variabile di tipo SortOrder e se questo fosse private, 010. 'non si potrebbe usare al di fuori della classe, cosa 011. 'che invece viene richiesta. 012. Public Enum SortOrder 013. Ascending 'Crescente 014. Descending 'Decrescente 015. None 'Nessun ordinamento 016. End Enum 017. 018. 'Mantiene in memoria il senso di ordinamento della lista, 019. 'per evitare di riordinarla nel caso fosse richiesto due 020. 'volte lo stesso 021. Private CurrentOrder As SortOrder = SortOrder.None 022. 'Mantiene in memoria una copia dell'array, che è 023. 'accessibile ai soli membri della classe. In 024. 'questo modo, è possibile eseguire tutte 025. 'le operazioni di ordinamento usando un solo metodo 026. 'per l'inserimento dell'array 027. Private Buffer() As Double 028. 029. 'Memorizza in Buffer l'array passato come parametro 030. Public Sub PushArray(ByVal Array() As Double) 031. 'Se Buffer è diverso da Nothing, lo imposta 032. 'esplicitamente a Nothing (equivale a distruggere 033. 'l'oggetto) 034. If Buffer IsNot Nothing Then 035. Buffer = Nothing 036. End If 037. 'Copia l'array: ricordate come si comportano i tipi 038. 'reference e pensate a quali ripercussioni tale 039. 'comportamento potrà avere sul codice 040. Buffer = Array 041. 'Annulla CurrentOrder 042. CurrentOrder = SortOrder.None 043. End Sub 044. 045. 'Procedura che ordina l'array secondo il senso specificato 046. Public Sub Sort(ByVal Order As SortOrder) 047. 'Se il senso è None, oppure è uguale a quello corrente, 048. 'è inutile proseguire, quindi si ferma ed esce 049. If (Order = SortOrder.None) Or (Order = CurrentOrder) Then 050. Exit Sub 051. End If 052. 053. 'Questa variabile tiene conto di tutti gli scambi 054. 'effettuati 055. Dim Occurrences As Int32 = 0 056. 057. 'Il ciclo seguente ordina l'array in senso crescente: 058. 'se l'elemento i è maggiore dell'elemento i+1, 059. 'ne inverte il posto, e aumenta il contatore di 1. 060. 'Quando il contatore rimane 0 anche dopo il For, 061. 'significa che non c'è stato nessuno scambio 062. 'e quindi l'array è ordinato. 063. Do 064. Occurrences = 0 065. For I As Int32 = 0 To Buffer.Length - 2 066. If Buffer(I) > Buffer(I + 1) Then 067. Dim Temp As Double = Buffer(I) 068. Buffer(I) = Buffer(I + 1) 069. Buffer(I + 1) = Temp 070. Occurrences += 1 071. End If 072. Next 073. Loop Until Occurrences = 0 074. 075. 'Se l'ordine era discendente, inverte l'array 076. If Order = SortOrder.Descending Then 077. Array.Reverse(Buffer) 078. End If 079. 080. 'Memorizza l'ordine 081.
  • 73. CurrentOrder = Order 082. End Sub 083. 084. 'Restituisce l'array ordinato 085. Public Function PopArray() As Double() 086. Return Buffer 087. End Function 088. End Class 089. 090. Sub Main() 091. 'Crea un array temporaneo 092. Dim a As Double() = {1, 6, 2, 9, 3, 4, 8} 093. 'Crea un nuovo oggetto BubbleSorter 094. Dim b As New BubbleSorter() 095. 096. 'Vi inserisce l'array 097. b.PushArray(a) 098. 'Invoca la procedura di ordinamento 099. b.Sort(BubbleSorter.SortOrder.Descending) 100. 101. 'E per ogni elemento presente nell'array finale 102. '(quello restituito dalla funzione PopArray), ne stampa 103. 'il valore a schermo 104. For Each n As Double In (b.PopArray()) 105. Console.Write(n & " ") 106. Next 107. 108. Console.ReadKey() 109. End Sub 110. End Module Ric apitolando... Ricapitolando, quindi, davanti a ogni membr o si può specificar e una keyw or d tr a Pr ivate, Public e Fr iend (per quello che abbiamo visto in questo capitolo), che ne limita l'accesso. Nel caso non si specifichi nulla, lo specificator e pr edefinito var ia a seconda dell'entità a cui è stato applicato, secondo questa tabella: Pr ivate per var iabili contenute in una classe Public per var iabili contenute in una str uttur a Fr iend per tutte le altr e entità
  • 74. A20. Le Proprietà - Parte I Le pr opr ietà sono una categor ia di membr i di classe molto impor tante, che user emo molto spesso da qui in avanti. Non è possibile definir ne con pr ecisione la natur a: esse sono una via di mezzo tr a metodi (pr ocedur e o funzioni) e campi (var iabili dichiar ate in una classe). In gener e, si dice che le pr opr ietà siano "campi intelligenti", poiché il lor o r uolo consiste nel mediar e l'inter azione tr a codice ester no alla classe e campo di una classe. Esse si "avvolgono" intor no a un campo (per questo motivo vengono anche chiamate w r apper , dall'inglese w r ap = impacchettar e) e decidono, tr amite codice scr itto dal pr ogr ammator e, quali valor i siano leciti per quel campo e quali no - stile buttafuor i, per intender ci. La sintassi con cui si dichiar a una pr opr ietà è la seguente: 01. Property [Nome]() As [Tipo] 02. Get 03. '... 04. Return [Valore restituito] 05. End Get 06. Set(ByVal value As [Tipo]) 07. '... 08. End Set 09. End Property Or a, questa sintassi, nel suo insieme, è molto diver sa da tutto ciò che abbiamo visto fino ad or a. Tuttavia, guar dando bene, possiamo r iconoscer e alcuni blocchi di codice e r icondur li ad una categor ia pr ecedentemente spiegata: La pr ima r iga di codice r icor da la dichiar azione di una var iabile; Il blocco Get r icor da una funzione; il codice ivi contenuto viene eseguito quando viene r ichiesto il valor e della pr opr ietà; Il blocco Set r icor da una pr ocedur a a un par ametr o; il codice ivi contenuto viene eseguito quando un codice imposta il valor e della pr opr ietà. Da quello che ho appena scr itto sembr a pr opr io che una pr opr ietà sia una var iabile pr ogr ammabile, ma allor a da dove si pr ende il valor e che essa assume? Come ho già r ipetuto, una pr opr ietà media l'inter azione tr a codice ester no e campo di una classe: quindi dobbiamo stabilir e un modo per collegar e la pr opr ietà al campo che ci inter essa. Ecco un esempio: 01. Module Module1 02. Class Example 03. 'Campo pubblico di tipo Single. 04. Public _Number As Single 05. 06. 'La proprietà Number media, in questo caso, l'uso 07. 'del campo _Number. 08. Public Property Number() As Single 09. Get 10. 'Quando viene chiesto il valore di Number, viene 11. 'restituito il valore della variabile _Number. Si 12. 'vede che la proprietà non fa altro che manipolare 13. 'una variabile esistente e non contiene alcun 14. 'dato di per sé 15. Return _Number 16. End Get 17. Set(ByVal value As Single) 18. 'Quando alla proprietà viene assegnato un valore, 19. 'essa modifica il contenuto di _Number impostandolo 20. 'esattamente su quel valore 21. _Number = value 22. End Set 23. End Property 24. End Class 25.
  • 75. 26. Sub Main() 27. Dim A As New Example() 28. 29. 'Il codice di Main sta impostando il valore di A.Number. 30. 'Notare che una proprietà si usa esattamente come una 31. 'comunissima variabile di istanza. 32. 'La proprietà, quindi, richiama il suo blocco Set come 33. 'una procedura e assegna il valore 20 al campo A._Number 34. A.Number = 20 35. 36. 'Nella prossima riga, invece, viene richiesto il valore 37. 'di Number per poterlo scrivere a schermo. La proprietà 38. 'esegue il blocco Get come una funzione e restituisce al 39. 'chiamante (ossia il metodo/oggetto che ha invocato Get, 40. 'in questo caso Console.WriteLine) il valore di A._Number 41. Console.WriteLine(A.Number) 42. 43. 'Per gli scettici, facciamo un controllo per vedere se 44. 'effettivamente il contenuto di A._Number è cambiato. 45. 'Potrete constatare che è uguale a 20. 46. Console.WriteLine(A._Number) 47. 48. Console.ReadLine() 49. End Sub 50. End Module Per pr ima cosa bisogna subito far e due impor tanti osser vazioni: Il nome della pr opr ietà e quello del campo a cui essa sovr intende sono molto simili. Questa similar ità viene mentenuta per l'appunto a causa dello str etto legame che lega pr opr ietà e campo. È una convenzione che il nome di un campo mediato da una pr opr ietà inizi con il car atter e under scor e ("_"), oppur e con una di queste combinazioni alfanumer iche: "p_", "m_". Il nome usato per la pr opr ietà sar à, invece, identico, ma senza l'under scor e iniziale, come in questo esempio. Il tipo definito per la pr opr ietà è identico a quello usato per il campo. Abbastanza ovvio, d'altr onde: se essa deve mediar e l'uso di una var iabile, allor a anche tutti i valor i r icevuti e r estituiti dovr anno esser e compatibili. La potenza nasc osta delle proprietà Ar r ivati a questo punto, uno potr ebbe pensar e che, dopotutto, non vale la pena di spr ecar e spazio per scr iver e una pr opr ietà quando può acceder e dir ettamente al campo. Bene, se c'è ver amente qualcuno che leggendo quello che ho scr itto ha pensato ver amente a questo, può anche andar e a compianger si in un angolino buio. XD Scher zi a par te, l'utilità c'è, ma spesso non si vede. Pr ima di tutto, iniziamo col dir e che se un campo è mediato da una pr opr ietà, per convenzione (ma anche per buon senso), deve esser e Pr ivate, altr imenti lo si potr ebbe usar e indiscr iminatamente senza limitazioni, il che è pr opr io quello che noi vogliamo impedir e. A questo possiamo anche aggiunger e una consider azione: visto che abbiamo la possibilità di far lo, aggiungendo del codice a Get e Set, per chè non far e qualche contr ollo sui valor i inser iti, giusto per evitar e er r or i peggior i in un immediato futur o? Ammettiamo di aver e la nostr a bella classe: 01. Module Module1 02. 'Questa classe rappresenta un semplice sistema inerziale, 03. 'formato da un piano orizzontale scabro (con attrito) e 04. 'una massa libera di muoversi su di esso 05. Class InertialFrame 06. Private _DynamicFrictionCoefficient As Single 07. Private _Mass As Single 08. Private _GravityAcceleration As Single 09. 10. 'Coefficiente di attrito radente (dinamico), μ 11. Public Property DynamicFrictionCoefficient() As Single 12. Get 13.
  • 76. Return _DynamicFrictionCoefficient 14. End Get 15. Set(ByVal value As Single) 16. _DynamicFrictionCoefficient = value 17. End Set 18. End Property 19. 20. 'Massa, m 21. Public Property Mass() As Single 22. Get 23. Return _Mass 24. End Get 25. Set(ByVal value As Single) 26. _Mass = value 27. End Set 28. End Property 29. 30. 'Accelerazione di gravità che vale nel sistema, g 31. Public Property GravityAcceleration() As Single 32. Get 33. Return _GravityAcceleration 34. End Get 35. Set(ByVal value As Single) 36. _GravityAcceleration = value 37. End Set 38. End Property 39. 40. 'Calcola e restituisce la forza di attrito che agisce 41. 'quando la massa è in moto 42. Public Function CalculateFrictionForce() As Single 43. Return (Mass * GravityAcceleration) * DynamicFrictionCoefficient 44. End Function 45. 46. End Class 47. 48. Sub Main() 49. Dim F As New InertialFrame() 50. 51. Console.WriteLine("Sistema inerziale formato da:") 52. Console.WriteLine(" - Un piano orizzontale e scabro;") 53. Console.WriteLine(" - Una massa variabile.") 54. Console.WriteLine() 55. 56. Console.WriteLine("Inserire i dati:") 57. Console.Write("Coefficiente di attrito dinamico = ") 58. F.DynamicFrictionCoefficient = Console.ReadLine 59. Console.Write("Massa (Kg) = ") 60. F.Mass = Console.ReadLine 61. Console.Write("Accelerazione di gravità (m/s<sup>2</sup>) = ") 62. F.GravityAcceleration = Console.ReadLine 63. 64. Console.WriteLine() 65. Console.Write("Attrito dinamico = ") 66. Console.WriteLine(F.CalculateFrictionForce() & " N") 67. 68. Console.ReadLine() 69. End Sub 70. End Module I calcoli funzionano, le pr opr ietà sono scr itte in modo cor r etto, tutto gir a alla per fezione, se non che... qualcuno tr ova il modo di metter e μ = 2 e m = -7, valor i assur di poiché 0 < μ <= 1 ed m > 0. Modificando il codice delle pr opr ietà possiamo impor r e questi vincoli ai valor i inser ibili: 01. Module Module1 02. Class InertialFrame 03. Private _DynamicFrictionCoefficient As Single 04. Private _Mass As Single 05. Private _GravityAcceleration As Single 06. 07. Public Property DynamicFrictionCoefficient() As Single 08. Get 09.
  • 77. Return _DynamicFrictionCoefficient 10. End Get 11. Set(ByVal value As Single) 12. If (value > 0) And (value <= 1) Then 13. _DynamicFrictionCoefficient = value 14. Else 15. Console.WriteLine(value & " non è un valore consentito!") 16. Console.WriteLine("Coefficiente attrito dinamico = 0.1") 17. _DynamicFrictionCoefficient = 0.1 18. End If 19. End Set 20. End Property 21. 22. Public Property Mass() As Single 23. Get 24. Return _Mass 25. End Get 26. Set(ByVal value As Single) 27. If value > 0 Then 28. _Mass = value 29. Else 30. Console.WriteLine(value & " non è un valore consentito!") 31. Console.WriteLine("Massa = 1") 32. _Mass = 1 33. End If 34. End Set 35. End Property 36. 37. Public Property GravityAcceleration() As Single 38. Get 39. Return _GravityAcceleration 40. End Get 41. Set(ByVal value As Single) 42. _GravityAcceleration = Math.Abs(value) 43. End Set 44. End Property 45. 46. Public Function CalculateFrictionForce() As Single 47. Return (Mass * GravityAcceleration) * DynamicFrictionCoefficient 48. End Function 49. 50. End Class 51. 52. '... 53. End Module In gener e, ci sono due modi di agir e quando i valor i che la pr opr ietà r iceve in input sono er r ati: Modificar e il campo r eimpostandolo su un valor e di default, ossia la str ategia che abbiamo adottato per questo esempio; Lanciar e un'eccezione. La soluzione for malmente più cor r etta sar ebbe la seconda: il codice chiamante dovr ebbe poi cattur ar e e gestir e tale eccezione, lasciando all'utente la possibilità di decider e cosa far e. Tuttavia, per far vi fr onte, bisogner ebbe intr odur r e ancor a un po' di teor ia e di sintassi, r agion per cui il suo uso è stato posto in secondo piano r ispetto alla pr ima. Inoltr e, bisogner ebbe anche evitar e di por r e il codice che comunica all'utente l'er r or e nel cor po della pr opr ietà e, più in gener ale, nella classe stessa, poiché questo codice potr ebbe esser e r iutilizzato in un'altr a applicazione che magar i non usa la console (altr a r agione per sceglier e la seconda possibilità). Mettendo da par te tali osser vazioni di cir costanza, comunque, si nota come l'uso delle pr opr ietà offr a molta più gestibilità e flessibilità di un semplice campo. E non è ancor a finita... Curiosità: dietro le quinte di una proprietà N.B.: Potete anche pr oceder e a legger e il pr ossimo capitolo, poiché questo par agr afo è pur amente illustr ativo.
  • 78. Come esempio user ò questa pr opr ietà: 01. Property Number() As Single 02. Get 03. Return _Number 04. End Get 05. Set(ByVal value As Single) 06. If (value > 30) And (value < 100) Then 07. _Number = value 08. Else 09. _Number = 31 10. End If 11. End Set 12. End Property Quando una pr opr ietà viene dichiar ata, ci sembr a che essa esista come un'entità unica nel codice, ed è più o meno ver o. Tuttavia, una volta che il sor gente passa nelle fauci del compilator e, succede una cosa abbastanza singolar e. La pr opr ietà cessa di esister e e viene invece spezzata in due elementi distinti: Una funzione senza par ametr i, di nome "get_[Nome Pr opr ietà]", il cui cor po viene cr eato copiando il codice contenuto nel blocco Get. Nel nostr o caso, get_Number : 1. Function get_Number() As Single 2. Return _Number 3. End Function Una pr ocedur a con un par ametr o, di nome "set_[Nome Pr opr ietà]", il cui cor po viene cr eato copiando il codice contenuto nel blocco Set. Nel nostr o caso, set_Number : 1. Sub set_Number(ByVal value As Single) 2. If (value > 30) And (value < 100) Then 3. _Number = value 4. Else 5. _Number = 31 6. End If 7. End Sub Entr ambi i metodi hanno come specificator e di accesso lo stesso della pr opr ietà. Inoltr e, ogni r iga di codice del tipo 1. [Proprietà] = [Valore] oppur e 1. [Valore] = [Proprietà] viene sostituita con la cor r ispondente r iga: 1. set_[Nome Proprietà]([Valore]) oppur e: 1. [Valore] = get_[Nome Proprietà] Ad esempio, il seguente codice: 1. Dim A As New Example 2. A.Number = 20 3. Console.WriteLine(A.Number) viene tr asfor mato, dur ante la compilazione, in: 1. Dim A As New Example 2. A.set_Number(20) 3. Console.WriteLine(A.get_Number())
  • 79. Questo per dir e che una pr opr ietà è un costr utto di alto livello, uno str umento usato nella pr ogr ammazione astr atta: esso viene scomposto nelle sue par ti fondamentali quando il pr ogr amma passa al livello medio, ossia quando è tr adotto in IL, lo pseudo-linguaggio macchina del Fr amew or k .NET.
  • 80. A21. Le Proprietà - Parte II Proprietà ReadOnly e W riteOnly Fin'or a abbiamo visto che le pr opr ietà sono in gr ado di mediar e l'inter azione tr a codice ester no alla classe e suoi campi, e tale mediazione compr endeva la possibilità di r ifiutar e cer ti valor i e consentir ne altr i. Ma non è finita qui: usando delle apposite keyw or ds è possibile r ender e una pr opr ietà a sola lettur a (ossia è possibile legger ne il valor e ma non modificar lo) o a sola scr ittur a (ossia è possibile modificar ne il valor e ma non ottener lo). Per quanto r iguar da la pr ima, viene abbastanza natur ale pensar e che ci possano esser e valor i solo esposti ver so cui è pr oibita la manipolazione dir etta, magar i per ché par ticolar mente impor tanti o, più spesso, per chè logicamente immutabili (vedi oltr e per un esempio); spostando l'attenzione per un attimo sulla seconda, per ò, sar à par imenti del tutto lecito domandar si quale sia la lor o utilità. Le var iabili, i campi, e quindi, per estensione, anche le pr opr ietà, sono per lor o natur a atti a contener e dati, che ver r anno poi utilizzati in altr e par ti del pr ogr amma: tali dati vengono continuamente letti e/o modificati e, per quanto sia possibile cr eder e che ve ne siano di immodificabili, come costanti e valor i a sola lettur a, appar e invece assur da l'esistenza di campi solo modificabili. Per modificar e qualcosa, infatti, se ne deve conoscer e almeno qualche infor mazione. La r ealtà è che le pr opr ietà Wr iteOnly sono innatur ali per la str agr ande maggior andza dei pr ogr ammator i; piuttosto di usar le è meglio definir e pr ocedur e. Mi occuper ò quindi di tr attar e solo la keyw or d ReadOnly. In br eve, la sintassi di una pr opr ietà a sola lettur a è questa: 1. ReadOnly Property [Nome]() As [Tipo] 2. Get 3. '... 4. Return [Valore] 5. End Get 6. End Property Notate che il blocco Set è assente: ovviamente, si tr atta di codice inutile dato che la pr opr ietà non può esser e modificata. Per continuar e il discor so iniziato pr ima, ci sono pr incipalmente tr e motivi per dichiar ar e un'entità del gener e: I dati a cui essa for nisce accesso sono impor tanti per la vita della classe, ed è quindi necessar io lasciar e che la modifica avvenga tr amite altr i metodi della classe stessa. Tuttavia, non c'è motivo di nasconder ne il valor e al codice ester no, cosa che può anche r ivelar si molto utile, sia come dato da elabor ar e, sia come infor mazione di dettaglio; La pr opr ietà espr ime un valor e che non si può modificar e per chè per pr opr ia natur a immutabile. Un classico esempio può esser e la data di nascita di una per sona: tipicamente la si inser isce come par ametr o del costr uttor e, o la si pr eleva da un database, e viene memor izzata in un campo esposto tr amite pr opr ietà ReadOnly. Questo è logico, poiché non si può cambiar e la data di nascita; è quella e basta. Un caso par ticolar e sar ebbe quello di un er r or e commesso dur ante l'inser imento della data, che costr inger ebbe a cambiar la. In questi casi, la modifica avviene per altr e vie (metodi con autenticazione o modifica del database); La pr opr ietà espr ime un valor e che viene calcolato al momento. Questo caso è molto speciale, poiché va al di là della nor male funzione di w r apper che le pr opr ietà svolgono nor malmente. Infatti, si può anche scr iver e una pr opr ietà che non sovr intende ad alcun campo, ma che, anzi, cr ea un campo fittizio: ossia, da fuor i sembr a che ci sia un'infor mazione in più nella classe, ma questa viene solo desunta o inter polata da altr i dati noti. Esempio: 01. Class Cube 02. Private _SideLength As Single 03. Private _Density As Single 04. 05. Public Property SideLength() As Single 06.
  • 81. Get 07. Return _SideLength 08. End Get 09. Set(ByVal value As Single) 10. If value > 0 Then 11. _SideLength = value 12. Else 13. _SideLength = 1 14. End If 15. End Set 16. End Property 17. 18. Public Property Density() As Single 19. Get 20. Return _Density 21. End Get 22. Set(ByVal value As Single) 23. If value > 0 Then 24. _Density = value 25. Else 26. _Density = 1 27. End If 28. End Set 29. End Property 30. 31. Public ReadOnly Property SurfaceArea() As Single 32. Get 33. Return (SideLength ^ 2) 34. End Get 35. End Property 36. 37. Public ReadOnly Property Volume() As Single 38. Get 39. Return (SideLength ^ 3) 40. End Get 41. End Property 42. 43. Public ReadOnly Property Mass() As Single 44. Get 45. Return (Volume * Density) 46. End Get 47. End Property 48. End Class Vedendola dall'ester no, si può pensar e che la classe Cube contenga come dati concr eti (var iabili) SideLength, Density, Sur faceAr ea, Volume e Mass, e che questi siano esposti tr amite una pr opr ietà. In r ealtà essa ne contiene solo i pr imi due e in base a questi calcola gli altr i. In questo esempio teor ico, le due pr opr ietà esposte sono r eadonly per il pr imo e il secondo motivo: 01. Module Esempio3 02. Class LogFile 03. Private _FileName As String 04. Private _CreationTime As Date 05. 06. 'Niente deve modificare il nome del file, altrimenti 07. 'potrebbero verificarsi errori nella lettura o scrittura 08. 'dello stesso, oppure si potrebbe chiudere un file 09. 'che non esiste ancora 10. Public ReadOnly Property FileName() As String 11. Get 12. Return _FileName 13. End Get 14. End Property 15. 16. 'Allo stesso modo non si pu� modificare la data di 17. 'creazione di un file: una volta creato, viene 18. 'prelevata l'ora e il giorno e impostata la 19. 'variabile. Se potesse essere modificata 20. 'non avrebbe più alcun significato 21.
  • 82. Public ReadOnly Property CreationTime() As Date 22. Get 23. Return _CreationTime 24. End Get 25. End Property 26. 27. Public Sub New(ByVal Path As String) 28. _FileName = Path 29. _CreationTime = Date.Now 30. End Sub 31. End Class 32. End Module Una nota sui tipi referenc e C'è ancor a un'ultima, ma impor tante, clausola da far notar e per le pr opr ietà ReadOnly. Si è gi� vista la differ enza tr a i tipi value e i tipi r efer ence: i pr imi contengono un valor e, mentr e i secondi un puntator e all'ar ea di memor ia in cui r isiede l'oggetto voluto. A causa di questa par ticolar e str uttur a, legger e il valor e di un tipo r efer ence da una pr opr ietà ReadOnly significa saper ne l'indir izzo, il che equivale ad ottener e il valor e dell'oggetto puntato. Non è quindi assolutamente sbagliato scr iver e: 01. Class ASystem 02. Private _Box As Cube 03. 04. Public ReadOnly Property Box() As Cube 05. Get 06. Return _Box 07. End Get 08. End Property 09. 10. '... 11. End Class 12. 13. '... 14. 15. Dim S As New ASystem() 16. S.Box.SideLength = 4 In questo modo, noi staimo effettivamente modificando l'oggetto S.Box , ma indir ettamente: non stiamo, invece, cambiando il valor e del campo S._Box , che effettivamente è ciò che ci viene impedito di far e. In sostanza, non stiamo as s egn an do un nuovo oggetto alla var iabile S._Box , ma stiamo solo manipolando i dati di un oggetto esistente, e questo è consentito. Anzi, è molto meglio dichiar ar e pr opr ietà di tipo r efer ence come ReadOnly quando non è necessar io assegnar e o impostar e nuovi oggetti.
  • 83. A22. Le Proprietà - Parte III Proprietà c on parametri Nei due capitoli pr ecedenti, ho sempr e scr itto pr opr ietà che semplicemente r estituivano il valor e di un campo, ossia il codice del blocco Get non er a nulla di più di un semplice Retur n. Intr oduciamo or a, invece, la possibilità di ottener e infor mazioni diver se dalla stessa pr opr ietà specificando un par ametr o, pr opr io come fosse un metodo. Avr ete notato, infatti, che fin dal pr incipio c'er a una coppia di par entesi tonde vicino al nome della pr opr ietà, ossia pr opr io la sintassi che si usa per dichiar ar e metodi senza par ametr i. Ecco un esempio: 01. Module Module1 02. 'Classe che rappresenta un estrattore di numeri 03. 'casuali 04. Class NumberExtractor 05. Private _ExtractedNumbers() As Byte 06. 'Generatore di numeri casuali. Random è una classe 07. 'del namespace System 08. Private Rnd As Random 09. 10. 'Questa proprietà ha un parametro, Index, che 11. 'specifica a quale posizione dell'array si intende recarsi 12. 'per prelevarne il valore. Nonostante l'array abbia solo 6 13. 'elementi di tipo Byte, l'indice viene comunemente sempre 14. 'indicato come intero a 32 bit. È una specie di 15. 'convenzione, forse derivante dalla maggior facilità di 16. 'elaborazione su macchine a 32 bit 17. Public ReadOnly Property ExtractedNumbers(ByVal Index As Int32) As Byte 18. Get 19. If (Index >= 0) And (Index < _ExtractedNumbers.Length) Then 20. Return _ExtractedNumbers(Index) 21. Else 22. Return 0 23. End If 24. End Get 25. End Property 26. 27. Public Sub New() 28. 'Essendo di tipo reference, si deve creare un nuovo 29. 'oggetto Random e assegnarlo a Rnd. La ragione per cui 30. 'Rnd è un membro di classe consiste nel fatto 31. 'che se fosse stata variabile temporanea del corpo 32. 'della procedura ExtractNumbers, sarebbero usciti 33. 'gli stessi numeri. Questo perchè la sequenza 34. 'pseudocasuale creata dalla classe non cambia se 35. 'non glielo si comunica espressamente usando un altro 36. 'costruttore. Non tratterò questo argomento ora 37. Rnd = New Random() 38. ReDim _ExtractedNumbers(5) 39. End Sub 40. 41. Public Sub ExtractNumbers() 42. 'Estrae 6 numeri casuali tra 1 e 90 e li pone nell'array 43. For I As Int32 = 0 To 5 44. _ExtractedNumbers(I) = Rnd.Next(1, 91) 45. Next 46. End Sub 47. End Class 48. 49. Sub Main() 50. Dim E As New NumberExtractor() 51. 52. E.ExtractNumbers() 53. Console.WriteLine("Numeri estratti: ") 54. For I As Int32 = 0 To 5 55. Console.Write(E.ExtractedNumbers(I) & " ") 56.
  • 84. Next 57. 58. Console.ReadKey() 59. End Sub 60. End Module Notar e che sar ebbe stato logicamente equivalente cr ear e una pr opr ietà che r estituisse tutto l'ar r ay, in questo modo: 1. Public ReadOnly Property ExtractedNumbers() As Byte() 2. Get 3. Return _ExtractedNumbers 4. End Get 5. End Property Ma non si sar ebbe avuto alcun contr ollo sull'indice che l'utente avr ebbe potuto usar e: nel pr imo modo, invece, è possibile contr ollar e l'indice usato e r estituir e 0 qualor a esso non sia coer ente con i limiti dell'ar r ay. La r estituzione di elementi di una lista, tuttavia, è solo una delle possibilità che le pr opr ietà par ametr iche offr ono, e non c'è limite all'uso che se ne può far e. Nonostante ciò, è bene sottolinear e che è meglio utilizzar e una funzione o una pr ocedur a (poiché le pr opr ietà di questo tipo possono anche non esser e r eadonly, questo er a solo un caso) qualor a si debbano eseguir e calcoli o elabor azioni non immediati, diciamo oltr e le 20/30 r ighe di codice, ma anche di meno, a seconda della pesantezza delle oper azioni. Fate conto che le pr opr ietà debbano sempr e esser e il più legger e possibile, computazionalmente par lando: qualche costr utto di contr ollo come If o Select, qualche calcolo sul Retur n, ma nulla di più. Proprietà di default Con questo ter mine si indica la pr opr ietà pr edefinita di una classe. Per esister e, essa deve soddisfar e questi due r equisiti: Deve posseder e almeno un par ametr o; Deve esser e unica. Anche se solitamente si usa in altr e cir costanze, ecco una pr opr ietà di default applicata al pr ecedente esempio: 01. Module Module1 02. Class NumberExtractor 03. Private _ExtractedNumbers() As Byte 04. Private Rnd As Random 05. 06. 'Una proprietà di default si dichiara come una 07. 'normalissima proprietà, ma anteponendo allo specificatore 08. 'di accesso la keyword Default 09. Default Public ReadOnly Property ExtractedNumbers(ByVal Index As Int32) As Byte 10. Get 11. If (Index >= 0) And (Index < _ExtractedNumbers.Length) Then 12. Return _ExtractedNumbers(Index) 13. Else 14. Return 0 15. End If 16. End Get 17. End Property 18. 19. Public Sub New() 20. Rnd = New Random() 21. ReDim _ExtractedNumbers(5) 22. End Sub 23. 24. Public Sub ExtractNumbers() 25. For I As Int32 = 0 To 5 26. _ExtractedNumbers(I) = Rnd.Next(1, 91) 27. Next 28. End Sub 29. End Class 30. 31. Sub Main() 32.
  • 85. Dim E As New NumberExtractor() 33. 34. E.ExtractNumbers() 35. Console.WriteLine("Numeri estratti: ") 36. For I As Int32 = 0 To 5 37. 'Ecco l'utilità delle proprietà di default: si possono 38. 'richiamare anche omettendone il nome. In questo caso 39. 'E(I) è equivalente a scrivere E.ExtractedNumbers(I), 40. 'ma poiché ExtractedNumbers è di default, 41. 'viene desunta automaticamente 42. Console.Write(E(I) & " ") 43. Next 44. 45. Console.ReadKey() 46. End Sub 47. End Module Dal codice salta subito all'occhio la motivazione dei due pr er equisiti specificati inizialmente: Se la pr opr ietà non avesse almeno un par ametr o, sar ebbe impossibile per il compilator e saper e quando il pr ogr ammator e si sta r ifer endo all'oggetto e quando alla sua pr opr ietà di default; Se non fosse unica, sar ebbe impossibile per il compilator e decider e quale pr ender e. Le pr opr ietà di default sono molto diffuse, specialmente nell'ambito degli oggetti w indow s for m, ma spesso non le si sa r iconoscer e. Anche per quello che abbiamo impar ato fin'or a, per ò, possiamo scovar e un esempio di pr opr ietà di default. Il tipo Str ing espone una pr opr ietà par ametr izzata Char s(I), che per mette di saper e quale car atter e si tr ova alla posizione I nella str inga, ad esempio: 1. Dim S As String = "Ciao" 2. Dim C As Char = S.Chars(1) 3. ' > C = "i", poiché "i" è il carattere alla posizione 1 4. ' nella stringa S Ebbene, Char s è una pr opr ietà di default, ossia è possibile scr iver e: 1. Dim S As String = "Ciao" 2. Dim C As Char = S(1) 3. ' > C = "i" Get e Set c on spec ific atori di ac c esso Anche se a pr ima vista potr ebbe sembr ar e str ano, sì, è possibile assegnar e uno specificator e di accesso anche ai singoli blocchi Get e Set all'inter no di una pr opr ietà. Questa peculiar e car atter istica viene sfr uttata ver amente poco, ma offr e una gr ande flessibilità e un'altr ettanto gr ande potenzialità di gestione. Limitando l'accesso ai singoli blocchi, è possibile r ender e una pr opr ietà ReadOnly solo per cer te par ti di codice e/o Wr iteOnly solo per altr e par ti, pur senza usar e dir ettamente tali keyw or ds. Ovviamente, per esser e logicamente applicabili, gli specificator i di accesso dei blocchi inter ni devono esser e più r estr ittivi di quello usato per contr assegnar e la pr opr ietà stessa. Infatti, se una pr opr ietà è pr ivata, ovviamente non potr à aver e un blocco get pubblico. In gener e, la ger ar chia di r estr ittività segue questa lista, dove Public è il meno r estr ittivo e Pr ivate il più r estr ittivo: Public Pr otected Fr iend Fr iend Pr otected Pr ivate Altr a condizione necessar ia è che uno solo tr a Get e Set può esser e mar cato con uno scope diver so da quello della
  • 86. pr opr ietà. Non avr ebbe senso, infatti, ad esempio, definir e una pr opr ietà pubblica con un Get Fr iend e un Set Pr ivate, poiché non sar ebbe più pubblica (in quanto sia get che set non sono pubblici)! Ecco un esempio: 1. Public Property A() As Byte 2. Get 3. '... 4. End Get 5. Private Set(ByVal value As Byte) 6. '... 7. End Set 8. End Property La pr opr ietà A è sempr e leggibile, ma modificabile solo all'inter no della classe che la espone. In pr atica, è come una nor male pr opr ietà per il codice inter no alla classe, ma come una ReadOnly per quello ester no. È pur ver o che in questo caso, si sar ebbe potuto r ender la dir ettamente ReadOnly e modificar e dir ettamente il campo da essa avvolto invece che espor r e un Set pr ivato, ma sono punti di vista. Ad ogni modo, l'uso di scope diver sificati per mette di far e di tutto e di più ed è solo un caso che non mi sia venuto in mente un esempio più significativo. Mettiamo un po' d'ordine sulle key w ord In questi ultimi capitoli ho spiegato un bel po' di keyw or d diver se, e specialmente nelle pr opr ietà può accader e di dover specificar e molte keyw or d insieme. Ecco l'or dine cor r etto (anche se l'editor del nostr o ambiente di sviluppo le r ior dina per noi nel caso dovessimo sbagliar e): 1. [Default] [ReadOnly/WriteOnly] [Public/Friend/Private/...] Property ... E or a quelle che conoscete sono ancor a poche... pr ovate voi a scr iver e una pr opr ietà del gener e: 1. Default Protected Friend Overridable Overloads ReadOnly Property A(ByVal Index As Int32) As Byte 2. Get 3. '... 4. End Get 5. End Property N.B.: ovviamente, tutto quello che si è detto fin'or a sulle pr opr ietà nelle classi vale anche per le str uttur e!
  • 87. A23. Membri Shared Tutte le categor ie di membr i che abbiamo analizzato nei pr ecedenti capitoli - campi, metodi, pr opr ietà, costr uttor i - sono sempr e state viste come appar tenenti ad un oggetto, ad un'istanza di classe. Infatti, ci si r ifer isce ad una pr opr ietà o a un metodo di un o s pecifico oggetto, dicendo ad esempio "La pr opr ietà SideLength dell'oggetto A di tipo Cube vale 3, mentr e quella dell'oggetto B anch'esso di tipo Cube vale 4.". La classe, ossia il tipo di una var iabile r efer ence, ci diceva solo quali membr i un cer to oggetto potesse espor r e: ci for niva, quindi, il "pr ogetto di costr uzione" di un oggetto nella memor ia, in cui si potevano collocar e tali campi, tali metodi, tal'altr e pr opr ietà e via dicendo. Or a, un membr o shar ed, o co ndiv iso , o statico (ter mine da usar si più in C# che non in VB.NET), non appar tiene più ad un'istanza di classe, ma alla classe stessa. Mi r endo conto che il concetto possa esser e all'inizio difficile da capir e e da inter ior izzar e cor r ettamente. Per questo motivo far ò un esempio il più semplice, ma più significativo possibile. Ripr endiamo la classe Cube, modificata come segue: 01. Module Module1 02. 03. Class Cube 04. Private _SideLength As Single 05. Private _Density As Single 06. 07. 'Questo campo è Shared, condiviso. Come vedete, 08. 'per dichiarare un membro come tale, si pone la 09. 'keyword Shared dopo lo specificatore di accesso. Questa 10. 'variabile conterrà il numero di cubi creati 11. 'dal nostro programma. 12. 'N.B.: I campi Shared sono di default Private... 13. Private Shared _CubesCount As Int32 = 0 14. 15. Public Property SideLength() As Single 16. Get 17. Return _SideLength 18. End Get 19. Set(ByVal value As Single) 20. If value > 0 Then 21. _SideLength = value 22. Else 23. _SideLength = 1 24. End If 25. End Set 26. End Property 27. 28. Public Property Density() As Single 29. Get 30. Return _Density 31. End Get 32. Set(ByVal value As Single) 33. If value > 0 Then 34. _Density = value 35. Else 36. _Density = 1 37. End If 38. End Set 39. End Property 40. 41. Public ReadOnly Property SurfaceArea() As Single 42. Get 43. Return (SideLength ^ 2) 44. End Get 45. End Property 46. 47. Public ReadOnly Property Volume() As Single 48. Get 49. Return (SideLength ^ 3) 50.
  • 88. End Get 51. End Property 52. 53. Public ReadOnly Property Mass() As Single 54. Get 55. Return (Volume * Density) 56. End Get 57. End Property 58. 59. 'Allo stesso modo, la proprietà che espone il membro 60. 'shared deve essere anch'essa shared 61. Public Shared ReadOnly Property CubesCount() As Int32 62. Get 63. Return _CubesCount 64. End Get 65. End Property 66. 67. 'Ogni volta che un nuovo cubo viene creato, _CubesCount 68. 'viene aumentato di uno, per rispecchiare il nuovo numero 69. 'di istanze della classe Cube esistenti in memoria 70. Sub New() 71. _CubesCount += 1 72. End Sub 73. End Class 74. 75. Sub Main() 76. Dim Cube1 As New Cube() 77. Cube1.SideLength = 1 78. Cube1.Density = 2700 79. 80. Dim Cube2 As New Cube() 81. Cube2.SideLength = 0.9 82. Cube2.Density = 3500 83. 84. Console.Write("Cubi creati: ") 85. 'Notate come si accede a un membro condiviso: poiché 86. 'appartiene alla classe e non alla singola istanza, vi si 87. 'accede specificando prima il nome della classe, poi 88. 'il comune operatore punto, e successivamente il nome 89. 'del membro. Tutti i membri shared funzionano in questo 90. 'modo 91. Console.WriteLine(Cube.CubesCount) 92. 93. Console.ReadKey() 94. End Sub 95. End Module Facendo cor r er e l'applicazione, si vedr à appar ir e a scher mo il numer o 2, poiché abbiamo cr eato due oggetti di tipo Cube. Come si vede, il campo CubesCount non r iguar da un solo specifico oggetto, ma la totalità di tutti gli oggetti di tipo Cube, poiché è un dato globale. In gener ale, esso è di dominio della classe Cube, ossia della r appr esentazione più astr atta dell'essenza di ogni oggetto: per saper e quanti cubi sono stati cr eati, non si può inter pellar e una singola istanza, per chè essa non "ha per cezione" di tutte le altr e istanze esistenti. Per questo motivo CubesCount è un membr o condiviso. Anche in questo caso c'è una r istr etta gamma di casi in cui è oppor tuno sceglier e di definir e un membr o come condiviso: Quando contiene infor mazioni r iguar danti la totalità delle istanze di una classe, come in questo caso; Quando contiene infor mazioni accessibili e necessar ie a tutte le istanze della classe, come illustr er ò fr a qualche capitolo; Quando si tr atta di un metodo "di libr er ia". I cosiddetti metodi di libr er ia sono metodi sempr e shar ed che svolgono funzioni gener ali e sono utilizzabili da qualsiasi par te del codice. Un esempio potr ebbe esser e la funzione Math.Abs(x ), che r estituisce il valor e assoluto di x . Come si vede, è shar ed poiché vi si accede usando il nome della classe. Inoltr e, essa è sempr e usabile, poiché si tr atta di una semplice funzione matematica, che, quindi, for nisce ser vizi di or dine gener ale;
  • 89. Quando si tr ova in un modulo, come spiegher ò nel pr ossimo par agr afo. Classi Shared Come!?!? Esistono classi shar ed? Ebbene sì. Può sembr ar e assur do, ma ci sono, ed è lecito domandar si quale sia la lor o funzione. Se un membr o shar ed appar tiene a una classe... cosa possiamo dir e di una classe shar ed? A dir e il ver o, abbiamo sempr e usato classi shar ed senza saper lo: i moduli, infatti, non sono altr o che classi condivise (o statiche). Tuttavia, il significato della par ola shar ed, se applicato alle classi, cambia r adicalmente. Un modulo, quindi una classe shar ed, r ende implicitamente shar ed tutti i suoi membr i. Quindi, tutte le pr opr ietà, i campi e i metodi appar tenenti ad un modulo - ivi compr esa la Sub Main - sono membr i shar ed. Che senso ha questo? I moduli sono consuetamente usati, al di fuor i delle applicazioni console, per r ender e disponibili a tutto il pr ogetto membr i di par ticolar e r ilevanza o utilità, ad esempio funzioni per il salvataggio dei dati, infor mazioni sulle opzioni salvate, ecceter a... Infatti è impossibile definir e un membr o shar ed in un modulo, poiché ogni membr o del modulo lo è già di per sé: 1. Module Module1 2. Shared Sub Hello() 3. 4. End Sub 5. 6. '... 7. End Sub Il codice sopr a r ipor tato pr ovocher à il seguente er r or e: 1. Methods in a Module cannot be declared 'Shared'. Inoltr e, è anche possibile acceder e a membr i di un modulo senza specificar e il nome del modulo, ad esempio: 01. Module Module2 02. Sub Hello() 03. Console.WriteLine("Hello!") 04. End Sub 05. End Module 06. 07. Module Module1 08. Sub Main() 09. Hello() ' = Module2.Hello() 10. Console.ReadKey() 11. End Sub 12. End Module Questo fenomeno è anche noto col nome di Im po r ts statico . A dir la ver ità esiste una piccola differ enza tr a classi statiche e moduli. Una classe può esser e statica anche solo se tutti i suoi membr i lo sono, ma non gode dell'Impor ts Statico. Un modulo, al contr ar io, oltr e ad aver e tutti i membr i shar ed, gode sempr e dell'Impor ts Statico. Per far la br eve: 01. Module Module2 02. Sub Hello() 03. Console.WriteLine("Hello Module2!") 04. End Sub 05. End Module 06. 07. Class Class2 08. Shared Sub Hello() 09. Console.WriteLine("Hello Class2!") 10. End Sub 11. End Class 12. 13. Module Module1 14. Sub Main() 15.
  • 90. 'Per richiamare l'Hello di Class2, è sempre 16. 'necessaria questa sintassi: 17. Class2.Hello() 18. 'Per invocare l'Hello di Module2, invece, basta 19. 'questa, a causa dell'Imports Statico 20. Hello() 21. Console.ReadKey() 22. End Sub 23. End Module
  • 91. A24. ArrayList, HashTable e SortedList Abbiamo già ampiamente visto e illustr ato il funzionamento degli ar r ay. Ho anche già detto più volte come essi non siano sempr e la soluzione miglior e ai nostr i pr oblemi di immagazzinamento dati. Infatti, è difficile decider ne la dimensione quando non si sa a pr ior i quanti dati ver r anno immessi: inoltr e, è oner oso in ter mini di tempo e r isor se modificar ne la lunghezza mentr e il pr ogr amma gir a; e nel caso contr ar io, è molto limitativo conceder e all'utente un numer o pr efissato massimo di valor i. A questo pr oposito, ci vengono in aiuto delle classi già pr esenti nelle libr er ie standar d del Fr amew or k .NET che aiutano pr opr io a gestir e insiemi di elementi di lunghezza var iabile. Di seguito ne pr opongo una br eve panor amica. Array List Si tr atta di una classe per la gestione di liste di elementi. Essendo un tipo r efer ence, quindi, segue che ogni oggetto dichiar ato come di tipo Ar r ayList debba esser e inizializzato pr ima dell'uso con un adeguato costr uttor e. Una volta cr eata un'istanza, la si può utilizzar e nor malmente. La differ enza con l'Ar r ay r isiede nel fatto che l'Ar r ayList, all'inizio della sua "vita", non contiene nessun elemento, e, di conseguenza occupa r elativamente meno memor ia. Infatti, quando noi inizializziamo un ar r ay, ad esempio così: 1. Dim A(100) As Int32 nel momento in cui questo codice viene eseguito, il pr ogr amma r ichiede 101 celle di memor ia della gr andezza di 4 bytes ciascuna da r iser var e per i pr opr i dati: che esse siano impostate o meno (all'inizio sono tutti 0), non ha impor tanza, per chè A occuper à sempr e la stessa quantità di memor ia. Al contr ar io l'Ar r ayList non "sa" nulla su quanti dati vor r emmo intr odur r e, quindi, ogni volta che un nuovo elemento viene intr odotto, esso si es pan de allocando dinamicamente nuova memor ia solo se ce n'è bisogno. In questo r isiede la potenza delle liste. Per aggiunger e un nuovo elemento all'ar r aylist bisogna usar e il metodo d'istanza Add, passandogli come par ametr o il valor e da aggiunger e. Ecco un esempio: 01. Module Module1 02. 03. Class Cube 04. '... 05. End Class 06. 07. Sub Main() 08. 'Crea un nuovo arraylist 09. Dim Cubes As New ArrayList 10. 11. Console.WriteLine("Inserismento cubi:") 12. Console.WriteLine() 13. Dim Cmd As Char 14. Do 15. Console.WriteLine() 16. Dim C As New Cube 17. 'Scrive il numero del cubo 18. Console.Write((Cubes.Count + 1) & " - ") 19. Console.Write("Lato (m): ") 20. C.SideLength = Console.ReadLine 21. 22. Console.Write(" Densità (kg/m<sup>3</sup>): ") 23. C.Density = Console.ReadLine 24. 25. 'Aggiunge un nuovo cubo alla collezione 26. Cubes.Add(C) 27. 28. Console.WriteLine("Termina inserimento? y/n") 29.
  • 92. Cmd = Console.ReadKey().KeyChar 30. Loop Until Char.ToLower(Cmd) = "y" 31. 32. 'Calcola la massa totale di tutti i cubi nella lista 33. Dim TotalMass As Single = 0 34. 'Notate che l'ArrayList si può usare come un 35. 'normale array. L'unica differenza sta nel fatto che 36. 'esso espone la proprietà Count al posto di Length. 37. 'In genere, tutte le liste espongono Count, che comunque 38. 'ha sempre lo stesso significato: restituisce il numero 39. 'di elementi nella lista 40. For I As Int32 = 0 To Cubes.Count - 1 41. TotalMass += Cubes(I).Mass 42. Next 43. 44. Console.WriteLine("Massa totale: " & TotalMass) 45. Console.ReadKey() 46. End Sub 47. End Module Allo stesso modo, è possibile r imuover e o inser ir e elementi con altr i metodi: Remove(x ) : r imuove l'elemento x dall'ar r aylist RemoveAt(x ) : r imuove l'elemento che si tr ova nella posizione x dell'Ar r ayList Index Of(x ) : r estituisce l'indice dell'elemento x Contains(x ) : r estituisce Tr ue se x è contenuto nell'Ar r ayList, altr imenti False Clear : pulisce l'ar r aylist eliminando ogni elemento Clone : r estituisce una copia esatta dell'Ar r ayList. Questo ar gomento ver r à discusso più in là nella guida. Hashtable L'Hashtable possiede un meccanismo di allocazione della memor ia simile a quello di un Ar r ayList, ma è concettualmente differ ente in ter mini di utilizzo. L'Ar r ayList, infatti, non si discosta molto, par lando di pr atica, da un Ar r ay - e infatti vediamo questa somiglianza nel nome: ogni elemento è pur sempr e contr addistinto da un indice, e mediante questo è possibile ottener ne o modificar ne il valor e; inoltr e, gli indici sono sempr e su base 0 e sono sempr e numer i inter i, gener almente a 32 bit. Quest'ultima peculiar ità ci per mette di dir e che in un Ar r ayList gli elementi sono logicamente or dinati. In un Hashtable, al contr ar io, tutto ciò che ho esposto fin'or a non vale. Questa nuova classe si basa sull'associazione di una chiav e (key) con un v alo r e (value). Quando si aggiunge un nuovo elemento all'Hashtable, se ne deve specificar e la chiave, che può esser e qualsiasi cosa: una str inga, un numer o, una data, un oggetto, ecceter a... Quando si vuole r ipescar e quello stesso elemento bisogna usar e la chiave che gli er a stata associata. Usando numer i inter i come chiavi si può s imulare il compor tamento di un Ar r ayList, ma il meccanismo intr inseco di questo tipo di collezione r imane pur sempr e molto diver so. Ecco un esempio: 01. 'Hashtabel contenente alcuni materiali e le 02. 'relative densità 03. Dim H As New Hashtable 04. 'Aggiunge un elemento, contraddistinto da una chiave stringa 05. H.Add("Acqua", 1000) 06. H.Add("Alluminio", 2700) 07. H.Add("Argento", 10490) 08. H.Add("Nichel", 8800) 09. 10. '... 11. 'Possiamo usare l'hashtable per associare 12. 'facilmente densità ai nostri cubi: 13. Dim C As New Cube(1, H("Argento")) Notar e che è anche possibile far e il contr ar io, ossia: 1. Dim H As New Hashtable 2.
  • 93. H.Add(1000, "Acqua") 3. H.Add(2700, "Alluminio") 4. H.Add(10490, "Argento") 5. H.Add(8800, "Nichel") In quest'ultimo esempio, l'Hashtable contiene quattr o chiavi costituite da valor i numer ici: non è comunque possibile ciclar le usando un For . Infatti, negli Ar r ayList e negli Ar r ay, abbiamo la gar anzia che se la collezione contiene 8 elementi, ad esempio, ci sar anno sempr e degli indici inter i validi tr a 0 e 7; con gli Hashtable, al contr ar io, non possiamo desumer e n ulla sulle chiavi osser vando il semplice numer o di elementi. In gener e, per iter ar e attr aver so gli elementi di un Hashtable, si usano dei costr utti For Each: 1. For Each V As String In H.Values 2. 'Enumera tutti gli elementi di H 3. ' V = "Acqua", "Alluminio", "Argento", ... 4. Next 1. For Each K As Int32 In H.Keys 2. 'Enumera tutte le chiavi 3. 'K = 1000, 2700, 10490, ... 4. Next Per l'iter azione ci vengono in aiuto le pr opr ietà Values e Keys, che contengono r ispettivamente tutti i valor i e tutte le chiavi dell'Hashtable: queste collezioni sono a sola lettur a, ossia non è possibile modificar le in alcun modo. D'altr onde, è abbastanza ovvio: se aggiungessimo una chiave l'Hashtable non sapr ebbe a quale elemento associar la. L'unico modo per modificar le è indir etto e consiste nell'usar e metodi come Add, Remove, ecceter a... che sono poi gli stessi di Ar r ayList. SortedList Si compor ta esattamente come un Hashtable, solo che gli elementi vengono mantenuti sempr e in or dine secondo la chiave.
  • 94. A25. Metodi factory Si definisce Factor y un metodo che ha come unico scopo quello di cr ear e una nuova istanza di una classe e r estituir e tale istanza al chiamante (dato che si par la di "r estituir e", i metodi Factor y sar anno sempr e funzioni). Or a, ci si potr ebbe chieder e per chè usar e metodi factor y al posto di nor mali costr uttor i. La differ enza tr a questi non è da sottovalutar e: i costr uttor i ser vono ad istanziar e un oggetto, ma, una volta avviati, non possono "fer mar si". Con questo voglio dir e che, qualor a venisser o r iscontr ati degli er r or i nei par ametr i di cr eazione dell'istanza (nel caso ce ne siano), il costr uttor e cr eer ebbe comunque un nuovo oggetto, ma molto pr obabilmente quest'ultimo conter r ebbe dati er r onei. Un metodo Factor y, invece, contr olla che tutto sia a posto pr im a di cr ear e il nuovo oggetto: in questo modo, se c'è qualcosa che non va, lo può comunicar e al pr ogr ammator e (o all'utente), ad esempio lanciando un'eccezione o visualizzando un messaggio di er r or e. E' convenzione - ma è anche logica - che un metodo Factor y sia definito sempr e all'inter no della stessa classe che cor r isponde al suo tipo di output e che sia Shar ed (altr imenti non si potr ebbe r ichiamar e pr ima della cr eazione dell'oggetto, ovviamente). Un esempio di quanto detto: 01. Module Module1 02. Class Document 03. 'Campo statico che contiene tutti i documenti 04. 'aperi fin'ora 05. Private Shared Documents As New Hashtable 06. 'Identificatore del documento: un paragrafo nel prossimo 07. 'capitolo spiegherà in dettaglio i significato e 08. 'l'utilità delle variabili ReadOnly 09. Private ReadOnly _ID As Int16 10. 'Nome del file e testo contenuto in esso 11. Private ReadOnly _FileName, _Text As String 12. 13. Public ReadOnly Property ID() As Int16 14. Get 15. Return _ID 16. End Get 17. End Property 18. 19. Public ReadOnly Property FileName() As String 20. Get 21. Return _FileName 22. End Get 23. End Property 24. 25. Public ReadOnly Property Text() As String 26. Get 27. Return _Text 28. End Get 29. End Property 30. 31. 'Da notare il costruttore Private: nessun client al di 32. 'fuori della classe può inizializzare il nuovo 33. 'oggetto. Solo il metodo factory lo può fare 34. Private Sub New(ByVal ID As Int16, ByVal Path As String) 35. Me._ID = ID 36. Me._FileName = Path 37. Me._Text = IO.File.ReadAllText(Path) 38. 'Me fa riferimento alla classe stessa 39. Documents.Add(ID, Me) 40. End Sub 41. 42. 'Il metodo factory crea un documento se non esiste l'ID 43. 'e se il percorso su disco è diverso, altrimenti 44. 'restituisce il documento che esiste già 45. Public Shared Function Create(ByVal ID As Int16, _ 46. ByVal Path As String) As Document 47. If Documents.ContainsKey(ID) Then 48. 'Ottiene il documento già esistente con questo ID 49.
  • 95. Dim D As Document = Documents(ID) 50. 'Se coincidono sia l'ID che il nome del file, 51. 'allora restituisce l'oggetto già esistente 52. If D.FileName = Path Then 53. Return D 54. Else 55. 'Altrimenti restituisce Nothing, dato che non 56. 'possono esistere due documenti con uguale ID, 57. 'o si farebbe confusione 58. Return Nothing 59. End If 60. End If 61. 'Se non esiste un documento con questo ID, lo crea 62. Return New Document(ID, Path) 63. End Function 64. End Class 65. 66. Sub Main() 67. Dim D As Document = Document.Create(0, "C:testo.txt") 68. Dim E As Document = Document.Create(0, "C:testo.txt") 69. Dim F As Document = Document.Create(0, "C:file.txt") 70. Dim G As Document = Document.Create(1, "C:file.txt") 71. 72. 'Dimostra che se ID e Path coincidono, i due oggetti 73. 'sono la stessa istanza 74. Console.WriteLine(E Is D) 75. 'Dimostra che se l'ID esiste già, ma il Path differisce, 76. 'l'oggetto restituito è Nothing 77. Console.WriteLine(F Is Nothing) 78. Console.ReadKey() 79. End Sub 80. End Module Il codice sopr a r ipor tato cr ea volutamente tutte le situazioni contemplate all'inter no del metodo factor y statico: E ha gli stessi par ametr i di D, quindi nel metodo factor y usato per cr ear e E viene r estituita l'istanza D già esistente; F ha lo stesso ID, quindi è Nothing. A pr ova di ciò, sullo scher mo appar ir à il seguente output: 1. True 2. True Classi fac tory e oggetti immutabili Una classe contenente solo metodi factor y è detta classe factor y. Il più delle volte, l'uso di una tattica simile a quella sopr a r ipor tata potr ebbe por tar e alcuni dubbi: dato che esistono due var iabili che puntano alla stessa istanza, il modificar ne l'una potr ebbe causar e l'automatica modifica dell'altr a. Tuttavia, spesse volte, gli oggetti che possono esser e cr eati con metodi factor y non espongono alcun altr o metodo per la modifica o l'eliminazione dello stesso oggetto, che quindi non può esser e cambiato in alcun modo. Oggetti di questo tipo sono detti im m utabili: un esempio di oggetti immutabili sono la str inghe. Al contr ar io di come si potr ebe pensar e, una volta cr eate il lor o valor e non può esser e cambiato: l'unica cosa che si può far e è assegnar e alla var iabile str inga un nuovo valor e: 1. 'Questa stringa è immutabile 2. Dim S As String = "Ciao" 3. 'Viene creata una nuova stringa temporanea con valore "Buongiorno" 4. 'e assegnata a S. "Ciao" verrà distrutta dal Garbage Colletcion 5. S = "Buongiorno"
  • 96. A26. Costruttori Come si è accennato nelle pr ecedenti lezioni, i costr uttor i ser vono a cr ear e un oggetto, un'istanza mater iale della classe. Ogni costr uttor e, poichè ce ne può esser e anche più di uno, è sempr e dichiar ato usando la keyw or d New e non può esser e altr imenti. Si possono passar e par ametr i al costr uttor e allo stesso modo di come si passano alle nor mali pr ocedur e o funzioni, specificandoli tr a par entesi. Il codice scr itto nel costr uttor e viene eseguito pr ima di ogni altr o metodo nella classe, per ciò può anche modificar e le var iabili r ead-only (in sola lettur a), come vedr emo in seguito. Anche i moduli possono aver e un costr uttor e e questo viene eseguito pr ima della pr ocedur a Main. Una cosa da tener e bene a mente è che, nonostante New sia eseguito pr ima di ogni altr a istr uzione, sia le costanti sia i campi con inizializzator e (ad esempio Dim I As Int32 = 50) sono già stati inizializzati e contengono già il lor o valor e. Esempio: 01. Module Module1 02. 'Classe 03. Class Esempio 04. 'Costante pubblica 05. Public Const Costante As Byte = 56 06. 'Variabile pubblica che non pu� essere modificata 07. Public ReadOnly Nome As String 08. 'Variabile privata 09. Private Variabile As Char 10. 11. 'Costruttore della classe: accetta un parametro 12. Sub New(ByVal Nome As String) 13. Console.WriteLine("Sto inizializzando un oggetto Esempio...") 14. 'Le variabili ReadOnly sono assegnabli solo nel 15. 'costruttore della classe 16. Me.Nome = Nome 17. Me.Variabile = "c" 18. End Sub 19. End Class 20. 21. 'Costruttore del Modulo 22. Sub New() 23. Console.WriteLine("Sto inizializzando il Modulo...") 24. End Sub 25. 26. Sub Main() 27. Dim E As New Esempio("Ciao") 28. E.Nome = "Io" ' Sbagliato: Nome è ReadOnly 29. Console.ReadKey() 30. End Sub 31. End Module Quando si fa cor r er e il pr ogr amma si ha questo output: 1. Sto inizializzando il Modulo... 2. Sto inizializzando un oggetto Esempio... L'esempio mostr a l'or dine in cui vengono eseguiti i costr uttor i: pr ima viene inizializzato il modulo, in seguito viene inizializzato l'oggetto E, che occupa la pr ima linea di codice della pr ocedur a Main. È evidente che Main viene eseguita dopo New . V ariabili ReadOnly Ho par lato pr ima delle var iabili ReadOnly e ho detto che possono solamente esser e lette ma non modificate. La domanda che viene spontaneo por si è: non sar ebbe meglio usar e una costante? La differ enza è più mar cata di quanto sembr i: le costanti devono esser e inizializzate con un valor e immutabile, ossia che definisce il pr ogr ammator e mentr e scr ive il codice (ad esempio, 1, 2, "Ciao" ecceter a); la var iabili ReadOnly possono esser e impostate nel costr uttor e, ma,
  • 97. cosa più impor tante, possono assumer e il valor e der ivante da un'espr essione o da una funzione. Ad esempio: 1. Public Const Data_Creazione_C As Date = Date.Now 'Sbagliato! 2. Public ReadOnly Data_Creazione_V As Date = Date.Now ' Giusto La pr ima istr uzione gener a un er r or e "Costant ex pr ession is r equir ed!" ("È r ichiesta un'espr essione costante!"), der ivante dal fatto che Date.Now è una funzione e, come tale, il suo valor e, pur pr eso una sola volta, non è costante, ma può var iar e. Non si pone nessun pr oblema, invece, per le var iabili ReadOnly, poichè sono sempr e var iabili. Costruttori Shared I costr uttor i Shar ed sono detti co str utto r i statici e vengono eseguiti solamente quando è cr eata la pr im a istanza di una data classe: per questo sono detti anche co str utto r i di classe o di tipo poichè non appar tengono ad ogni singolo oggetto che da quella classe pr ende la str uttur a, ma piuttosto alla classe stessa (vedi differ enza tr a classe e oggetto). Un esempio di una possibile applicazione può esser e questo: si sta scr ivendo un pr ogr amma che tiene tr accia di ogni er r or e r ipor tandolo su un file di log, e gli er r or i vengono gestiti da una classe Er r or s. Data la str uttur a dell'applicazione, possono esister e più oggetti di tipo Er r or s, ma tutti devono condivider e un file comune... Come si fa? Costr uttor e statico! Questo fa in modo che si apr a il file di log solamente una volta, ossia quando viene istanziato il pr imo oggetto Er r or s. Esempio: 01. Module Esempio 02. Class Errors 03. 'Variabile statica che rappresenta un oggetto in grado 04. 'di scrivere su un file 05. Public Shared File As IO.StreamWriter 06. 07. 'Costruttore statico che inizializza l'oggetto StreamWriter 08. 'Da notare è che un costruttore statico NON può avere 09. 'parametri: il motivo è semplice. Se li potesse avere 10. 'e ci fossero più costruttori normali il compilatore 11. 'non saprebbe cosa fare, poichè Shared Sub New 12. 'potrebbe avere parametri diversi dagli altri 13. Shared Sub New() 14. Console.WriteLine("Costruttore statico: sto creando il log...") 15. File = New IO.StreamWriter("Errors.log") 16. End Sub 17. 18. 'Questo è il costruttore normale 19. Sub New() 20. Console.WriteLine("Costruttore normale: sto creando un oggetto...") 21. End Sub 22. 23. Public Sub WriteLine(ByVal Text As String) 24. File.WriteLine(Text) 25. End Sub 26. End Class 27. 28. Sub Main() 29. 'Qui viene eseguito il costruttore statico e quello normale 30. Dim E1 As New Errors 31. 'Qui solo quello normale 32. Dim E2 As New Errors 33. 34. E1.WriteLine("Nessun errore") 35. 36. Console.ReadKey() 37. End Sub 38. End Module L'ouput è: 1. Costruttore statico: sto creando il log... 2. Costruttore normale: sto creando un oggetto... 3. Costruttore normale: sto creando un oggetto...
  • 98. Questo esempio evidenzia bene come vengano eseguiti i costr uttor i: mentr e si cr ea il pr imo oggetto Er r or s in assoluto viene eseguito quello statico e in più anche quello nor male, per i successivi, invece, solo quello nor male. Ovviamente non tr over e il file Er r or s.log con la scr itta "Nessun er r or e" poichè nell'esempio il file non è stato chiuso. Ripr ender emo lo stesso discor so con i distr uttor i. Costruttori Friend e Private I costr uttor i possono esser e specificati come Fr iend e Pr ivate pr opr io come ogni altr o membr o di classe. Tuttavia l'uso degli specificator i di accesso sui costr uttor i ha par ticolar i effetti collater ali. Dichiar ar e un costr uttor e Pr ivate, ad esempio, equivale e impor r e che niente possa inizializzar e l'oggetto al di fuor i della classe stessa: questo caso par ticolar e è stato analizzato nella lezione pr ecedente con i metodi factor y statici e ser ve a r ender e obbligator io l'uso di questi ultimi. Un costr uttor e Fr iend invece r ende la classe inizializzabile da ogni par te del pr ogetto cor r ente: se un client ester no utilizzasse la classe impor tandola da una libr er ia (vedi oltr e) non potr ebbe usar ne il costr uttor e.
  • 99. A27. Gli Operatori Gli oper ator i sono speciali metodi che per mettono di eseguir e, appunto, oper azioni tr a due valor i mediante l'uso di un simbolo (ad esempio, + per la somma, - per la differ enza, ecceter a...). Quando facciamo i calcoli, comunemente usando i tipi base numer ici del Fr amew or k, come Int16 o Double, usiamo pr aticamente sempr e degli oper ator i. Essi non sono nulla di "str aor dinar io", nel senso che anche se non sembr a, data la lor o par ticolar e sintassi, sono pur sempr e definiti all'inter no delle var ie classi come nor mali membr i (statici). Gli oper ator i, come i tipi base, del r esto, non si sottr aggono alla globale astr azione degli linguaggi or ientati agli oggetti: tutto è sempr e incasellato al posto giusto in una qualche classe. Ma questo lo vedr emo più avanti quando par ler ò della Reflection. Sor volando su questa br eve par entesi idilliaca, tor niamo all'aspetto più concr eto di questo capitolo. Anche il pr ogr ammator e ha la possibilità di defin ire nuovi oper ator i per i tipi che ha cr eato: ad esempio, può scr iver e oper ator i che oper ino tr a str uttur e e tr a classi. In gener e, si pr efer isce adottar e gli oper ator i nel caso delle str uttur e poiché, essendo tipi value, si pr estano meglio - come idea, più che altr o - al fatto di subir e oper azioni tr amite simboli. Venendo alla pr atica, la sintassi gener ale di un oper ator e è la seguente: 1. Shared Operator [Simbolo]([Parametri]) As [Tipo Restituito] 2. '... 3. Return [Risultato] 4. End Operator Come si vede, la sintassi è molto simile a quella usata per dichiar ar e una funzione, ad eccezione della keyw or d e dell'identificator e. Inoltr e, per far sì che l'oper ator e sia non solo sintatticamente, ma anche semanticamente valido, devono esser e soddisfatte queste condizioni: L'oper ator e deve SEM PRE esser e dichiar ato come Shar ed, ossia statico. Infatti, l'oper ator e r ientr a nel dominio della classe in sé e per sé, appar tiene al tipo, e non ad un'istanza in par ticolar e. Infatti, l'oper ator e può esser e usato per eseguir e oper azioni tr a tutte le istanze possibili della classe. Anche se viene definito in una str uttur a, deve comunque esser e Shar ed. Infatti, sebbene il concetto di str uttur a si pr esti di meno a questa "visione" un po' assiomatica del concetto di istanza, è pur sempr e ver o che possono esister e tante var iabili diver se contenenti dati diver si, ma dello stesso tipo str uttur ato. L'oper ator e può specificar e al m assim o due par ametr i (si dice unar io se ne specifica uno, e binar io se due), e di questi almeno uno DEVE esser e dello stesso tipo in cui l'oper ator e è definito - tipicamente il pr imo dei due deve soddisfar e questa seconda condizione. Questo r isulta abbastanza ovvio: se avessimo una str uttur a Fr azione, come fr a poco mostr er ò, a cosa ser vir ebbe dichiar ar vi all'inter no un oper ator e + definito tr a due numer i inter i? A par te il fatto che esiste già, è logico aspettar si che, dentr o un nuovo tipo, si descr ivano le istr uzioni necessar ie ad oper ar e con quel nuovo tipo, o al massimo ad attuar e calcoli tr a questo e i tipi già esistenti. Il simbolo che contr addistingue l'oper ator e dev e esser e scelto tr a quelli disponibili, di cui qui r ipor to un elenco con annessa descr izione della funzione che usualmente l'oper ator e r icopr e: + (somma) - (differ enza) * (pr odotto) / (divisione) (divisione inter a) ^ (potenza) & (concatenazione) = (uguaglianza) > (maggior e)
  • 100. < (minor e) >= (maggior e o uguale) <= (minor e o uguale) >> (shift destr o dei bit) << (shift sinistr o dei bit) And (inter sezione logica) Or (unione logica) Not (negazione logica) Xor (aut logico) Mod (r esto della divisione inter a) Like (r icer ca di un patter n: di solito il pr imo ar gomento indica dove cer car e e il secondo cosa cer car e) IsTr ue (è ver o) IsFalse (è falso) CType (conver sione da un tipo ad un altr o) Sintatticamente par lando, nulla vieta di usar e il simbolo And per far e una somma, ma sar ebbe meglio attener si alle nor mali nor me di utilizzo r ipor tate. Ed ecco un esempio: 001. Module Module1 002. 003. Public Structure Fraction 004. 'Numeratore e denominatore 005. Private _Numerator, _Denumerator As Int32 006. 007. Public Property Numerator() As Int32 008. Get 009. Return _Numerator 010. End Get 011. Set(ByVal value As Int32) 012. _Numerator = value 013. End Set 014. End Property 015. 016. Public Property Denumerator() As Int32 017. Get 018. Return _Denumerator 019. End Get 020. Set(ByVal value As Int32) 021. If value <> 0 Then 022. _Denumerator = value 023. Else 024. 'Il denominatore non può mai essere 0 025. 'Dovremmo lanciare un'eccezione, ma vedremo più 026. 'avanti come si fa. Per ora lo impostiamo a uno 027. _Denumerator = 1 028. End If 029. End Set 030. End Property 031. 032. 'Costruttore con due parametri, che inizializza numeratore 033. 'e denominatore 034. Sub New(ByVal N As Int32, ByVal D As Int32) 035. Me.Numerator = N 036. Me.Denumerator = D 037. End Sub 038. 039. 'Restituisce la Fraction sottoforma di stringa 040. Function Show() As String 041. Return Me.Numerator & " / " & Me.Denumerator 042. End Function 043. 044. 'Semplifica la Fraction 045. Sub Semplify() 046.
  • 101. Dim X As Int32 047. 048. 'Prende X come il valore meno alto in modulo 049. 'e lo inserisce in X. X servirà per un 050. 'calcolo spicciolo del massimo comune divisore 051. X = Math.Min(Math.Abs(Me.Numerator), Math.Abs(Me.Denumerator)) 052. 053. 'Prima di iniziare, per evitare errori, controlla 054. 'se numeratore e denominatore sono entrambi negativi: 055. 'in questo caso li divide per -1 056. If (Me.Numerator < 0) And (Me.Denumerator < 0) Then 057. Me.Numerator /= -1 058. Me.Denumerator /= -1 059. End If 060. 061. 'E con un ciclo scova il valore più alto di X 062. 'per cui sono divisibili sia numeratore che denominatore 063. '(massimo comune divisore) e li divide per quel numero. 064. 065. 'Continua a decrementare X finché non trova un 066. 'valore per cui siano divisibili sia numeratore che 067. 'denominatore: dato che era partito dall'alto, questo 068. 'sarà indubbiamente il MCD 069. Do Until ((Me.Numerator Mod X = 0) And (Me.Denumerator Mod X = 0)) 070. X -= 1 071. Loop 072. 073. 'Divide numeratore e denominatore per l'MCD 074. Me.Numerator /= X 075. Me.Denumerator /= X 076. End Sub 077. 078. 'Somma due frazioni e restituisce la somma 079. Shared Operator +(ByVal F1 As Fraction, ByVal F2 As Fraction) _ 080. As Fraction 081. Dim F3 As Fraction 082. 083. 'Se i denumeratori sono uguali, si limita a sommare 084. 'i numeratori 085. If F1.Denumerator = F2.Denumerator Then 086. F3.Denumerator = F1.Denumerator 087. F3.Numerator = F1.Numerator + F2.Numerator 088. Else 089. 'Altrimenti esegue tutta l'operazione 090. 'x a x*b + a*y 091. '- + - = --------- 092. 'y b y*b 093. F3.Denumerator = F1.Denumerator * F2.Denumerator 094. F3.Numerator = F1.Numerator * F2.Denumerator + F2.Numerator * F1.Denumerator 095. End If 096. 097. 'Semplifica la Fraction 098. F3.Semplify() 099. Return F3 100. End Operator 101. 102. 'Sottrae due Fraction e restituisce la differenza 103. Shared Operator -(ByVal F1 As Fraction, ByVal F2 As Fraction) _ 104. As Fraction 105. 'Somma l'opposto del secondo membro 106. F2.Numerator = -F2.Numerator 107. Return F1 + F2 108. End Operator 109. 110. 'Moltiplica due frazioni e restituisce il prodotto 111. Shared Operator *(ByVal F1 As Fraction, ByVal F2 As Fraction) _ 112. As Fraction 113. 'Inizializza F3 con il numeratore pari al prodotto 114. 'dei numeratori e il denominatore pari al prodotto dei 115. 'denominatori 116. Dim F3 As Fraction = New Fraction(F1.Numerator * F2.Numerator, _ 117. F1.Denumerator * F2.Denumerator) 118.
  • 102. F3.Semplify() 119. Return F3 120. End Operator 121. 122. 'Divide due frazioni e restituisce il quoziente 123. Shared Operator /(ByVal F1 As Fraction, ByVal F2 As Fraction) _ 124. As Fraction 125. 'Inizializza F3 eseguendo l'operazione: 126. 'a x a y 127. '- / - = - * - 128. 'b y b x 129. Dim F3 As Fraction = New Fraction(F1.Numerator * F2.Denumerator, _ 130. F1.Denumerator * F2.Numerator) 131. F3.Semplify() 132. Return F3 133. End Operator 134. End Structure 135. 136. Sub Main() 137. Dim A As New Fraction(8, 112) 138. Dim B As New Fraction(3, 15) 139. 140. A.Semplify() 141. B.Semplify() 142. Console.WriteLine(A.Show()) 143. Console.WriteLine(B.Show()) 144. 145. Dim C As Fraction = A + B 146. Console.WriteLine("A + B = " & C.Show()) 147. 148. Console.ReadKey() 149. End Sub 150. End Module CTy pe CType è un par ticolar e oper ator e che ser ve per conver tir e da un tipo di dato ad un altr o. Non è ancor a stato intr odotto nei pr ecedenti capitoli, ma ne par ler ò più ampiamente in uno dei successivi. Scr ivo comunque un par agr afo a questo r iguar do per amor di completezza e utilità di consultazione. Come è noto, CType può eseguir e conver sioni da e ver so tipi conosciuti: la sua sintassi, tuttavia, potr ebbe sviar e dalla cor r etta dichiar azione. Infatti, nonostante CType accetti due par ametr i, la sua dichiar azione ne implica uno solo, ossia il tipo che si desider a conver tir e, in questo caso Fr action. Il secondo par ametr o è implicitamente indicato dal tipo di r itor no: se scr ivessimo "CType(ByVal F As Fr action) As Double", questa istr uzione gener er ebbe un CType in gr ado di conver tir e dal tipo Fr action al tipo Double nella manier a consueta in cui siamo abituati: 1. Dim F As Fraction 2. '... 3. Dim D As Double = CType(F, Double) La dichiar azione di una conver sione ver so Double gener a automaticamente anche l'oper ator e CDbl, che si può usar e tr anquillamente al posto della ver sione completa di CType. Or a conviene por r e l'accento sul come CType viene dichiar ato: la sua sintassi non è speciale solo per chè può esser e confuso da unar io a binar io, ma anche per chè deve dichiar ar e sem pr e se una conver sione è W idening (di espansione, ossia senza per dita di dati) o Nar r o w ing (di r iduzione, con possibile per dita di dati). Per questo motivo si deve specificar e una delle suddette keyw or d tr a Shar ed e Oper ator . Ad esempio: Fr action r appr esenta un numer o r azionale e, sebbene Double non r appr esenti tutte le cifr e di un possibile numer o per iodico, possiamo consider ar e che nel passaggio ver so i Double non ci sia per dita di dati nè di pr ecisione in modo r ilevante. Possiamo quindi definir e la conver sione Widening: 1. Shared Widening Operator CType(ByVal F As Fraction) As Double 2. Return F.Numerator / F.Denumerator 3. End Operator
  • 103. Invece, la conver sione ver so un numer o inter o implica non solo una per dita di pr ecisione r ilevante ma anche di dati, quindi la definir emo Nar r ow ing: 1. Shared Narrowing Operator CType(ByVal F As Fraction) As Int32 2. 'Notare l'operatore di divisione intera (per maggiori 3. 'informazioni sulla divisione intera, vedere capitolo A6) 4. Return F.Numerator F.Denumerator 5. End Operator Operatori di c onfronto Gli oper ator i di confr onto godono anch'essi di una car atter istica par ticolar e: devono sempr e esser e definiti in coppia, < con >, = con <>, <= con >=. Non può infatti esister e un modo per ver ificar e se una var iabile è minor e di un altr a e non se è maggior e. Se manca uno degli oper ator i complementar i, il compilator e visualizzer à un messaggio di er r or e. Ovviamente, il tipo r estituito dagli oper ator i di confr onto sar à sempr e Boolean, poiché una condizione può esser e solo o ver a o falsa. 01. Shared Operator <(ByVal F1 As Fraction, ByVal F2 As Fraction) As Boolean 02. 'Converte le frazioni in double e confronta questi valori 03. Return (CType(F1, Double) < CType(F2, Double)) 04. End Operator 05. 06. Shared Operator >(ByVal F1 As Fraction, ByVal F2 As Fraction) As Boolean 07. Return (CDbl(F1) > CDbl(F2)) 08. End Operator 09. 10. Shared Operator =(ByVal F1 As Fraction, ByVal F2 As Fraction) As Boolean 11. Return (CDbl(F1) = CDbl(F2)) 12. End Operator 13. 14. Shared Operator <>(ByVal F1 As Fraction, ByVal F2 As Fraction) As Boolean 15. 'L'operatore "diverso" restituisce sempre un valore opposto 16. 'all'operatore "uguale" 17. Return Not (F1 = F2) 18. End Operator È da notar e che le espr essioni come (a=b) o (a-c>b) r estituiscano un valor e booleano. Possono anche esser e usate nelle espr essioni, ma è sconsigliabile, in quanto il valor e di Tr ue è spesse volte confuso: in VB.NET è -1, ma a r untime è 1, mentr e negli altr i linguaggi è sempr e 1. Queste espr essioni possono tuttavia esser e assegnate con sicur ezza ad altr i valor i booleani: 1. '... 2. a = 10 3. b = 20 4. Console.WriteLine("a è maggiore di b: " & (a > b)) 5. 'A schermo compare: "a è maggiore di b: False"
  • 104. A28. Differenze tra classi e strutture Nel cor so dei pr ecedenti capitoli ho più volte detto che le classi ser vono per cr ear e nuovi tipi e aggiunger e a questi nuove funzionalità, così da estender e le nor mali capacità del Fr amew or k, ma ho detto la stessa cosa delle str uttur e, magar i enfatizzandone di meno l'impor tanza. Le classi possono espor r e campi, e le str uttur e anche; le classi possono espor r e pr opr ietà, e le stuttur e anche; le classi possono espor r e metodi, e le str uttur e anche; le classi possono espor r e costr uttor i, e le str uttur e anche; le classi e i membr i di classe possono aver e specificator i di accesso, e le str uttur e e i lor o membr i anche. Insomma... a dir la tutta sembr er ebbe che classi e str uttur e siano concetti un po' r idondanti, cr eati solo per aver e un tipo r efer ence e un tipo value, ma in definitiva molto simili. Ovviamente non avr ei scr itto questo capitolo se le cose fosser o state r ealmente così. Le classi sono infinitamente più potenti delle str uttur e e fr a pochissimo capir ete il per chè. Memorizzazione Iniziamo col chiar ir e un aspetto già noto. Le str uttur e sono tipi value, mentr e le classi sono tipi r efer ence. Ripetendo concetti già spiegati pr ecedentemente, le pr ime vengono collocate dir ettamente sullo stack, ossia sulla memor ia pr incipale, nello spazio r iser vato alle var iabili del pr ogr amma, mentr e le seconde vengono collocate in un'altr a par te della memor ia (heap managed) e pongono sullo stack solo un puntator e alla lor o ver a locazione. Questo significa pr incipalmente due cose: L'accesso a una str uttur a e ai suoi membr i è più r apido di un accesso ad una classe; La classe occupa più memor ia, a par ità di membr i (almeno 6 bytes in più). Inoltr e, una str uttur a si pr esta meglio alla memor izzazione "linear e", ed è infatti gr andemente pr efer ita quando si esegue il mar shalling dei dati (ossia la lor o tr asfor mazione da entità alla pur a r appr esentazione in memor ia, costituita da una semplice ser ie di bits). In questo modo, per pr ima cosa è molto più facile legger e e scr iver e str uttur e in memor ia se si devono attuar e oper azioni di basso livello, ed è anche possibile r ispar miar e spazio usando un'oppor tuna disposizione delle var iabili. Le classi, al contr ar io, non sono così or dinate, ed è meno facile manipolar le. Non mi addentr er ò oltr e in questo ambito, ma, per chi volesse, ci sono delle mie dispense che spiegano come funziona la memor izzazione delle str uttur e. Identità Un'altr a conseguenza del fatto che le classi siano tipi r efer ence consiste in questo: due oggetti, a par ità di campi, sono sem pr e diver si, poiché si tr atta di due istanze distinte, seppur contenti gli stessi dati. Due var iabili di tipo str uttur ato che contengono gli stessi dati, invece, sono uguali, per chè non esiste il concetto di istanza per i tipi value. I tipi value sono, per l'appunto, v alo r i, ossia semplici dati, infor mazione pur a, ammasso di bits, né più né meno. Per questo motivo, ad esempio, è impossibile modificar e una pr opr ietà di una str uttur a tr amite l'oper ator e punto, poiché sar ebbe come tentar e di modificar e la par te decimale di 1.23: 1.23 è sempr e 1.23, si tr atta di un valor e e non lo si può modificar e, ma al massimo si può assegnar e un altr o valor e alla var iabile che lo contiene. Al contr ar io, gli oggetti sono entità più complesse: non si tr atta di "infor mazione pur a" come i tipi str uttur ati. Un oggetto contiene molteplici campi e pr opr ietà sempr e modificabili, per chè indicano solo un aspetto dell'oggetto: ad esempio, il color e di una par ete è sempr e modificabile: basta tinteggiar e la par ete con un nuovo color e. Come dir e che "la par ete" non è come un numer o, che è sempr e quello e basta: essa è un qualcosa di concr eto con diver se pr opr ietà. Sono concetti molto astr atti e per cer ti ver si molto ar dui da capir e di pr imo acchito... io ho tentato di far e esempi convinceti, ma sper o che con il tempo impar er ete da soli a inter ior izzar e queste differ enze - differ enze che, pur
  • 105. essendo impor tanti, non sono le più impor tanti. Paradigma di programmazione ad oggetti Ed eccoci ar r ivati al punto caldo della discussione. La sostanziale differ enza che separ a nettamente str uttur e da classi è l'ader enza ai dettami del par adigma di pr ogr ammazione ad oggetti, in par ticolar e ad er editar ietà e polimor fismo. Le classi possono er editar e cer ti membr i da altr e classi e modificar ne il funzionamento. Le str uttur e non possono far e questo. Inoltr e, le classi possono implementar e inter facce, ossia sistemar e i pr opr i membr i per ader ir e a scheletr i di base: le str uttur e non per mettono di far e neppur e questo. Queste tr e car ater istiche (ma le pr ime due in par ticolar e) sono potenti str umenti a disposizione del pr ogr ammator e, e nei pr ossimi capitoli le analizzer emo nel dettaglio.
  • 106. A29. L'Ereditarietà Eccoci ar r ivati a par lar e degli aspetti peculiar i di un linguaggio ad oggetti! Iniziamo con l'Eder editar ietà. L'ereditarietà è la possibilità di un linguaggio ad oggetti di far der ivar e una classe da un'altr a: in questo caso, la pr ima assume il nome di classe der iv ata, mentr e la seconda quello di classe base. La classe der ivata acquisisce tutti i membr i della classe base, ma può r idefinir li o aggiunger ne di nuovi. Questa car atter istica di ogni linguaggio Object Or iented è par ticolar mente efficace nello schematizzar e una r elazione "is-a" (ossia "è un"). Per esempio, potr emmo definir e una classe Vegetale, quindi una nuova classe Fior e, che er edita Vegetale. Fior e è un Vegetale, come mostr a la str uttur a ger ar chica dell'er editar ietà. Se definissimo un'altr a classe Pr imula, der ivata da Fior e, dir emmo che Pr imula è un Fior e, che a sua volta è un Vegetale. Quest'ultimo tipo di r elazione, che cr ea classi der ivate che sar anno basi per er editar e altr e classi, si chiama er editar ietà indir e tta. Passiamo or a a veder e come si dichiar a una classe der ivata: 1. Class [Nome] 2. Inherits [Classe base] 3. 'Membri della classe 4. End Class La keyw or d Inher its specifica quale classe base er editar e: si può aver e solo UNA dir ettiva Inher its per classe, ossia non è possibile er editar e più classi base. In questo fr angente, si può scopr ir e come le pr opr ietà siano utili e flessibili: se una classe base definisce una var iabile pubblica, questa diver r à par te anche della classe der ivata e su tale var iabile ver r anno basate tutte le oper azioni che la coinvolgono. Siccome è possibile che la classe der ivata voglia r idefinir e tali oper azioni e molto pr obabilmente anche l'utilizzo della var iabile, è sempr e consigliabile dichiar ar e campi Pr ivate avvolti da una pr opr ietà, poichè non c'è mai alcun per icolo nel modificar e una pr opr ietà in classi der ivate, ma non è possibile modificar e i campi nella stessa classe. Un semplice esempio di er editar ietà: 01. Class Person 02. 'Per velocizzare la scrittura del codice, assumiamo che 03. 'questi campi pubblici siano proprietà 04. Public FirstName, LastName As String 05. 06. Public ReadOnly Property CompleteName() As String 07. Get 08. Return FirstName & " " & LastName 09. End Get 10. End Property 11. End Class 12. 13. 'Lo studente, ovviamente, è una persona 14. Class Student 15. 'Student eredita da Person 16. Inherits Person 17. 18. 'In più, definisce anche questi campi pubblici 19. 'La scuola frequentata 20. Public School As String 21. 'E l'anno di corso 22. Public Grade As Byte 23. End Class In seguito, si può utilizzar e la classe der ivata come si è sempr e fatto con ogni altr a classe. Nel far ne uso, tuttavia, è necessar io consider ar e che una classe der ivata possiede non solo i membr i che il pr ogr ammator e ha esplicitamente definito nel suo cor po, ma anche tutti quei membr i pr esenti nella classe base che si sono implicitamente acquisiti nell'atto stesso di scr iver e "Inher its". Se vogliamo, possiamo assimilar e una classe ad un insieme, i cui elementi sono i
  • 107. suoi membr i: una classe base è sottoinsieme della cor r ispondente classe der ivata. Di solito, l'ambiente di sviluppo aiuta molto in questo, poiché, nei sugger imenti pr oposti dur ante la scr ittur a del codice, vengono automaticamente inser ite anche le voci er editate da altr e classi. Ciò che abbiamo appena visto vale anche per er editar ietà indir etta: se A er edita da B e B er edita da C, A dispor r à dei membr i di B, alcuni dei quali sono anche membr i di C (semplice pr opr ietà tr ansitiva). Or a, per ò, bisogna por r e un bel Nota Bene alla questione. Infatti, non tutto è semplice come sembr a. For se nessuno si è chiesto che fine fanno gli specificator i di accesso quando un membr o viene er editato da una classe der ivata. Ebbene, esistono delle pr ecise r egole che indicano come gli scope vengono tr attati quando si er edita: Un membr o Public o Fr iend della classe base diventa un membr o Public o Fr iend della classe der ivata (in pr atica, non cambia nulla; viene er editato esattamente com'è); Un membr o Pr iv ate della classe base non è accessibile dalla classe der ivata, poichè il suo ambito di visibilità impedisce a ogni chiamante ester no alla classe base di far vi r ifer imento, come già visto nelle lezioni pr ecedenti; Un membr o Pr o tected della classe base diventa un membr o Pr otected della classe der ivata, ma si compor ta come un membr o Pr ivate. Ed ecco che abbiamo intr odotto uno degli specificator i che ci er avamo lasciati indietr o. I membr i Pr otected sono par ticolar mente utili e costituiscono una sor ta di "scappatoia" al fatto che quelli pr ivati non subiscono l'er editar ietà. Infatti, un memebr o Pr otected si compor ta esattamente come uno Pr ivate, con un'unica eccezione: è er editabile, ed in questo caso diventa un membr o Pr otected della classe der ivata. Lo stesso discor so vale anche per Pr otected Fr iend. Ecco uno schema che esemplifica il compor tamento dei pr incipali Scope: Esempio: 001. Module Esempio 002. Class Person 003. 'Due campi protected 004. Protected _FirstName, _LastName As String 005. 'Un campo private readonly: non c'è ragione di rendere 006. 'questo campo Protected poichè la data di nascita non 007. 'cambia ed è sempre accessibile tramite la proprietà 008. 'pubblica BirthDay 009. Private ReadOnly _BirthDay As Date 010. 011. Public Property FirstName() As String 012. Get 013. Return _FirstName 014. End Get 015. Set(ByVal Value As String) 016. If Value <> "" Then 017. _FirstName = Value 018. End If 019. End Set 020. End Property 021. 022. Public Property LastName() As String 023. Get 024. Return _LastName 025. End Get 026. Set(ByVal Value As String) 027. If Value <> "" Then 028.
  • 108. _LastName = Value 029. End If 030. End Set 031. End Property 032. 033. Public ReadOnly Property BirthDay() As Date 034. Get 035. Return _BirthDay 036. End Get 037. End Property 038. 039. Public ReadOnly Property CompleteName() As String 040. Get 041. Return _FirstName & " " & _LastName 042. End Get 043. End Property 044. 045. 'Costruttore che accetta tra parametri obbligatori 046. Sub New(ByVal FirstName As String, ByVal LastName As String, _ 047. ByVal BirthDay As Date) 048. Me.FirstName = FirstName 049. Me.LastName = LastName 050. Me._BirthDay = BirthDay 051. End Sub 052. End Class 053. 054. 'Lo studente, ovviamente, è una persona 055. Class Student 056. 'Student eredita da Person 057. Inherits Person 058. 059. 'La scuola frequentata 060. Private _School As String 061. 'E l'anno di corso 062. Private _Grade As Byte 063. 064. Public Property School() As String 065. Get 066. Return _School 067. End Get 068. Set(ByVal Value As String) 069. If Value <> "" Then 070. _School = Value 071. End If 072. End Set 073. End Property 074. 075. Public Property Grade() As Byte 076. Get 077. Return _Grade 078. End Get 079. Set(ByVal Value As Byte) 080. If Value > 0 Then 081. _Grade = Value 082. End If 083. End Set 084. End Property 085. 086. 'Questa nuova proprietà si serve anche dei campi FirstName 087. 'e LastName nel modo corretto, poichè sono Protected anche 088. 'nella classe derivata e fornisce un profilo completo 089. 'dello studente 090. Public ReadOnly Property Profile() As String 091. Get 092. 'Da notare l'accesso a BirthDay tramite la proprietà 093. 'Public: non è possibile accedere al campo _BirthDay 094. 'perchè è privato nella classe base 095. Return _FirstName & " " & _LastName & ", nato il " & _ 096. BirthDay.ToShortDateString & " frequenta l'anno " & _ 097. _Grade & " alla scuola " & _School 098. End Get 099. End Property 100.
  • 109. 101. 'Altra clausola importante: il costruttore della classe 102. 'derivata deve sempre richiamare il costruttore della 103. 'classe base 104. Sub New(ByVal FirstName As String, ByVal LastName As String, _ 105. ByVal BirthDay As Date, ByVal School As String, _ 106. ByVal Grade As Byte) 107. MyBase.New(FirstName, LastName, BirthDay) 108. Me.School = School 109. Me.Grade = Grade 110. End Sub 111. End Class 112. 113. Sub Main() 114. Dim P As New Person("Pinco", "Pallino", Date.Parse("06/07/90")) 115. Dim S As New Student("Tizio", "Caio", Date.Parse("23/05/92"), _ 116. "Liceo Classico Ugo Foscolo", 2) 117. 118. Console.WriteLine(P.CompleteName) 119. 'Come si vede, la classe derivata gode degli stessi membri 120. 'di quella base, acquisiti secondo le regole 121. 'dell'ereditarietà appena spiegate 122. Console.WriteLine(S.CompleteName) 123. 'E in più ha anche i suoi nuovi membri 124. Console.WriteLine(S.Profile) 125. 126. 'Altra cosa interessante: dato che Student è derivata da 127. 'Person ed espone tutti i membri di Person, più altri, 128. 'non è sbagliato assegnare un oggetto Student a una 129. 'variabile Person 130. P = S 131. Console.WriteLine(P.CompleteName) 132. 133. Console.ReadKey() 134. End Sub 135. End Module L'output: 1. Pinco Pallino 2. Tizio Caio 3. Tizio Caio, nato il 23/5/1992 frequenta l'anno 2 alla scuola Liceo Classico Ugo 4. Foscolo 5. Tizio Caio (Per maggior i infor mazioni sulle oper azioni con le date, veder e il capitolo B13) Anche se il sor gente è ampiamente commentato mi soffer mer ei su alcuni punti caldi. Il costr uttor e della classe der ivata deve sem pr e r ichiamar e il costr uttor e della classe base, e questo avviene tr amite la keyw or d MyBase che, usata in una classe der ivata, fa r ifer imento alla classe base cor r ente: attr aver so questa par ola r iser vata è possibile anche r aggiunger e i membr i pr ivati della classe base, ma si fa r ar amente, poichè il suo impiego più fr equente è quello di r ipr ender e le vecchie ver sioni di metodi modificati. Il secondo punto r iguar da la conver sione di classi: passar e da Student a Per son non è, come potr ebbe sembr ar e, una conver sione di r iduzione, poichè dur ante il pr ocesso, nulla va per duto nel ver o senso della par ola. Cer to, si per dono le infor mazioni supplementar i, ma alla classe base queste non ser vono: la sicur ezza di eseguir e la conver sione r isiede nel fatto che la classe der ivata gode degli stessi membr i di quella base e quindi non si cor r e il r ischio che ci sia r ifer imento a un membr o inesistente. Questo invece si ver ifica nel caso opposto: se una var iabile di tipo Student assumesse il valor e di un oggetto Per son, School e Gr ade sar ebber o pr ivi di valor e e ciò gener ebbe un er r or e. Per eseguir e questo tipo di passaggi è necessar io l'oper ator e Dir ectCast.
  • 110. A30. Polimorfismo Il polimor fismo è la capacità di un linguaggio ad oggetti di r idefinir e i membr i della classe base in modo tale che si compor tino in manier a differ ente all'inter no delle classi der ivate. Questa possibilità è quindi str ettamente legata all'er editar ietà. Le keyw or ds che per mettono di attuar ne il funzionamento sono due: Over r idable e Over r ides. La pr ima deve mar car e il membr o della classe base che si dovr à r idefinir e, mentr e la seconda contr assegna il membr o della classe der ivata che ne costituisce la nuova ver sione. È da notar e che solo membr i della stessa categor ia con no m e ug uale e sig natur e identica (ossia con lo stesso numer o e lo stesso tipo di par ametr i) possono subir e questo pr ocesso: ad esempio non si può r idefinir e la pr ocedur a Show Tex t() con la pr opr ietà Tex t, per chè hanno nome differ ente e sono di diver sa categor ia (una è una pr ocedur a e l'altr a una pr opr ietà). La sintassi è semplice: 1. Class [Classe base] 2. Overridable [Membro] 3. End Class 4. 5. Class [Classe derivata] 6. Inherits [Classe base] 7. Overrides [Membro] 8. End Class Questo esempio pr ende come base la classe Per son definita nel capitolo pr ecedente e sviluppa da questa la classe Teacher (insegnante), modificandone le pr opr ietà LastName e CompleteName: 001. Module Module1 002. Class Person 003. Protected _FirstName, _LastName As String 004. Private ReadOnly _BirthDay As Date 005. 006. Public Property FirstName() As String 007. Get 008. Return _FirstName 009. End Get 010. Set(ByVal Value As String) 011. If Value <> "" Then 012. _FirstName = Value 013. End If 014. End Set 015. End Property 016. 017. 'Questa proprietà sarà ridefinita nella classe Teacher 018. Public Overridable Property LastName() As String 019. Get 020. Return _LastName 021. End Get 022. Set(ByVal Value As String) 023. If Value <> "" Then 024. _LastName = Value 025. End If 026. End Set 027. End Property 028. 029. Public ReadOnly Property BirthDay() As Date 030. Get 031. Return _BirthDay 032. End Get 033. End Property 034. 035. 'Questa proprietà sarà ridefinita nella classe Teacher 036. Public Overridable ReadOnly Property CompleteName() As String 037. Get 038. Return _FirstName & " " & _LastName 039. End Get 040.
  • 111. End Property 041. 042. 'Costruttore che accetta tra parametri obbligatori 043. Sub New(ByVal FirstName As String, ByVal LastName As String, _ 044. ByVal BirthDay As Date) 045. Me.FirstName = FirstName 046. Me.LastName = LastName 047. Me._BirthDay = BirthDay 048. End Sub 049. End Class 050. 051. Class Teacher 052. Inherits Person 053. Private _Subject As String 054. 055. Public Property Subject() As String 056. Get 057. Return _Subject 058. End Get 059. Set(ByVal Value As String) 060. If Value <> "" Then 061. _Subject = Value 062. End If 063. End Set 064. End Property 065. 066. 'Ridefinisce la proprietà LastName in modo da aggiungere 067. 'anche il titolo di Professore al cognome 068. Public Overrides Property LastName() As String 069. Get 070. Return "Prof. " & _LastName 071. End Get 072. Set(ByVal Value As String) 073. 'Da notare l'uso di MyBase e LastName: in questo 074. 'modo si richiama la vecchia versione della 075. 'proprietà LastName e se ne imposta il 076. 'valore. Viene quindi richiamato il blocco Set 077. 'vecchio: si risparmiano due righe di codice 078. 'poichè non si deve eseguire il controllo 079. 'If su Value 080. MyBase.LastName = Value 081. End Set 082. End Property 083. 084. 'Ridefinisce la proprietà CompleteName in modo da 085. 'aggiungere anche la materia insegnata e il titolo di 086. 'Professore 087. Public Overrides ReadOnly Property CompleteName() As String 088. Get 089. 'Anche qui viene richiamata la vecchia versione di 090. 'CompleteName, che restituisce semplicemente il 091. 'nome completo 092. Return "Prof. " & MyBase.CompleteName & _ 093. ", dottore in " & Subject 094. End Get 095. End Property 096. 097. Sub New(ByVal FirstName As String, ByVal LastName As String, _ 098. ByVal BirthDay As Date, ByVal Subject As String) 099. MyBase.New(FirstName, LastName, BirthDay) 100. Me.Subject = Subject 101. End Sub 102. End Class 103. 104. Sub Main() 105. Dim T As New Teacher("Mario", "Rossi", Date.Parse("01/01/1950"), _ 106. "Letteratura italiana") 107. 108. 'Usiamo le nuove proprietà, ridefinite nella classe 109. 'derivata 110. Console.WriteLine(T.LastName) 111. '> "Prof. Rossi" 112.
  • 112. Console.WriteLine(T.CompleteName) 113. '> "Prof. Mario Rossi, dottore in Letteratura italiana" 114. 115. Console.ReadKey() 116. End Sub 117. End Module In questo modo si è visto come r idefinir e le pr opr ietà. Ma pr ima di pr oseguir e vor r ei far notar e un compor tamento par ticolar e: 1. Dim P As Person = T 2. Console.WriteLine(P.LastName) 3. Console.WriteLine(P.CompleteName) In questo caso ci si aspetter ebbe che le pr opr ietà r ichiamate da P agiscano come specificato nella classe base (ossia senza includer e altr e infor mazioni se non il nome ed il cognome), poiché P è di quel tipo. Questo, invece, non accade. Infatti, P e T, dato che abbiamo usato l'oper ator e =, puntano or a allo stesso oggetto in memor ia, solo che P lo vede come di tipo Per son e T come di tipo Teacher . Tuttavia, l'oggetto r eale è di tipo Teacher e per ciò i suoi metodi sono a tutti gli effetti quelli r idefiniti nella classe der ivata. Quando P tenta di r ichiamar e le pr opr ietà in questione, ar r iva all'indir izzo di memor ia dove sono conser vate le istr uzioni da eseguir e, solo che queste si tr ovano all'inter no di un oggetto Teacher e il lor o codice è, di conseguenza, diver so da quello della classe base. Questo compor tamento, al contr ar io di quanto potr ebbe sembr ar e, è utilissimo: ci per mette, ad esempio, di memor izzar e in un ar r ay di per sone sia studenti che insegnanti, e ci per mette di scr iver e a scher mo i lor o nomi differ entemente senza eseguir e una conver sione. Ecco un esempio: 01. Dim Ps(2) As Person 02. 03. Ps(0) = New Person("Luigi", "Ciferri", Date.Parse("7/7/1982")) 04. Ps(1) = New Student("Mario", "Bianchi", Date.Parse("19/10/1991"), _ 05. "Liceo Scientifico Tecnologico Cardano", 5) 06. Ps(2) = New Teacher("Ubaldo", "Nicola", Date.Parse("11/2/1980"), "Filosofia") 07. 08. For Each P As Person In Ps 09. Console.WriteLine(P.CompleteName) 10. Next È lecito assegnar e oggetti Student e Teacher a una cella di un ar r ay di Per son in quanto classi der ivate da Per son. I metodi r idefiniti, tuttavia, r imangono e modificano il compor tamento di ogni oggetto anche se r ichiamato da una "mascher a" di classe base. Pr oviamo or a con un piccolo esempio sul polimor fismo dei metodi: 01. Class A 02. Public Overridable Sub ShowText() 03. Console.WriteLine("A: Testo di prova") 04. End Sub 05. End Class 06. 07. Class B 08. Inherits A 09. 10. 'Come si vede il metodo ha: 11. '- lo stesso nome: ShowText 12. '- lo stesso tipo: è una procedura 13. '- gli stessi parametri: senza parametri 14. 'Qualunque tentativo di cambiare una di queste caratteristiche 15. 'produrrà un errore del compilatore, che comunica di non poter 16. 'ridefinire il metodo perchè non ne esistono di uguali nella 17. 'classe base 18. Public Overrides Sub ShowText() 19. Console.WriteLine("B: Testo di prova") 20. End Sub 21. End Class Ultime due pr ecisazioni: le var iabili non possono subir e polimor fismo, così come i membr i statici.
  • 113. Shadow ing Se il polimor fismo per mette di r idefinir e accur atamente membr i che pr esentano le stesse car atter istiche, ed è quindi più pr eciso, lo shadow ing per mette letter almente di oscur ar e qualsiasi membr o che abbia lo stesso nome, indipendentemente dalla categor ia, dalla signatur e e dalla qauntità di ver sioni alter native pr esenti. La keyw or d da usar e è Shadow s, e si applica solo sul membr o della classe der ivata che intendiamo r idefinir e, oscur ando l'omonimo nella classe base. Ad esempio: 01. Module Esempio 02. Class Base 03. Friend Control As Byte 04. End Class 05. 06. Class Deriv 07. Inherits Base 08. Public Shadows Sub Control(ByVal Msg As String) 09. Console.WriteLine("Control, seconda versione: " & Msg) 10. End Sub 11. End Class 12. 13. Sub Main() 14. Dim B As New Base 15. Dim D As New Deriv 16. 17. 'Entrambe le classe hanno lo stesso membro di nome 18. '"Control", ma nella prima è un campo friend, 19. 'mentre nella seconda è una procedura pubblica 20. Console.WriteLine(B.Control) 21. D.Control("Ciao") 22. 23. Console.ReadKey() 24. End Sub 25. End Module Come si vede, la sintassi è come quella di Over r ides: Shadow s viene specificato tr a lo specificator e di accesso (se c'e') e la tipologia del membr o (in questo caso Sub, pr ocedur a). Entr ambe le classi pr esentano Contr ol, ma la seconda ne fa un uso totalmente diver so. Ad ogni modo l'uso dello shadow ing in casi come questo è for tememente sconsigliabile: più che altr o lo si usa per assicur ar si che, se mai dovesse uscir e una nuova ver sione della classe base con dei nuovi metodi che pr esentano lo stesso nome di quelli della classe der ivata da noi definita, non ci siano pr oblemi di compatibilità. Se una var iabile è dichiar ata Shadow s, viene omessa la keyw or d Dim.
  • 114. A31. Conversioni di dati Il Fr amew or k .NET è in gr ado di eseguir e conver sioni automatiche a r untime ver so tipi di ampiezza maggior e, per esempio è in gr ado di conver tir e Int16 in Int32, Char in Str ing, Single in Double e via dicendo. Queste oper azioni di conver sione vengono dette w idening (dall'inglese w ide = lar go), ossia che avvengono senza la per dita di dati, poiché tr aspor tano un valor e che contiene una data infor mazione in un tipo che può contener e più infor mazioni. Gli oper ator i di conver sione ser vono per eseguir e conver sioni che vanno nella dir ezione opposta, e che sono quindi, nar r o w ing (dall'inglese nar r ow = str etto). Queste ultime possono compor tar e la per dita di dati e per ciò gener ano un er r or e se implicite. CTy pe CType è l'oper ator e di conver sione univer sale e per mette la conver sione di qualsiasi tipo in qualsiasi altr o tipo, almeno quando questa è possibile. La sintassi è molto semplice: [Variabile] = CType([Valore da convertire], [Tipo in cui convertire]) Ad esempio: 1. Dim I As Int32 = 50 2. 'Converte I in un valore Byte 3. Dim B As Byte = CType(I, Byte) Questa lista r ipor ta alcuni casi in cui è bene usar e esplicitamente l'oper ator e di conver sione CType: Per conver tir e un valor e inter o o decimale in un valor e booleano; Per conver tir e un valor e Single o Double in Decimal; Per conver tir e un valor e inter o con segno in uno senza segno; Per conver tir e un valor e inter o senza segno in uno con segno della stessa ampiezza (ad esempio da UInt32 a Int32). Oltr e a CType, esistono moltissime ver sioni più cor te di quest'ultimo che conver tono in un solo tipo: CInt conver te sempr e in Int32, CBool sempr e in booleano, CByte in byte, CShor t Int16, CLong, CUShor t, CULong, CUInt, CSng, CDbl, CDec, CStr , CDate, CObj. È inoppor tuno utilizzar e CStr poichè ci si può sevir e della funzione ToStr ing er editata da ogni classe da System.Object; allo stesso modo, è meglio evitar e CDate, a favor e di Date.Par se, come si vedr à nella lezione "DateTimePicker : Lavor ar e con le date". CType può comunque esser e usato per qualsiasi altr a conver sione contemplabile, anche e sopr attutto con i tipi Refer ence. Direc tCast Dir ectCast lavor a in un modo legger mente di diver so: CType tenta sempr e di conver tir e l'ar gomento di or gine nel tipo specificato, mentr e Dir ectCast lo fa solo se tale valor e può esser e sottoposto al casting (al "passaggio" da un tipo all'altr o, piuttosto che alla conver sione) ver so il tipo indicato. Per ciò non è, ad esempio, in gr ado di conver tir e una str inga in inter o, e neanche un valor e shor t in un integer , sebbene questa sia una conver sione di espansione. Questi ultimi esempi non sono validi anche per chè questo par ticolar e oper ator e può accettar e come ar gomenti solo oggetti, e quindi tipi Refer ence. In gener ale, quindi, dato il legger o r ispar mio di tempo di Dir ectCast in confr onto a CType, è conveniente usar e Dir ectCast:
  • 115. Per eseguir e l'unbox ing di tipi value; Per eseguir e il casting di una classe base in una classe der ivata (vedi "Er editar ieta'"); Per eseguir e il casting di un oggetto in qualsiasi altr o tipo r efer ence; Per eseguir e il casting di un oggetto in un'inter faccia. N.B.: notar e che tutti i casi sopr a menzionati hanno come tipo di par tenza un oggetto, pr opr io come detto pr ecedentemente. Try Cast Tr yCast ha la stessa sintassi di Dir ectCast, e quindi anche di CType, ma nasconde un piccolo pr egio. Spesso, quando si esegue una conver sione si deve pr ima contr ollar e che la var iabile in questione sia di un deter minato tipo base o implementi una deter minata inter faccia e solo successivamente si esegue la conver sione ver a e pr opr ia. Con ciò si contr olla due volte la stessa var iabile, pr ima con l'If e poi con Dir ectCast. Tr yCast, invece, per mette di eseguir e il tutto in un unico passaggio e r estituisce semplicemente Nothing se il cast fallisce. Questo appr occio r ende tale oper ator e cir ca 0,2 volte più veloce di Dir ectCast. Convert Esiste, poi, una classe statica definita del namespace System - il namespace più impor tante di tutto il Fr amew or k. Questa classe, essendo statica (e qui facciamo un po' di r ipasso), espone solo metodi statici e non può esser e istanziata (non espone costr uttor i e comunque sar ebbe inutile far lo). Essa contiene molte funzioni per eseguir e la conver sione ver so i tipi di base ed espone anche un impor tante valor e che vedr emo molto più in là par lando dei database. Essenzialmente, tutti i suoi metodi hanno un nome del tipo "ToXXXX", dove XXXX è uno qualsiasi tr a i tipi base: ad esempio, c'è, ToInt32, ToDouble, ToByte, ToStr ing, ecceter a... Un esempio: 01. Dim I As Int32 = 34 02. Dim D As Double = Convert.ToDouble(I) 03. ' D = 34.0 04. Dim S As String = Convert.ToString(D) 05. ' S = "34" 06. Dim N As Single = Convert.ToSingle(S) 07. ' N = 34.0 08. Dim K As String = "31/12/2008" 09. Dim A As Date = Convert.ToDate(K) All'inter no di Conver t sono definiti anche alcuni metodi per conver tir e una str inga da e ver so il for mato Base64, una par ticolar e codifica che utilizza solo 64 car atter i, al contr ar io dell'ASCII standar d che ne utilizza 128 o di quello esteso che ne utilizza 256. Tale codifica viene usata ad esempio nell'invio delle e-mail e pr oduce output un ter zo più voluminosi degli input, ma in compenso tutti i car atter i contemplati sono sempr e leggibili (non ci sono, quindi, car atter i "speciali"). Per appr ofondir e l'ar gomento, cliccate su w ik ipedia. Per r ipr ender e il discor so conver sioni, sar ebbe lecito pensar e che la definizione di una classe del gener e, quando esistono già altr i oper ator i come CType e Dir ectCast - altr ettanto qualificati e per for manti - sia abbastanza r idondante. Più o meno è così. Utilizzar e la classe Conver t al posto degli altr i oper ator i di casting non gar antisce alcun vantaggio di sor ta, e può anche esser e r icondotta ad una questione di gusti (io per sonalmente pr efer isco CType). Ad ogni modo, c'è da dir e un'altr a cosa al r iguar do: i metodi di Conver t sono piuttosto r igor osi e for niscono dei ser vizi molto mir ati. Per questo motivo, in casi molto vantaggiosi, ossia quando il cast può esser e ottimizzato, essi eseguono pur sempr e le stesse istr uzioni: al contr ar io, CType può "ingegnar si" e for nir e una conver sione più efficiente. Quest'ultimo, quindi, è legger mente più elastico ed adattabile alle situazioni.
  • 116. Parse Un'oper azione di par sing legge una str inga, la elabor a, e la conver te in un valor e di altr o tipo. Abbiamo già visto un utilizzo di Par se nell'uso delle date, poiché il tipo Date espone il metodo Par se, che ci per mette di conver tir e la r appr esentazione testuale di una data in un valor e date appr opr iato. Quasi tutti i tipi base del Fr amew or k espongono un metodo Par se, che per mette di passar e da una str inga a quel tipo: possiamo dir e che Par se è l'inver sa di ToStr ing. Ad esempio: 01. Dim I As Int32 02. 03. I = Int32.Parse("27") 04. ' I = 27 05. 06. I = Int32.Parse("78.000") 07. ' Errore di conversione! 08. 09. I = Int32.Parse("123,67") 10. ' Errore di conversione! Come vedete, Par se ha pur sempr e dei limiti: ad esempio non contempla i punti e le vir gole, sebbene la conver sione, vista da noi "umani", sia del tutto lecita (78.000 è settantottomila con il separ ator e delle migliaia e 123,67 è un numer o decimale, quindi conver tibile in inter o con un ar r otondamento). Inoltr e, Par se viene anche automaticamente chiamato dai metodi di Conver t quando il valor e passato è una str inga. Ad esempio, Conver t.ToInt32("27") r ichiama a sua volta Int32.Par se("27"). Per far vi veder e in che modo CType è più flessibile, r ipetiamo l'esper imento di pr ima usando appunto CType: 01. Dim I As Int32 02. 03. I = CType("27", Int32) 04. ' I = 27 05. 06. I = CType("78.000", Int32) 07. ' I = 78000 08. 09. I = CType("123,67", Int32) 10. ' I = 124 Per fetto: niente er r or i di conver sione e tutto come ci si aspettava! Try Parse Una var iante di Par se è Tr yPar se, anch'essa definita da molti tipi base. La sostanziale differ enza r isiede nel fatto che, mentr e la pr ima può gener ar e er r or i nel caso la str inga non possa esser e conver tita, la seconda non lo fa, ma non r estituisce neppur e il r isultato. Infatti, Tr yPar se accetta due ar gomenti, come nella seguente signatur e: 1. TryParse(ByVal s As String, ByRef result As [Tipo]) As Boolean Dove [Tipo] dipende da quale tipo base la stiamo r ichiamando: Int32.Tr yPar se avr à il secondo ar gomento di tipo Int32, Date.Tr yPar se ce l'avr à di tipo Date, e così via. In sostanza Tr yPar se tenta di eseguir e la funzione Par se sulla str inga s: se ci r iesce, r estituisce Tr ue e pone il r isultato in r esult (notar e che il par ametr o è passato per indir izzo); se non ci r iesce, r estituisce False. Ecco un esempio: 01. Dim S As String = "56/0/1000" 02. 'S contiene una data non valida 03. Dim D As Date 04. 05. If Date.TryParse(S, D) Then 06. Console.WriteLine(D.ToLongDateString()) 07. Else 08. Console.WriteLine("Data non valida!") 09. End If
  • 117. Ty peOf TypeOf ser ve per contr ollar e se una var iabile è di un cer to tipo, der iva da un cer to tipo o implementa una cer ta inter faccia, ad esempio: 1. Dim I As Int32 2. If TypeOf I Is Int32 Then 3. 'Questo blocco viene eseguito poichè I è di tipo Int32 4. End If Oppur e: 1. Dim T As Student 2. If TypeOf T Is Person Then 3. 'Questo blocco viene eseguito perchè T, essendo Student, è 4. 'anche di tipo Person, in quanto Student è una sua classe 5. 'derivata 6. End If Ed infine un esempio sulle inter facce, che potr ete tor nar e a guar dar e da qui a qualche capitolo: 1. Dim K(9) As Int32 2. If TypeOf Is IEnumerable Then 3. 'Questo blocco viene eseguito poiché gli array implementano 4. 'sempre l'interfaccia IEnumerable 5. End If
  • 118. A31. L'Overloading L'Over loading è la capacità di un linguaggio ad oggetti di poter definir e, nella stessa classe, più var ianti dello stesso metodo. Per poter eseguir e cor r ettamente l'over loading, è che ogni var iante del metodo abbia queste car atter istiche: Sia della stessa categor ia (pr ocedur a O funzione, anzi, per dir la in modo più esplicito: pr ocedur a Xor funzione); Abbia lo stesso nome; Abbia signatur e diver sa da tutte le altr e var ianti. Per color o che non se lo r icor dasser o, la signatur e di un metodo indica il tipo e la quantità dei suoi par ametr i. Questo è il tr atto essenziale che per mette di differ enziar e concr etamente una var iante dall'altr a. Per far e un esempio, il metodo Console.Wr iteLine espone ben 18 ver sioni diver se, che ci consentono di stampar e pr essoché ogni dato sullo scher mo. Fr a quelle che non abbiamo mai usato, ce n'è una in par ticolar e che vale la pena di intr odur r e or a, poiché molto utile e flessibile. Essa pr evede un pr imo par ametr o di tipo str inga e un secondo Par amAr r ay di oggetti: 1. Console.WriteLine("stringa", arg0, arg1, arg2, arg3, ...) Il pr imo par ametr o pr ende il nome di str ing a di fo r m ato , poiché specifica il for mato in cui i dati costituiti dagli ar gomenti addizionali dovr anno esser e visualizzati. All'inter no di questa str inga, si possono specificar e, oltr e ai nor mali car atter i, dei codici speciali, nella for ma "{I}", dove I è un numer o compr eso tr a 0 e il numer o di par amtr i meno uno: "{I}" viene detto segnaposto e ver r à sostituito dal par ametr o I nella str inga. Ad esempio: 1. A = 1 2. B = 3 3. Console.WriteLine("La somma di {0} e {1} è {2}.", A, B, A + B) 4. '> "La somma di 1 e 3 è 4." Ulter ior i infor mazioni sulle str inghe di for mato sono disponibili nel capitolo "Magie con le str inghe". Ma or a passiamo alla dichiar azione dei metodi in over load. La par ola chiave da usar e, ovviamente, è Over loads, specificata poco dopo lo scope, e dopo gli eventuali Over r idable od Over r ides. Le entità che possono esser e sottoposte ad over load, oltr e ai metodi, sono: Metodi statici Oper ator i Pr opr ietà Costr uttor i Distr uttor i Anche se gli ultimi due sono sempr e metodi - per or a tr alasciamo i distr uttor i, che non abbiamo ancor a analizzato - è bene specificar e con pr ecisione, per chè a compiti speciali spesso cor r ispondono compor tamenti altr ettanto speciali. Ecco un semplicissimo esempio di over load: 01. Module Module1 02. 03. 'Restituisce il numero di secondi passati dalla data D a oggi 04. Private Function GetElapsed(ByVal D As Date) As Single 05. Return (Date.Now - D).TotalSeconds 06. End Function 07. 08. 'Come sopra, ma il parametro è di tipo intero e indica 09. 'un anno qualsiasi 10. Private Function GetElapsed(ByVal Year As Int32) As Single 11. 'Utilizza Year per costruire un nuovo valore Date 12.
  • 119. 'e usa la precedente variante del metodo per 13. 'ottenere il risultato 14. Return GetElapsed(New Date(Year, 1, 1)) 15. End Function 16. 17. 'Come le due sopra, ma il parametro è di tipo stringa 18. 'e indica la data 19. Private Function GetElapsed(ByVal D As String) As Single 20. Return GetElapsed(Date.Parse(D)) 21. End Function 22. 23. Sub Main() 24. 'GetElapsed viene chiamata con tre tipi di parametri 25. 'diversi, ma sono tutti leciti 26. Dim El1 As Single = GetElapsed(New Date(1987, 12, 4)) 27. Dim El2 As Single = GetElapsed(1879) 28. Dim El3 As Single = GetElapsed("12/12/1991") 29. 30. Console.ReadKey() 31. End Sub 32. 33. End Module Come avr ete notato, nell'esempio pr ecedente non ho usato la keyw or d Over loads: anche se le r egole dicono che i membr i in over load vanno segnati, non è sempr e necessar io far lo. Anzi, molte volte si evita di dichiar ar e esplicitamente i membr i di cui esistono var ianti come Over loads. Ci sono var ie r agioni per questa pr atica: l'over load è scontato se i metodi pr esentano lo stesso nome, e il compilator e r iesce comunque a distinguer e tutto nitidamente; inoltr e, capita spesso di definir e var ianti e per r ender e il codice più leggibile e meno pesante (anche se i sor genti in VB tendono ad esser e un poco pr olissi), si omette Over loads. Tuttavia, esistono casi in cui è assolutamente necessar io usar e la keyw or d; eccone un esempio: 01. Module Module1 02. Class Person 03. Protected _FirstName, _LastName As String 04. Private ReadOnly _BirthDay As Date 05. 06. Public Property FirstName() As String 07. Get 08. Return _FirstName 09. End Get 10. Set(ByVal Value As String) 11. If Value <> "" Then 12. _FirstName = Value 13. End If 14. End Set 15. End Property 16. 17. Public Overridable Property LastName() As String 18. Get 19. Return _LastName 20. End Get 21. Set(ByVal Value As String) 22. If Value <> "" Then 23. _LastName = Value 24. End If 25. End Set 26. End Property 27. 28. Public ReadOnly Property BirthDay() As Date 29. Get 30. Return _BirthDay 31. End Get 32. End Property 33. 34. Public Overridable ReadOnly Property CompleteName() As String 35. Get 36. Return _FirstName & " " & _LastName 37. End Get 38. End Property 39.
  • 120. 40. 'ToString è una funzione definita nella classe 41. 'System.Object e poiché ogni cosa in .NET 42. 'deriva da questa classe, &egrae; sempre possibile 43. 'ridefinire tramite polimorfismo il metodo ToString. 44. 'In questo caso ne scriveremo non una, ma due versioni, 45. 'quindi deve essere dichiarato sia Overrides, perchè 46. 'sovrascrive System.Object.ToString, sia Overloads, 47. 'perchè è una versione alternativa di 48. 'quella che andremo a scrivere tra poco 49. Public Overloads Overrides Function ToString() As String 50. Return CompleteName 51. End Function 52. 53. 'Questa versione accetta un parametro stringa che assume 54. 'la funzione di stringa di formato: il metodo restituirà 55. 'la frase immessa, sostituendo {F} con FirstName e {L} con 56. 'LastName. In questa versione è sufficiente 57. 'Overloads, dato che non esiste un metodo ToString che 58. 'accetti un parametro stringa in System.Object e perciò 59. 'non lo potremmo modificare 60. Public Overloads Function ToString(ByVal FormatString As String) _ 61. As String 62. Dim Temp As String = FormatString 63. 'Sostituisce {F} con FirstName 64. Temp = Temp.Replace("{F}", _FirstName) 65. 'Sostituisce {L} con LastName 66. Temp = Temp.Replace("{L}", _LastName) 67. 68. Return Temp 69. End Function 70. 71. Sub New(ByVal FirstName As String, ByVal LastName As String, _ 72. ByVal BirthDay As Date) 73. Me.FirstName = FirstName 74. Me.LastName = LastName 75. Me._BirthDay = BirthDay 76. End Sub 77. End Class 78. 79. Sub Main() 80. Dim P As New Person("Mario", "Rossi", Date.Parse("17/07/67")) 81. 82. Console.WriteLine(P.ToString) 83. '> Mario Rossi 84. 85. 'vbCrLf è una costante che rappresenta il carattere 86. '"a capo" 87. Console.WriteLine(P.ToString("Nome: {F}" & vbCrLf & "Cognome: {L}")) 88. '> Nome: Mario 89. '> Cognome: Rossi 90. 91. Console.ReadKey() 92. End Sub 93. End Module Come mostr ato dall'esempio, quando il membr o di cui si vogliono definir e var ianti è sottoposto anche a polimor fismo, è necessar io specificar e la keyw or d Over loads, poiché, in caso contr ar io, il compilator e r intr accer ebbe quello stesso membr o come diver so e, non potendo esister e membr i con lo stesso nome, pr odur r ebbe un er r or e.
  • 121. A32. Gestione degli errori Fino ad or a, nello scr iver e il codice degli esempi, ho sempr e (o quasi sempr e) supposto che l'utente inser isse dati coer enti e cor r etti. Al massimo, ho inser ito qualche costr utto di contr ollo per ver ificar e che tutto andasse bene. Infatti, per quello che abbiamo visto fino ad or a, c'er ano solo due modi per evitar e che il pr ogr amma andasse in cr ash o pr oducesse output pr ivi di senso: scr iver e del codice a pr ova di bomba (e questo, gar antisco, non è sempr e possibile) o contr ollar e, pr ima di eseguir e le oper azioni, che tutti i dati fosser o per fettamente coer enti con il pr oblema da affr ontar e. Ahim�, non è sempr e possibile agir e in questo modo: ci sono cer ti casi in cui né l'uno né l'altr o metodo sono efficaci. E di questo posso for nir e subito un esempio lampante: ammettiamo di aver scr itto un pr ogr amma che esegua la divisione tr a due numer i. Molto banale come codice. Chiediamo all'utente i suddetti dati con Console.ReadLine, contr olliamo che il secondo sia diver so da 0 (pr opr io per evitar e un er r or e a r untime) e in questo caso stampiamo il r isultato. Ma... se l'utente inser isse, ad esempio, una letter a anziché un numer o, o per sbaglio o per pur o sadismo? Beh, qualcuno potr à pensar e "Usiamo Tr yCast", tuttavia Tr yCast, essendo una r iedizione di Dir ectCast, agisce solo ver so tipi r efer ence e Int32 è un tipo base. Qualcun altr o, invece, potr ebbe pr opor r e di usar e Tr yPar se, ma abbiamo già r ilevato come la funzione Par se sia di vedute r istr ette. In definitiva, non abbiamo alcun modo di contr ollar e pr ima se il dato immesso o no sia r ealmente coer ente con ciò che stiamo chiedendo all'utente. Possiamo saper e se il dato non è coer ente solo quando si ver ifica l'er r or e, ma in questo caso non possiamo per metter ci che il pr ogr amma vada in cr ash per una semplice distr azione. Dovr emo, quindi, ges tire l'er r or e. In .NET quelli che finor a ho chiamato "er r or i" si dicono, più pr opr iamente, Eccezio ni e sono anch'esse r appr esentate da una classe: la classe base di tutte le eccezioni è System.Ex ception, da cui der ivano tutte le var ianti per le specifiche eccezioni (ad esempio divisione per zer o, file inesistente, for mato non valido, ecceter a...). Accanto a queste, esiste anche uno specifico costr utto che ser ve per gestir le, e pr ende il nome di Tr y. Ecco la sua sintassi: 1. Try 2. 'Codice che potrebbe generare l'eccezione 3. Catch [Variabile] As [Tipo Eccezione] 4. 'Gestisce l'eccezione [Tipo Eccezione] 5. End Try Tr a Tr y e Catch viene scr itto il codice incr iminato, che potr ebbe eventualmente gener ar e l'er r or e che noi stiamo tentando di r intr acciar e e gestir e. Nello specifico, quando accade un avvenimento del gener e, si dice che il codice "lancia" un'eccezione, poiché la par ola chiave usata per gener ar la, come vedr emo, è pr opr io Thr ow (= lanciar e). Or a, passatemi il par agone che sto per far e, for se un po' fantasioso: il metodo in questione è come una fionda che scaglia un sassolino - un pacchetto di infor mazioni che ci dice tutto sul per chè e sul per come è stato gener ato quello specifico er r or e. Or a, se questo sassolino viene inter cettato da qualcosa (dal blocco catch), possiamo evitar e danni collater ali, ma se niente blocca la sua cor sa, ahimé, dovr emmo r ipagar e i vetr i r otti a qualcuno. Ecco un esempio: 01. Module Module1 02. Sub Main() 03. Dim a, b As Single 04. 'ok controlla se a e b sono coerenti 05. Dim ok As Boolean = False 06. 07. Do 08. 'Tenta di leggere i numeri da tastiera 09. Try 10. Console.WriteLine("Inserire due numeri non nulli: ") 11. a = Console.ReadLine 12. b = Console.ReadLine 13. 'Se il codice arriva fino a questo punto, significa 14. 'che non si sono verificate eccezioni 15. ok = True 16.
  • 122. Catch Ex As InvalidCastException 17. 'Se, invece, il programma arriva in questo blocco, 18. 'vuol dire che abbiamo "preso" (catch) un'eccezione 19. 'di tipo InvalidCastException, che è stata 20. '"lanciata" dal codice precedente. Tutti i dati 21. 'relativi a quella eccezione sono ora conservati 22. 'nella variabile Ex. 23. 'Possiamo accedervi oppure no, come in questo caso, 24. 'ma sono in ogni caso informazioni utili, come 25. 'vedremo fra poco 26. Console.WriteLine("I dati inseriti non sono numeri!") 27. 'I dati non sono coerenti, quindi ok = False 28. ok = False 29. End Try 30. 'Richiede gli stessi dati fino a che non si tratta 31. 'di due numeri 32. Loop Until ok 33. 34. 'Esegue il controllo su b e poi effettua la divisione 35. If b <> 0 Then 36. Console.WriteLine("{0} / {1} = {2}", a, b, a / b) 37. Else 38. Console.WriteLine("Divisione impossibile!") 39. End If 40. 41. Console.ReadKey() 42. End Sub 43. End Module Or a potr este anche chieder vi "Come faccio a saper e quale classe r appr esenta quale eccezione?". Beh, in gener e, si mette un blocco Tr y dopo aver notato il ver ificar si dell'er r or e e quindi dopo aver letto il messaggio di er r or e che contiene anche il nome dell'eccezione. In alter nativa si può specificar e come tipo semplicemente Ex ception, ed in quel caso ver r anno cattur ate tutte le eccezioni gener ate, di qualsiasi tipo. Ecco una var iante dell'esempio pr ecedente: 01. Module Module1 02. Sub Main() 03. 'a e b sono interi short, ossia possono assumere 04. 'valori da -32768 a +32767 05. Dim a, b As Int16 06. Dim ok As Boolean = False 07. 08. Do 09. Try 10. Console.WriteLine("Inserire due numeri non nulli: ") 11. a = Console.ReadLine 12. b = Console.ReadLine 13. ok = True 14. Catch Ex As Exception 15. 'Catturiamo una qualsiasi eccezione e stampiamo il 16. 'messaggio 17. 'ad essa relativo. Il messaggio è contenuto nella 18. 'proprietà Message dell'oggetto Ex. 19. Console.WriteLine(Ex.Message) 20. ok = False 21. End Try 22. Loop Until ok 23. 24. If b <> 0 Then 25. Console.WriteLine("{0} / {1} = {2}", a, b, a / b) 26. Else 27. Console.WriteLine("Divisione impossibile!") 28. End If 29. 30. Console.ReadKey() 31. End Sub 32. End Module Pr ovando ad inser ir e un numer o tr oppo gr ande o tr oppo piccolo si otter r à "Over flow di un'oper azione ar itmetica."; inser endo una str inga non conver tibile in numer o si otter r à "Cast non valido dalla str inga [str inga] al tipo 'Shor t'".
  • 123. Questa ver sione, quindi, cattur a e gestisce ogni possibile eccezione. Ricor date che è possibile usar e anche più clausole Catch in un unico blocco Tr y, ad esempio una per ogni eccezione diver sa. Clausola Finally Il costr utto Tr y è costituito da un blocco Tr y e da una o più clausole Catch. Tuttavia, opzionalmente, è possibile specificar e anche un'ulter ior e clausola, che deve esser e posta dopo tutti i Catch: Finally. Finally dà inizio ad un altr o blocco di codice che viene s empre eseguito, sia che si gener i un'eccezione, sia che non se ne gener i alcuna. Il codice ivi contenuto viene eseguito comunque dopo il tr y e il catch. Ad esempio, assumiamo di aver e questo blocco di codice, con alcune istr uzioni di cui non ci inter essa la natur a: mar chiamo le istr uzioni con delle letter e e ipotizziamo che la D gener i un'eccezione: 01. Try 02. A 03. B 04. C 05. D 06. E 07. F 08. Catch Ex As Exception 09. G 10. H 11. Finally 12. I 13. L 14. End Try Le istr uzioni eseguite sar anno: 01. A 02. B 03. C 04. 'Eccezione: salta nel blocco Catch 05. G 06. H 07. 'Alla fine esegue comunque il Finally 08. I 09. L Lanc iare un'ec c ezione e c reare ec c ezioni personalizzate Ammettiamo or a di aver bisogno di un'eccezione che r appr esenti una par ticolar e cir costanza che si ver ifica solo nle nostr o pr ogr amma, e di cui non esiste un cor r ispettivo tr a le eccezioni pr edefinite del Fr amew or k. Dovr emo scr iver e una nuova eccezione. Per far ciò, bisogna semplicemente dichiar ar e una nuova classe che er editi dalla classe Ex eption: 01. Module Module1 02. 'Questa classe rappresenta l'errore lanciato quando una 03. 'password imessa è sbagliata. Per convenzione, tutte le 04. 'classi che rappresentano un'eccezione devono terminare 05. 'con la parola "Exception" 06. Class IncorrectPasswordException 07. Inherits System.Exception 'Eredita da Exception 08. 09. 'Queste proprietà ridefiniscono quelle della classe 10. 'Exception tramite polimorfismo, perciò sono 11. 'dichiarate Overrides 12. 13. 'Sovrascrive il messaggio di errore 14. Public Overrides ReadOnly Property Message() As String 15. Get 16.
  • 124. Return "La password inserita � sbagliata!" 17. End Get 18. End Property 19. 20. 'Modifica il link di aiuto 21. Public Overrides Property HelpLink() As String 22. Get 23. Return "http://totem.altervista.org" 24. End Get 25. Set(ByVal Value As String) 26. MyBase.HelpLink = value 27. End Set 28. End Property 29. 30. 'Il resto dei membri di Exception sono molto importanti 31. 'e vengono inizializzati con dati prelevati tramite 32. 'Reflection (ultimo argomento di questa sezione), perciò 33. 'è conveniente non modificare altro. Potete 34. 'semmai aggiungere qualche membro 35. End Class 36. 37. Sub Main() 38. Dim Pass As String = "b7dha90" 39. Dim NewPass As String 40. 41. Try 42. Console.WriteLine("Inserire la password:") 43. NewPass = Console.ReadLine 44. If NewPass <> Pass Then 45. 'Lancia l'eccezione usando la keyword Throw 46. Throw New IncorrectPasswordException 47. End If 48. Catch IPE As IncorrectPasswordException 49. 'Visualizza il messaggio 50. Console.WriteLine(IPE.Message) 51. 'E il link d'aiuto 52. Console.WriteLine("Help: " & IPE.HelpLink) 53. End Try 54. 55. Console.ReadKey() 56. End Sub 57. End Module Come si è visto nell'esempio, lanciar e un'eccezione è molto semplice: basta scr iver e Thr ow , seguito da un oggetto Ex ception valido. In questo caso abbiamo cr eato l'oggetto Incor r ectPassw or dEx ception nello stessa linea di codice in cui l'abbiamo lanciato.
  • 125. A33. Distruttori Avver tenza: questo è un capitolo molto tecnico. For se vi sar à più utile in futur o. Gli oggetti COM (Component Object Model) utilizzati dal vecchio VB6 possedevano una car atter istica peculiar e che per metteva di deter minar e quando non vi fosse più bisogno di lor o e la memor ia associata potesse esser e r ilasciata: er ano dotati di un r efer ence counter , ossia di un "contator e di r ifer imenti". Ogni volta che una var iabile veniva impostata su un oggetto COM, il contator e veniva aumentato di 1, mentr e quando quella var iabile veniva distr utta o se ne cambiava il valor e, il contator e scendeva di un'unità. Quando tale valor e r aggiungeva lo zer o, gli oggetti venivano distr utti. Er ano pr esenti alcuni pr oblemi di cor r uzione della memor ia, per ò: ad esempio se due oggetti si puntavano vicendevolmente ma non er ano utilizzati dall'applicazione, essi non venivano distr utti (r ifer imento cir colar e). Il meccanismo di gestione della memor ia con il .NET Fr amew or k è molto diver so, e or a vediamo come oper a. Garbage Collec tion Questo è il nome del pr ocesso sul quale si basa la gestione della memor ia del Fr amew or k. Quando l'applicazione tenta di cr ear e un nuovo oggetto e lo spazio disponibile nell'heap managed scar seggia, viene messo in moto questo meccanismo, attr aver so l'attivazione del Gar bage Collector . Per pr ima cosa vengono visitati tutti gli oggetti pr esenti nello heap: se ce n'è uno che non è r aggiungibile dall'applicazione, questo viene distr utto. Il pr ocesso è molto sofisticato, in quanto è in gr ado di r ilevar e anche dipendenze indir ette, come classi non r aggiungibili dir ettamente, r efer enziate da altr e classi che sono r aggiungibili dir ettamente; r iesce anche a r isolver e il pr oblema opposto, quello del r ifer imento cir colar e. Se uno o più oggetti non vengono distr utti per chè sono necessar i al pr ogr amma per funzionar e, si dice che essi sono sopr avvissuti a una Gar bage Collection e appar tengono alla gener azione 1, mentr e quelli inizializzati che non hanno subito ancor a nessun pr ocesso di r accolta della memor ia sono di gener azione 0. L'indice gener azionale viene incr ementato di uno fino ad un massimo di 2. Questi ultimi oggetti sono sopr avvissuti a molti contr olli, il che significa che continuano a esser e utilizzati nello stesso modo: per ciò il Gar bage Collector li sposta in una posizione iniziale dell'heap managed, in modo che si dovr anno eseguir e meno oper azioni di spostamento della memor ia in seguito. La stessa cosa vale per le gener azioni successive. Questo sistema assicur a che ci sia sempr e spazio liber o, ma non gar antisce che ogni oggetto logicamente distr utto lo sia anche fisicamente: se per quegli oggetti che allocano solo memor ia il pr oblema è r elativo, per altr i che utilizzano file e r isor se ester ne, invece, diventa più complicato. Il compito di r ilasciar e le r isor se spetta quindi al pr ogr ammator e, che dovr ebbe, in una classe ideale, pr eoccupar si che quando l'oggetto venga distr utto lo siano cor r ettamente anche le r isor se ad esso associate. Bisogna quindi far e eseguir e del codice appena pr ima della distr uzione: come? lo vediamo or a. Finalize Il metodo Finalize di un oggetto è speciale, poichè viene r ichiamato dal Gar bage Collector "in per sona" dur ante la r accolta della memor ia. Come già detto, non è possibile saper e quando un oggetto logicamente distr utto lo sar à anche fisicamente, quindi Finalize potr ebbe esser e eseguito anche diver si secondi, o minuti, o addir ittur a or e, dopo che sia stato annullato ogni r ifer imento all'oggetto. Come seconda clausola impor tante, è necessar io non acceder e m ai ad oggetti ester ni in una pr ocedur a Finalize: dato che il GC (acr onimo di gar bage collector ) può distr ugger e gli oggetti in qualsiasi or dine, non si può esser e sicur i che l'oggetto a cui si sta facendo r ifer imento esista ancor a o sia già stato distr utto. Questo vale anche per oggetti singleton come Console o Application, o addir ittur a per i tipi Str ing, Byte, Date e tutti gli altr i (dato che, essendo anch'essi istanze di System.Type, che definisce le car atter istiche di ciascun tipo,
  • 126. sono soggetti alla GC alla fine del pr ogr amma). Per saper e se il pr ocesso di distr uzione è stato avviato dalla chiusur a del pr ogr amma si può r ichiamar e una semplice pr opr ietà booleana, Envir onment.HasShutdow nStar ted. Per esemplificar e i concetti, in questo par agr afo far ò uso dell'oggetto singleton GC, che r appr esenta il Gar bage Collector , per mettendo di avviar e for zatamente la r accolta della memor ia e altr e cose: questo no n deve mai esser e fatto in un'applicazione r eale, poichè potr ebbe compr ometter ne le pr estazioni. 01. Module Module1 02. Class Oggetto 03. Sub New() 04. Console.WriteLine("Un oggetto sta per essere creato.") 05. End Sub 06. 'La procedura Finalize è definita in System.Object, quindi, 07. 'per ridefinirla dobbiamo usare il polimorfismo. Inoltre 08. 'deve essere dichiarata Protected, poichè non può 09. 'essere richiamata da altro ente se non dal GC e allo 10. 'stesso tempo è ereditabile 11. Protected Overrides Sub Finalize() 12. Console.WriteLine("Un oggetto sta per essere distrutto.") 13. 'Blocca il programma per 4 secondi circa, consentendoci 14. 'di vedere cosa viene scritto a schermo 15. System.Threading.Thread.CurrentThread.Sleep(4000) 16. End Sub 17. End Class 18. 19. Sub Main() 20. Dim O As New Oggetto 21. Console.WriteLine("Oggetto = Nothing") 22. Console.WriteLine("L'applicazione sta per terminare.") 23. End Sub 24. End Module L'output sar à: 1. Un oggetto sta per essere creato. 2. Oggetto = Nothing 3. L'applicazione sta per terminare. 4. Un oggetto sta per essere distrutto. Come si vede, l'oggetto viene distr utto do po il ter mine dell'applicazione (siamo for tunati che Console è ancor a "in vita" pr ima della distr uzione): questo significa che c'er a abbastanza spazio disponibile da non avviar e la GC, che quindi è stata r imandata fino alla fine del pr ogr amma. Ripr oviamo invece in questo modo: 01. Sub Main() 02. Dim O As New Oggetto 03. O = Nothing 04. Console.WriteLine("Oggetto = Nothing") 05. 06. 'NON PROVATECI A CASA! 07. 'Forza una garbage collection 08. GC.Collect() 09. 'Attende che tutti i metodi Finalize siano stati eseguiti 10. GC.WaitForPendingFinalizers() 11. 12. Console.WriteLine("L'applicazione sta per terminare.") 13. Console.ReadKey() 14. End Sub Ciò che appar ir à sullo scher mo è: 1. Un oggetto sta per essere creato. 2. Oggetto = Nothing 3. Un oggetto sta per essere distrutto. 4. L'applicazione sta per terminare. Si vede che l'or dine delle ultime due azioni è stato cambiato a causa delle GC avviata anzi tempo pr ima del ter mine del pr ogr amma.
  • 127. Anche se ci siamo diver titi con Finalize, questo metodo deve esser e definito solo se str ettamente necessar io, per alcune r agioni. La pr ima è che il GC impiega non uno, ma due cicli per finalizzar e un oggetto in cui è stata definita Finalize dal pr ogr ammator e. Il motivo consiste nella possibilità che venga usata la cosiddetta r esur r ezio ne dell'o g g etto : in questa tecnica, ad una var iabile globale viene assegnato il r ifer imento alla classe stessa usando Me; dato che in questo modo c'è ancor a un r ifer imento valido all'oggetto, questo non deve venir e distr utto. Tuttavia, per r ilevar e questo fenomeno, il GC impiega due cicli e si r ischia di occupar e memor ia inutile. Inoltr e, sempr e per questa causa, si impiega più tempo macchina che potr ebbe esser e speso in altr o modo. Dispose Si potr ebbe definir e Dispose come un Finalize manuale: esso per metto di r ilasciar e qualsiasi r isor sa che non sia la memor ia (ossia connessioni a database, files, immagini, pennelli, oggetti di sistema, ecceter a...) manualmente, appena pr ima di impostar e il r ifer imento a Nothing. In questo modo non si dovr à aspettar e una successiva GC affinchè sia r ilasciato tutto cor r ettamente. Dispose non è un metodo definito da tutti gli oggetti, e per ciò ogni classe che intende definir lo deve implementar e l'inter faccia IDisposable (per ulter ior i infor mazioni sulle inter facce, veder e capitolo 36): per or a pr endete per buono il codice che for nisco, vedr emo in seguito più appr ofonditamente l'agor mento delle inter facce. 01. Class Oggetto 02. 'Implementa l'interfaccia IDisposable 03. Implements IDisposable 04. 'File da scrivere: 05. Dim W As IO.StreamWriter 06. 07. Sub New() 08. 'Inizializza l'oggetto 09. W = New IO.StreamWriter("C:test.txt") 10. End Sub 11. 12. Public Sub Dispose() Implements IDisposable.Dispose 13. 'Chiude il file 14. W.Close() 15. End Sub 16. End Class Invocando il metodo Dispose di Oggetto, è possibile chiuder e il file ed evitar e che venga lasciato aper to. Il Vb.NET for nisce un costr utto, valido per tutti gli oggetti che implementano l'inter faccia IDisposable, che si assicur a di r ichiamar e il metodo Dispose e impostar e il r ifer imento a Nothing automaticamente dopo l'uso. La sintassi è questa: 1. Using [Oggetto] 2. 'Codice da eseguire 3. End Using 4. 5. 'Che corrisponde a scrivere: 6. 'Codice da eseguire 7. [Oggetto].Dispose() 8. [Oggetto] = Nothing Per convenzione, se una classe implementa un'inter faccia IDisposable e contiene altr e classi nidificate o altr i oggetti, il suo metodo Dispose deve r ichiamar e il Dispose di tutti gli oggetti inter ni, almeno per quelli che ce l'hanno. Altr a convenzione è che se viene r ichiamata Dispose da un oggetto già distr utto logicamente, deve gener ar si l'eccezione ObjectDisposedEx ception. Usare Dispose e Finalize Ci sono alcune cir costanze che r ichiedono l'uso di una sola delle due, altr e che non le r ichiedono e altr e ancor a che dovr ebber o r cihieder le entr ambe. Segue una piccola lista di sugger imenti su come metter e in pr atica questi
  • 128. meccanismi: Nè Dispose, nè Finalize: la classe impiega solo la memor ia come unica r isor sa o, se ne impiegate altr e, le r ilascia pr ima di ter minar e le pr opr ie oper azioni. Solo Dispose: la classe impiega r isor se facendo r ifer imento ad altr i oggetti .NET e si vuole for nir e al chiamante la possibilità di r ilasciar e tali r isor se il pr ima possibile. Dispose e Finalize: la classe impiega dir ettamente una r isor sa, ad esempio invocando un metodo di una libr er ia unmanaged, che r ichiede un r ilascio esplicito; in più si vuole for nir e al client la possibilità di deallocar e manualmente gli oggetti. Solo Finalize: si deve eseguir e un cer to codice pr ima della distr uzione. A questo punto ci si deve pr eoccupar e di due pr oblemi che possono pr esentar si: Finalize può esser e chiamato anche dopo che l'oggetto è stato distr utto e le sue r isor se deallocate con Dispose, quindi potr ebbe tantar e di distr ugger e un oggetto inesistente; il codice che viene eseguito in Finalize potr ebbe far r ifer imento a oggetti inesistenti. Le convenzioni per mettono di aggir ar e il pr oblema facendo uso di ver sioni in over load di Dispose e di una var iabile pr ivata a livello di classe. La var iabile booleana Disposed ha il compito di memor izzar e se l'oggetto è stato distr utto: in questo modo eviter emo di r ipeter e il codice in Finalize. Il metodo in over load di Dispose accetta un par ametr o di tipo booleano, di solito chiamato Disposing, che indica se l'oggetto sta subendo un pr ocesso di distr uzione manuale o di finalizzazione: pr ocedendo con questo metodo si è cer ti di r ichiamar e eventuali altr i oggetti nel caso non ci sia finalizzazione. Il codice seguente implementa una semplicissima classe FileWr iter e, tr amite messaggi a scher mo, visualizza quando e come l'oggetto viene r imosso dalla memor ia: 001. Module Module1 002. Class FileWriter 003. Implements IDisposable 004. 005. Private Writer As IO.StreamWriter 006. 'Indica se l'oggetto è già stato distrutto con Dispose 007. Private Disposed As Boolean 008. 'Indica se il file è aperto 009. Private Opened As Boolean 010. 011. Sub New() 012. Disposed = False 013. Opened = False 014. Console.WriteLine("FileWriter sta per essere creato.") 015. 'Questa procedura comunica al GC di non richiamare più 016. 'il metodo Finalize per questo oggetto. Scriviamo ciò 017. 'perchè se file non viene esplicitamente aperto con 018. 'Open non c'è alcun bisogno di chiuderlo 019. GC.SuppressFinalize(Me) 020. End Sub 021. 022. 'Apre il file 023. Public Sub Open(ByVal FileName As String) 024. Writer = New IO.StreamWriter(FileName) 025. Opened = True 026. Console.WriteLine("FileWriter sta per essere aperto.") 027. 'Registra l'oggetto per eseguire Finalize: ora il file 028. 'è aperto e può quindi essere chiuso 029. GC.ReRegisterForFinalize(Me) 030. End Sub 031. 032. 'Scrive del testo nel file 033. Public Sub Write(ByVal Text As String) 034. If Opened Then 035. Writer.Write(Text) 036. End If 037. End Sub 038. 039. 'Una procedura analoga a Open aiuta a impostare meglio 040. 'l'oggetto e non fa altro che richiamare Dispose: è 041. 'più una questione di completezza 042.
  • 129. Public Sub Close() 043. Dispose() 044. End Sub 045. 046. 'Questa versione è in overload perchè l'altra viene 047. 'chiamata solo dall'utente (è Public), mentre questa 048. 'implementa tutto il codice che è necessario eseguire 049. 'per rilasciare le risorse. 050. 'Il parametro Disposing indica se l'oggetto sta per 051. 'essere distrutto, quindi manualmente, o finalizzato, 052. 'quindi nel processo di GC: nel secondo caso altri oggetti 053. 'che questa classe utilizza potrebbero non esistere più, 054. 'perciò si deve controllare se è possibile 055. 'invocarli correttamente 056. Protected Overridable Overloads Sub Dispose(ByVal Disposing _ 057. As Boolean) 058. 'Esegue il codice solo se l'oggetto esiste ancora 059. If Disposed Then 060. 'Se è distrutto, esce dalla procedura 061. Exit Sub 062. End If 063. 064. If Disposing Then 065. 'Qui possiamo chiamare altri oggetti con la 066. 'sicurezza che esistano ancora 067. Console.WriteLine("FileWriter sta per essere distrutto.") 068. Else 069. Console.WriteLine("FileWriter sta per essere finalizzato.") 070. End If 071. 072. 'Chiude il file 073. Writer.Close() 074. 075. Disposed = True 076. Opened = False 077. End Sub 078. 079. Public Overloads Sub Dispose() Implements IDisposable.Dispose 080. 'L'oggetto è stato distrutto 081. Dispose(True) 082. 'Quindi non deve più essere finalizzato 083. GC.SuppressFinalize(Me) 084. End Sub 085. 086. Protected Overrides Sub Finalize() 087. 'Processo di finalizzazione: 088. Dispose(False) 089. End Sub 090. End Class 091. 092. Sub Main() 093. Dim F As New FileWriter 094. 'Questo blocco mostra l'esecuzione di Dispose 095. F.Open("C:test.txt") 096. F.Write("Ciao") 097. F.Close() 098. 099. 'Questo mostra l'esecuzione di Finalize 100. F = New FileWriter 101. F.Open("C:test2.txt") 102. F = Nothing 103. 104. GC.Collect() 105. GC.WaitForPendingFinalizers() 106. 107. Console.ReadKey() 108. End Sub 109. End Module L'output: 1. FileWriter sta per essere creato. 2.
  • 130. FileWriter sta per essere aperto. 3. FileWriter sta per essere distrutto. 4. FileWriter sta per essere creato. 5. FileWriter sta per essere aperto. 6. FileWriter sta per essere finalizzato.
  • 131. A34. I Delegate Con il ter mine Deleg ate si indica un par ticolar e tipo di dato che è in gr ado di "contener e" un metodo, ossia una pr ocedur a o una funzione. Ho messo di pr oposito le vir golette sul ver bo "contener e", poiché non è pr opr iamente esatto, ma ser ve per r ender e più incisiva la definizione. Come esistono tipi di dato per gli inter i, i decimali, le date, le str inghe, gli oggetti, ne esistono anche per i metodi, anche se può sembr ar e un po' str ano. Per chi avesse studiato altr i linguaggi pr ima di appr occiar si al VB.NET, possiamo assimilar e i Delegate ai tipi pr ocedur ali del Pascal o ai puntator i a funzione del C. Ad ogni modo, i delegate sono legger mente diver si da questi ultimi e pr esentano alcuni tr atti par ticolar i: Un delegate non può contener e quals ias i metodo, ma he dei limiti. Infatti, è in gr ado di contener e solo metodi con la stessa signatur e specificata nella definizione del tipo. Fr a br eve vedr emo in cosa consiste questo punto; Un delegate può contener e sia metodi di istanza sia metodi statici, a patto che questi r ispettino la r egole di cui al punto sopr a; Un delegate è un tipo r efer ence, quindi si compor ta come un comunissimo oggetto, seguendo quelle r egole che mi sembr a di aver già r ipetuto fino alla noia; Un oggetto di tipo delegate è un oggetto immutabile, ossia, una volta cr eato, non può esser e modificato. Per questo motivo, non espone alcuna pr opr ietà (tr anne due in sola lettur a). D'altr a par te, questo compor tamento er a pr evedibile fin dalla definizione: infatti, se un delegate contiene un r ifer imento ad un metodo - e quindi un metodo già esistente e magar i definito in un'altr a par te del codice - come si far ebbe a modificar lo? Non si potr ebbe modificar e la signatur e per chè questo andr ebbe in conflitto con la sua natur a, e non si potr ebbe modificar ne il cor po per chè si tr atta di codice già scr itto (r icor date che gli oggetti esistono solo a r un-time, per chè vengono cr eati solo dopo l'avvio del pr ogr amma, e tutto il codice è già stato compilato e tr asfor mato in linguaggio macchina inter medio); Un delegate è un tipo s afe, ossia non può mai contener e r ifer imenti ad indir izzi di memor ia che non indichino espr essamente un metodo (al contr ar io dei per icolosi puntator i del C). Mi r endo conto che questa intr oduzione può appar ir e un po' tr oppo teor ica e fumosa, ma ser ve per compr ender e il compor tamento dei delegate. Dic hiarazione di un delegate Un nuovo tipo delegate viene dichiar ato con questa sintassi: 1. Delegate [Sub/Function] [Nome]([Elenco parametri]) Appar e subito chiar o il legame con i metodi data la for tissima somiglianza della sintassi con quella usata per definir e, appunto, un metodo. Notate che in questo caso si specifica solo la signatur e (tipo e quantità dei par ametr i) e la categor ia (pr ocedur a o funzione) del delegate, mentr e il [Nome] indica il nome del nuovo tipo cr eato (così come il nome di una nuova classe o una nuova str uttur a), ma non vi è tr accia del "cor po" del delegate. Un delegate, infatti, non ha cor po, per chè, se invocato da un oggetto, esegue i metodi che esso stesso contiene, e quindi esegue il codice contenuto nei lor o cor pi. Da questo momento in poi, potr emo usar e nel codice questo nuovo tipo per immagazzinar e inter i metodi con le stesse car atter istiche appena definite. Dato che si tr atta di un tipo r efer ence, per ò, bisogna anche inizializzar e l'oggetto con un costr uttor e... Qui dovr ebbe sor ger e spontaneamente un dubbio: dove e come si dichiar a il costr uttor e di un delegate? Fino ad or a, infatti, gli unici tipi r efer ence che abbiamo impar ato a dichiar ar e sono le classi, e nelle classi è lecito scr iver e un nuovo costr uttor e New nel lor o cor po. Qui, invece, non c'è nessun cor po in cui por r e un ipotetico costr uttor e. La r ealtà è che si usa sem pr e il costr uttor e di default, ossia quello pr edefinito, che
  • 132. viene automaticamente cr eato all'atto stesso della dichiar azione, anche se noi non r iusciamo a veder lo. Questo costr uttor e accetta sempr e e solo un par ametr o: un oggetto di tipo indeter minato r estituito da uno speciale oper ator e, Addr essOf. Questo è un oper ator e unar io che accetta come oper ando il metodo di cui ottener e l'"indir izzo": 1. AddressOf [NomeMetodo] Ciò che Addr essOf r estituisce non è molto chiar o: la sua descr izione dice espr essamente che viene r estituito un oggetto delegate (il che è già abbastanza str ano di per sé, dato che per cr ear e un delegate ci vuole un altr o delegate). Tuttavia, se si utilizza come par ametr o del costr uttor e un oggetto System.Delegate viene r estituito un er r or e. Ma lasciamo queste disquisizioni a chi ha tempo da per der e e pr ocediamo con le cose impor tanti. N.B.: Dalla ver sione 2008, i costr uttor i degli oggetti delegate accettano anche espr essioni lambda! Una volta dichiar ata ed inizializzata una var iabile di tipo delegate, è possibile usar la esattamente come se fosse un metodo con la signatur e specificata. Ecco un esempio: 01. Module Module1 02. 'Dichiarazione di un tipo delegate Sub che accetta un parametro 03. 'di tipo stringa. 04. Delegate Sub Display(ByVal Message As String) 05. 06. 'Una procedura dimostrativa 07. Sub Write1(ByVal S As String) 08. Console.WriteLine("1: " & S) 09. End Sub 10. 11. 'Un'altra procedura dimostrativa 12. Sub Write2(ByVal S As String) 13. Console.WriteLine("2: " & S) 14. End Sub 15. 16. Sub Main() 17. 'Variabile D di tipo Display, ossia il nuovo tipo 18. 'delegate appena definito all'inizio del modulo 19. Dim D As Display 20. 21. 'Inizializa D con un nuovo oggetto delegate contenente 22. 'un riferimento al metodo Console.WriteLine 23. D = New Display(AddressOf Console.WriteLine) 24. 25. 'Invoca il metodo referenziato da D: in questo caso 26. 'equivarrebbe a scrivere Console.WriteLine("Ciao") 27. D("Ciao") 28. 29. 'Reinizializza D, assegnandogli l'indirizzo di Write1 30. D = New Display(AddressOf Write1) 31. 'è come chiamare Write1("Ciao") 32. D("Ciao") 33. 34. 'Modo alternativo per inizializzare un delegate: si omette 35. 'New e si usa solo AddressOf. Questo genera una conversione 36. 'implicita che dà errore di cast nel caso in cui Write1 37. 'non sia compatibile con la signature del delegate 38. D = AddressOf Write2 39. D("Ciao") 40. 41. 'Notare che D può contenere metodi di istanza 42. '(come Console.WriteLine) e metodi statici (come Write1 43. 'e Write2) 44. 45. Console.ReadKey() 46. End Sub 47. End Module La signatur e di un delegate no n può contener e par ametr i indefiniti (Par amAr r ay) od opzionali (Optional), tuttavia i metodi memor izzati in un oggetto di tipo delegate possono aver e par ametr i di questo tipo. Eccone un esempio: 001. Module Module1 002.
  • 133. 'Tipo delegate che può contenere riferimenti a funzioni Single 003. 'che accettino un parametro di tipo array di Single 004. Delegate Function ProcessData(ByVal Data() As Single) As Single 005. 'Tipo delegate che può contenere riferimenti a procedure 006. 'che accettino due parametri, un array di Single e un Boolean 007. Delegate Sub PrintData(ByVal Data() As Single, ByVal ReverseOrder As Boolean) 008. 009. 'Funzione che calcola la media di alcuni valori. Notare che 010. 'l'unico parametro è indefinito, in quanto 011. 'dichiarato come ParamArray 012. Function CalculateAverage(ByVal ParamArray Data() As Single) As Single 013. Dim Total As Single = 0 014. 015. For I As Int32 = 0 To Data.Length - 1 016. Total += Data(I) 017. Next 018. 019. Return (Total / Data.Length) 020. End Function 021. 022. 'Funzione che calcola la varianza di alcuni valori. Notare che 023. 'anche in questo caso il parametro è indefinito 024. Function CalculateVariance(ByVal ParamArray Data() As Single) As Single 025. Dim Average As Single = CalculateAverage(Data) 026. Dim Result As Single = 0 027. 028. For I As Int32 = 0 To Data.Length - 1 029. Result += (Data(I) - Average) ^ 2 030. Next 031. 032. Return (Result / Data.Length) 033. End Function 034. 035. 'Procedura che stampa i valori di un array in ordine normale 036. 'o inverso. Notare che il secondo parametro è opzionale 037. Sub PrintNormal(ByVal Data() As Single, _ 038. Optional ByVal ReverseOrder As Boolean = False) 039. If ReverseOrder Then 040. For I As Int32 = Data.Length - 1 To 0 Step -1 041. Console.WriteLine(Data(I)) 042. Next 043. Else 044. For I As Int32 = 0 To Data.Length - 1 045. Console.WriteLine(Data(I)) 046. Next 047. End If 048. End Sub 049. 050. 'Procedura che stampa i valori di un array nella forma: 051. '"I+1) Data(I)" 052. 'Notare che anche in questo caso il secondo parametro 053. 'è opzionale 054. Sub PrintIndexed(ByVal Data() As Single, _ 055. Optional ByVal ReverseOrder As Boolean = False) 056. If ReverseOrder Then 057. For I As Int32 = Data.Length - 1 To 0 Step -1 058. Console.WriteLine("{0}) {1}", Data.Length - I, Data(I)) 059. Next 060. Else 061. For I As Int32 = 0 To Data.Length - 1 062. Console.WriteLine("{0}) {1}", (I + 1), Data(I)) 063. Next 064. End If 065. End Sub 066. 067. Sub Main() 068. Dim Process As ProcessData 069. Dim Print As PrintData 070. Dim Data() As Single 071. Dim Len As Int32 072. Dim Cmd As Char 073. 074.
  • 134. Console.WriteLine("Quanti valori inserire?") 075. Len = Console.ReadLine 076. 077. ReDim Data(Len - 1) 078. For I As Int32 = 1 To Len 079. Console.Write("Inserire il valore " & I & ": ") 080. Data(I - 1) = Console.ReadLine 081. Next 082. 083. Console.Clear() 084. 085. Console.WriteLine("Scegliere l'operazione da eseguire: ") 086. Console.WriteLine("m - Calcola la media dei valori;") 087. Console.WriteLine("v - Calcola la varianza dei valori;") 088. Cmd = Console.ReadKey().KeyChar 089. Select Case Cmd 090. Case "m" 091. Process = New ProcessData(AddressOf CalculateAverage) 092. Case "v" 093. Process = New ProcessData(AddressOf CalculateVariance) 094. Case Else 095. Console.WriteLine("Comando non valido!") 096. Exit Sub 097. End Select 098. Console.WriteLine() 099. Console.WriteLine("Scegliere il metodo di stampa: ") 100. Console.WriteLine("s - Stampa i valori;") 101. Console.WriteLine("i - Stampa i valori con il numero ordinale a fianco.") 102. Cmd = Console.ReadKey().KeyChar 103. Select Case Cmd 104. Case "s" 105. Print = New PrintData(AddressOf PrintNormal) 106. Case "i" 107. Print = New PrintData(AddressOf PrintIndexed) 108. Case Else 109. Console.WriteLine("Comando non valido!") 110. Exit Sub 111. End Select 112. 113. Console.Clear() 114. 115. Console.WriteLine("Valori:") 116. 'Eccoci arrivati al punto. Come detto prima, i delegate 117. 'non possono definire una signature che comprenda parametri 118. 'opzionali o indefiniti, ma si 119. 'può aggirare questa limitazione semplicemente dichiarando 120. 'un array di valori al posto del ParamArray (in quanto si 121. 'tratta comunque di due vettori) e lo stesso parametro 122. 'non opzionale al posto del parametro opzionale. 123. 'L'inconveniente, in questo ultimo caso, è che il 124. 'parametro, pur essendo opzionale va sempre specificato 125. 'quando il metodo viene richiamato attraverso un oggetto 126. 'delegate. Questo escamotage permette di aumentare la 127. 'portata dei delegate, includendo anche metodi che 128. 'possono essere stati scritti tempo prima in un'altra 129. 'parte inaccessibile del codice: così 130. 'non è necessario riscriverli! 131. Print(Data, False) 132. Console.WriteLine("Risultato:") 133. Console.WriteLine(Process(Data)) 134. 135. Console.ReadKey() 136. End Sub 137. 138. End Module Un esempio più signific ativo I delegate sono par ticolar mente utili per r ispar miar e spazio nel codice. Tr amite i delegate, infatti, possiamo usar e lo
  • 135. stesso metodo per eseguir e più compiti differ enti. Dato che una var iabile delegate contiene un r ifr iento ad un metodo qualsiasi, semplicemente cambiando questo r ifer imento possiamo eseguir e codici diver si r ichiamando la stessa var iabile. E' come se potessimo "innestar e" del codice sempr e diver so su un substr ato costante. Ecco un esempio piccolo, ma significativo: 01. Module Module2 02. 'Nome del file da cercare 03. Dim File As String 04. 05. 'Questo delegate referenzia una funzione che accetta un 06. 'parametro stringa e restituisce un valore booleano 07. Delegate Function IsMyFile(ByVal FileName As String) As Boolean 08. 09. 'Funzione 1, stampa il contenuto del file a schermo 10. Function PrintFile(ByVal FileName As String) As Boolean 11. 'Io.Path.GetFileName(F) restituisce solo il nome del 12. 'singolo file F, togliendo il percorso delle cartelle 13. If IO.Path.GetFileName(FileName) = File Then 14. 'IO.File.ReadAllText(F) restituisce il testo contenuto 15. 'nel file F in una sola operazione 16. Console.WriteLine(IO.File.ReadAllText(FileName)) 17. Return True 18. End If 19. Return False 20. End Function 21. 22. 'Funzione 2, copia il file sul desktop 23. Function CopyFile(ByVal FileName As String) As Boolean 24. If IO.Path.GetFileName(FileName) = File Then 25. 'IO.File.Copy(S, D) copia il file S nel file D: 26. 'se D non esiste viene creato, se esiste viene 27. 'sovrascritto 28. IO.File.Copy(FileName, _ 29. My.Computer.FileSystem.SpecialDirectories.Desktop & _ 30. "" & File) 31. Return True 32. End If 33. Return False 34. End Function 35. 36. 'Procedura ricorsiva che cerca il file 37. Function SearchFile(ByVal Dir As String, ByVal IsOK As IsMyFile) _ 38. As Boolean 39. 'Ottiene tutte le sottodirectory 40. Dim Dirs() As String = IO.Directory.GetDirectories(Dir) 41. 'Ottiene tutti i files 42. Dim Files() As String = IO.Directory.GetFiles(Dir) 43. 44. 'Analizza ogni file per vedere se è quello cercato 45. For Each F As String In Files 46. 'È il file cercato, basta cercare 47. If IsOK(F) Then 48. 'Termina la funzione e restituisce Vero, cosicché 49. 'anche nel for sulle cartelle si termini 50. 'la ricerca 51. Return True 52. End If 53. Next 54. 55. 'Analizza tutte le sottocartelle 56. For Each D As String In Dirs 57. If SearchFile(D, IsOK) Then 58. 'Termina ricorsivamente la ricerca 59. Return True 60. End If 61. Next 62. End Function 63. 64. Sub Main() 65. Dim Dir As String 66.
  • 136. 67. Console.WriteLine("Inserire il nome file da cercare:") 68. File = Console.ReadLine 69. 70. Console.WriteLine("Inserire la cartella in cui cercare:") 71. Dir = Console.ReadLine 72. 73. 'Cerca il file e lo scrive a schermo 74. SearchFile(Dir, AddressOf PrintFile) 75. 76. 'Cerca il file e lo copia sul desktop 77. SearchFile(Dir, AddressOf CopyFile) 78. 79. Console.ReadKey() 80. End Sub 81. End Module Nel sor gente si vede che si usano pochissime r ighe per far compier e due oper azioni molto differ enti alla stessa pr ocedur a. In altr e condizioni, un aspir ante pr ogr ammator e che non conoscesse i delegate avr ebbe scr itto due pr ocedur e inter e, spr ecando più spazio, e condannandosi, inoltr e, a r iscr iver e la stessa cosa per ogni futur a var iante.
  • 137. A35. I Delegate Multicast Al contr ar io di un delegate semplice, un delegate multicast può contener e r ifer imenti a più metodi insieme, pur ché della stessa categor ia e con la stessa signatur e. Dato che il costr uttor e è sempr e lo stesso e accetta un solo par ametr o, non è possibile cr ear e delegate multicast in fase di inizializzazione. L'unico modo per far lo è r ichiamar e il metodo statico Combine della classe System.Delegate (ossia la classe base di tutti i delegate). Combine espone anche un over load che per mette di unir e molti delegate alla volta, specificandoli tr amite un Par amAr r ay. Dato che un delegate multicast contiene più r ifer imenti a metodi distinti, si par la di inv o catio n list (lista di invocazione) quando ci si r ifer isce all'insieme di tutti i metodi memor izzati in un delegate multicast. Ecco un semplice esempio: 01. Module Module2 02. 'Vedi esempio precedente 03. Sub Main() 04. Dim Dir As String 05. Dim D As IsMyFile 06. 07. Console.WriteLine("Inserire il nome file da cercare:") 08. File = Console.ReadLine 09. 10. Console.WriteLine("Inserire la cartella in cui cercare:") 11. Dir = Console.ReadLine 12. 13. 'Crea un delegate multicast, unendo PrintFile e CopyFile. 14. 'Da notare che in questa espressione è necessario usare 15. 'delle vere e proprie variabili delegate, poiché 16. 'l'operatore AddressOf da solo non è valido in questo caso 17. D = System.Delegate.Combine(New IsMyFile(AddressOf PrintFile), _ 18. New IsMyFile(AddressOf CopyFile)) 19. 'Per la cronaca, Combine è un metodo factory 20. 21. 'Ora il file trovato viene sia visualizzato che copiato 22. 'sul desktop 23. SearchFile(Dir, D) 24. 25. 'Se si vuole rimuovere uno o più riferimenti a metodi del 26. 'delegate multicast si deve utilizzare il metodo statico Remove: 27. D = System.Delegate.Remove(D, New IsMyFile(AddressOf CopyFile)) 28. 'Ora D farà visualizzare solamente il file trovato 29. 30. Console.ReadKey() 31. End Sub 32. End Module La funzione Combine, tuttavia, nasconde molte insidie. Infatti, essendo un metodo factor y della classe System.Delegate, come abbiamo detto nel capitolo r elativo ai metodi factor y, r estituisce un oggetto di tipo System.Delegate. Nell'esempio, noi abbiamo potuto assegnar e il valor e r estituito da Combine a D, che è di tipo IsMyFile, per chè solitamente le opzioni di compilazione per mettono di eseguir e conver sioni implicite di questo tipo - ossia Option Str ict è solitamente impostato su Off (per ulter ior i infor mazioni, veder e il capitolo sulle opzioni di compilazione). Come abbiamo detto nel capitolo sulle conver sioni, assegnar e il valor e di una classe der ivata a una classe base è lecito, poichè nel passaggio da una all'altr a non si per de alcun dato, ma si gener elizza soltanto il valor e r appr esentato; eseguir e il passaggio inver so, invece, ossia assegnar e una classe base a una der ivata, può r isultar e in qualche str ano er r or e per chè i membr i in più della classe der ivata sono vuoti. Nel caso dei delegate, che sono oggetti immutabili, e che quindi non espongono pr opr ietà modificabili, questo non è un pr oblema, ma il compilator e questo non lo sa. Per esser e sicur i, è meglio utilizzar e un oper ator e di cast come Dir ectCast: 1. DirectCast(System.Delegate.Combine(A, B), IsMyFile) N.B.: Quando un delegate multicast contiene delle funzioni e viene r ichiamato, il valor e r estituito è quello della pr ima
  • 138. funzione memor izzata. Ecco or a un altr o esempio molto ar ticolato sui delegate multicast: 001. 'Questo esempio si basa completamente sulla manipolazione 002. 'di file e cartelle, argomento non ancora affrontato. Se volete, 003. 'potete dare uno sguardo ai capitoli relativi nelle parti 004. 'successive della guida, oppure potete anche limitarvi a leggere 005. 'i commenti, che spiegano tutto ciò che accade. 006. Module Module1 007. 'In questo esempio eseguiremo delle operazioni su file con i delegate. 008. 'Nel menù sarà possibile scegliere quali operazioni 009. 'eseguire (una o tutte insieme) e sotto quali condizioni modificare 010. 'un file. 011. 'Il delegate FileFilter rappresenta una funzione che restituisce 012. 'True se la condizione è soddisfatta. Le condizioni 013. 'sono racchiuse in un delegate multicast che contiene più 014. 'funzioni di questo tipo 015. Delegate Function FileFilter(ByVal FileName As String) As Boolean 016. 'Il prossimo delegate rappresenta un'operazione su un file 017. Delegate Sub MassFileOperation(ByVal FileName As String) 018. 'AskForData è un delegate del tipo più semplice. 019. 'Servirà per reperire le informazioni necessarie ad 020. 'eseguire le operazioni (ad esempio, se si sceglie di copiare 021. 'tutti i file di una cartella, si dovrà anche scegliere 022. 'dove copiare questi file). 023. Delegate Sub AskForData() 024. 025. 'Queste variabili globali rappresentano le informazioni necesarie 026. 'per lo svolgimento delle operazioni o la verifica delle condizioni. 027. 028. 'Stringa di formato per rinominare i file 029. Dim RenameFormat As String 030. 'Posizione di un file nella cartella 031. Dim FileIndex As Int32 032. 'Directory in cui copiare i file 033. Dim CopyDirectory As String 034. 'File in cui scrivere. Il tipo StreamWriter permette di scrivere 035. 'facilmente stringhe su un file usando WriteLine come in Console 036. Dim LogFile As IO.StreamWriter 037. 'Limitazioni sulla data di creazione del file 038. Dim CreationDateFrom, CreationDateTo As Date 039. 'Limitazioni sulla data di ultimo accesso al file 040. Dim LastAccessDateFrom, LastAccessDateTo As Date 041. 'Limitazioni sulla dimensione 042. Dim SizeFrom, SizeTo As Int64 043. 044. 'Rinomina un file 045. Sub Rename(ByVal Path As String) 046. 'Ne prende il nome semplice, senza estensione 047. Dim Name As String = IO.Path.GetFileNameWithoutExtension(Path) 048. 'Apre un oggetto contenente le informazioni sul file 049. 'di percorso Path 050. Dim Info As New IO.FileInfo(Path) 051. 052. 'Formatta il nome secondo la stringa di formato RenameFormat 053. Name = String.Format(RenameFormat, _ 054. Name, FileIndex, Info.Length, Info.LastAccessTime, Info.CreationTime) 055. 'E aggiunge ancora l'estensione al nome modificato 056. Name &= IO.Path.GetExtension(Path) 057. 'Copia il vecchio file nella stessa cartella, ma con il nuovo nome 058. IO.File.Copy(Path, IO.Path.GetDirectoryName(Path) & "" & Name) 059. 'Elimina il vecchio file 060. IO.File.Delete(Path) 061. 062. 'Aumenta l'indice di uno 063. FileIndex += 1 064. End Sub 065. 066. 'Funzione che richiede i dati necessari per far funzionare 067. 'il metodo Rename 068. Sub InputRenameFormat() 069.
  • 139. Console.WriteLine("Immettere una stringa di formato valida per rinominare i file.") 070. Console.WriteLine("I parametri sono:") 071. Console.WriteLine("0 = Nome originale del file;") 072. Console.WriteLine("1 = Posizione del file nella cartella, in base 0;") 073. Console.WriteLine("2 = Dimensione del file, in bytes;") 074. Console.WriteLine("3 = Data dell'ultimo accesso;") 075. Console.WriteLine("4 = Data di creazione.") 076. RenameFormat = Console.ReadLine 077. End Sub 078. 079. 'Elimina un file di percorso Path 080. Sub Delete(ByVal Path As String) 081. IO.File.Delete(Path) 082. End Sub 083. 084. 'Copia il file da Path alla nuova cartella 085. Sub Copy(ByVal Path As String) 086. IO.File.Copy(Path, CopyDirectory & "" & IO.Path.GetFileName(Path)) 087. End Sub 088. 089. 'Richiede una cartella valida in cui copiare i file. Se non esiste, 090. la crea 091. Sub InputCopyDirectory() 092. Console.WriteLine("Inserire una cartella valida in cui copiare i file:") 093. CopyDirectory = Console.ReadLine 094. If Not IO.Directory.Exists(CopyDirectory) Then 095. IO.Directory.CreateDirectory(CopyDirectory) 096. End If 097. End Sub 098. 099. 'Scrive il nome del file sul file aperto 100. Sub Archive(ByVal Path As String) 101. LogFile.WriteLine(IO.Path.GetFileName(Path)) 102. End Sub 103. 104. 'Chiede il nome di un file su cui scrivere tutte le informazioni 105. Sub InputLogFile() 106. Console.WriteLine("Inserire il percorso del file su cui scrivere:") 107. LogFile = New IO.StreamWriter(Console.ReadLine) 108. End Sub 109. 110. 'Verifica che la data di creazione del file cada tra i limiti fissati 111. Function IsCreationDateValid(ByVal Path As String) As Boolean 112. Dim Info As New IO.FileInfo(Path) 113. Return (Info.CreationTime >= CreationDateFrom) And (Info.CreationTime >= CreationDateTo) 114. End Function 115. 116. 'Richiede di immettere una limitazione temporale per considerare 117. 'solo certi file 118. Sub InputCreationDates() 119. Console.WriteLine("Verranno considerati solo i file con data di creazione:") 120. Console.Write("Da: ") 121. CreationDateFrom = Date.Parse(Console.ReadLine) 122. Console.Write("A: ") 123. CreationDateTo = Date.Parse(Console.ReadLine) 124. End Sub 125. 126. 'Verifica che la data di ultimo accesso al file cada tra i limiti fissati 127. Function IsLastAccessDateValid(ByVal Path As String) As Boolean 128. Dim Info As New IO.FileInfo(Path) 129. Return (Info.LastAccessTime >= LastAccessDateFrom) And (Info.LastAccessTime >= LastAccessDateTo) 130. End Function 131. 132. 'Richiede di immettere una limitazione temporale per considerare 133. 'solo certi file 134. Sub InputLastAccessDates() 135. Console.WriteLine("Verranno considerati solo i file con data di creazione:") 136. Console.Write("Da: ") 137. LastAccessDateFrom = Date.Parse(Console.ReadLine) 138. Console.Write("A: ") 139.
  • 140. LastAccessDateTo = Date.Parse(Console.ReadLine) 140. End Sub 141. 142. 'Verifica che la dimensione del file sia coerente coi limiti fissati 143. Function IsSizeValid(ByVal Path As String) As Boolean 144. Dim Info As New IO.FileInfo(Path) 145. Return (Info.Length >= SizeFrom) And (Info.Length >= SizeTo) 146. End Function 147. 148. 'Richiede di specificare dei limiti dimensionali per i file 149. Sub InputSizeLimit() 150. Console.WriteLine("Verranno considerati solo i file con dimensione compresa:") 151. Console.Write("Tra (bytes):") 152. SizeFrom = Console.ReadLine 153. Console.Write("E (bytes):") 154. SizeTo = Console.ReadLine 155. End Sub 156. 157. 'Classe che rappresenta un'operazione eseguibile su file 158. Class Operation 159. Private _Description As String 160. Private _Execute As MassFileOperation 161. Private _RequireData As AskForData 162. Private _Enabled As Boolean 163. 164. 'Descrizione 165. Public Property Description() As String 166. Get 167. Return _Description 168. End Get 169. Set(ByVal value As String) 170. _Description = value 171. End Set 172. End Property 173. 174. 'Variabile che contiene l'oggetto delegate associato 175. 'a questa operazione, ossia un riferimento a una delle Sub 176. 'definite poco sopra 177. Public Property Execute() As MassFileOperation 178. Get 179. Return _Execute 180. End Get 181. Set(ByVal value As MassFileOperation) 182. _Execute = value 183. End Set 184. End Property 185. 186. 'Variabile che contiene l'oggetto delegate che serve 187. 'per reperire informazioni necessarie ad eseguire 188. 'l'operazione, ossia un riferimento a una delle sub 189. 'di Input definite poco sopra. E' Nothing quando 190. 'non serve nessun dato ausiliario (come nel caso 191. 'di Delete) 192. Public Property RequireData() As AskForData 193. Get 194. Return _RequireData 195. End Get 196. Set(ByVal value As AskForData) 197. _RequireData = value 198. End Set 199. End Property 200. 201. 'Determina se l'operazione va eseguita oppure no 202. Public Property Enabled() As Boolean 203. Get 204. Return _Enabled 205. End Get 206. Set(ByVal value As Boolean) 207. _Enabled = value 208. End Set 209. End Property 210. 211.
  • 141. Sub New(ByVal Description As String, _ 212. ByVal ExecuteMethod As MassFileOperation, _ 213. ByVal RequireDataMethod As AskForData) 214. Me.Description = Description 215. Me.Execute = ExecuteMethod 216. Me.RequireData = RequireDataMethod 217. Me.Enabled = False 218. End Sub 219. End Class 220. 221. 'Classe che rappresenta una condizione a cui sottoporre 222. 'i file nella cartella: verranno elaborati solo quelli che 223. 'soddisfano tutte le condizioni 224. Class Condition 225. Private _Description As String 226. Private _Verify As FileFilter 227. Private _RequireData As AskForData 228. Private _Enabled As Boolean 229. 230. Public Property Description() As String 231. Get 232. Return _Description 233. End Get 234. Set(ByVal value As String) 235. _Description = value 236. End Set 237. End Property 238. 239. 'Contiene un oggetto delegate associato a una delle 240. 'precedenti funzioni 241. Public Property Verify() As FileFilter 242. Get 243. Return _Verify 244. End Get 245. Set(ByVal value As FileFilter) 246. _Verify = value 247. End Set 248. End Property 249. 250. Public Property RequireData() As AskForData 251. Get 252. Return _RequireData 253. End Get 254. Set(ByVal value As AskForData) 255. _RequireData = value 256. End Set 257. End Property 258. 259. Public Property Enabled() As Boolean 260. Get 261. Return _Enabled 262. End Get 263. Set(ByVal value As Boolean) 264. _Enabled = value 265. End Set 266. End Property 267. 268. Sub New(ByVal Description As String, _ 269. ByVal VerifyMethod As FileFilter, _ 270. ByVal RequireDataMethod As AskForData) 271. Me.Description = Description 272. Me.Verify = VerifyMethod 273. Me.RequireData = RequireDataMethod 274. End Sub 275. End Class 276. 277. Sub Main() 278. 'Contiene tutte le operazioni da eseguire: sarà, quindi, un 279. 'delegate multicast 280. Dim DoOperations As MassFileOperation 281. 'Contiene tutte le condizioni da verificare 282. Dim VerifyConditions As FileFilter 283.
  • 142. 'Indica la cartella di cui analizzare i file 284. Dim Folder As String 285. 'Hashtable di caratteri-Operation o carattri-Condition. Il 286. 'carattere indica quale tasto è necessario 287. 'premere per attivare/disattivare l'operazione/condizione 288. Dim Operations As New Hashtable 289. Dim Conditions As New Hashtable 290. Dim Cmd As Char 291. 292. 'Aggiunge le operazioni esistenti. La 'c' messa dopo la stringa 293. 'indica che la costante digitata è un carattere e non una 294. 'stringa. Il sistema non riesce a distinguere tra stringhe di 295. lunghezza 1 e caratteri, al contrario di come accade in C 296. With Operations 297. .Add("r"c, New Operation("Rinomina tutti i file nella cartella;", _ 298. New MassFileOperation(AddressOf Rename), _ 299. New AskForData(AddressOf InputRenameFormat))) 300. .Add("c"c, New Operation("Copia tutti i file nella cartella in un'altra cartella;", _ 301. New MassFileOperation(AddressOf Copy), _ 302. New AskForData(AddressOf InputCopyDirectory))) 303. .Add("a"c, New Operation("Scrive il nome di tutti i file nella cartella su un file;", _ 304. New MassFileOperation(AddressOf Archive), _ 305. New AskForData(AddressOf InputLogFile))) 306. .Add("d"c, New Operation("Cancella tutti i file nella cartella;", _ 307. New MassFileOperation(AddressOf Delete), _ 308. Nothing)) 309. End With 310. 311. 'Aggiunge le condizioni esistenti 312. With Conditions 313. .Add("r"c, New Condition("Seleziona i file da elaborare in base alla data di creazione;", _ 314. New FileFilter(AddressOf IsCreationDateValid), _ 315. New AskForData(AddressOf InputCreationDates))) 316. .Add("l"c, New Condition("Seleziona i file da elaborare in base all'ultimo accesso;", _ 317. New FileFilter(AddressOf IsLastAccessDateValid), _ 318. New AskForData(AddressOf InputLastAccessDates))) 319. .Add("s"c, New Condition("Seleziona i file da elaborare in base alla dimensione;", _ 320. New FileFilter(AddressOf IsSizeValid), _ 321. New AskForData(AddressOf InputSizeLimit))) 322. End With 323. 324. Console.WriteLine("Modifica in massa di file ---") 325. Console.WriteLine() 326. 327. Do 328. Console.WriteLine("Immetti il percorso della cartella su cui operare:") 329. Folder = Console.ReadLine 330. Loop Until IO.Directory.Exists(Folder) 331. 332. Do 333. Console.Clear() 334. Console.WriteLine("Premere la lettera corrispondente per selezionare la voce.") 335. Console.WriteLine("Premere 'e' per procedere.") 336. Console.WriteLine() 337. For Each Key As Char In Operations.Keys 338. 'Disegna sullo schermo una casella di spunta, piena: 339. ' [X] 340. 'se l'operazione è attivata, altrimenti vuota: 341. ' [ ] 342. Console.Write("[") 343. If Operations(Key).Enabled = True Then 344. Console.Write("X") 345. Else 346. Console.Write(" ") 347. End If 348. Console.Write("] ") 349. 'Scrive quindi il carattere da premere e vi associa la descrizione 350.
  • 143. Console.Write(Key) 351. Console.Write(" - ") 352. Console.WriteLine(Operations(Key).Description) 353. Next 354. Cmd = Console.ReadKey().KeyChar 355. If Operations.ContainsKey(Cmd) Then 356. Operations(Cmd).Enabled = Not Operations(Cmd).Enabled 357. End If 358. Loop Until Cmd = "e"c 359. 360. Do 361. Console.Clear() 362. Console.WriteLine("Premere la lettera corrispondente per selezionare la voce.") 363. Console.WriteLine("Premere 'e' per procedere.") 364. Console.WriteLine() 365. For Each Key As Char In Conditions.Keys 366. Console.Write("[") 367. If Conditions(Key).Enabled = True Then 368. Console.Write("X") 369. Else 370. Console.Write(" ") 371. End If 372. Console.Write("] ") 373. Console.Write(Key) 374. Console.Write(" - ") 375. Console.WriteLine(Conditions(Key).Description) 376. Next 377. Cmd = Console.ReadKey().KeyChar 378. If Conditions.ContainsKey(Cmd) Then 379. Conditions(Cmd).Enabled = Not Conditions(Cmd).Enabled 380. End If 381. Loop Until Cmd = "e"c 382. 383. Console.Clear() 384. Console.WriteLine("Acquisizione informazioni") 385. Console.WriteLine() 386. 387. 'Cicla su tutte le operazioni presenti nell'Hashtable. 388. For Each Op As Operation In Operations.Values 389. 'Se l'operazione è attivata... 390. If (Op.Enabled) Then 391. 'Se richiede dati ausiliari, invoca il delegate memorizzato 392. 'nella proprietà RequireData. Invoke è un metodo 393. 'di istanza che invoca i metodi contenuti nel delegate. 394. 'Si può anche scrivere: 395. ' Op.RequireData()() 396. 'Dove la prima coppia di parentesi indica che la proprietà 397. 'non è indicizzata e la seconda, in questo caso, specifica 398. 'che il metodo sotteso dal delegate non richiede parametri. 399. 'È più comprensibile la prima forma 400. If Op.RequireData IsNot Nothing Then 401. Op.RequireData.Invoke() 402. End If 403. 'Se DoOperations non contiene ancora nulla, vi inserisce Op.Execute 404. If DoOperations Is Nothing Then 405. DoOperations = Op.Execute 406. Else 407. 'Altrimenti, combina gli oggetti delegate già memorizzati 408. 'con il nuovo 409. DoOperations = System.Delegate.Combine(DoOperations, Op.Execute) 410. End If 411. End If 412. Next 413. 414. For Each C As Condition In Conditions.Values 415. If C.Enabled Then 416. If C.RequireData IsNot Nothing Then 417. C.RequireData.Invoke() 418. End If 419. If VerifyConditions Is Nothing Then 420. VerifyConditions = C.Verify 421. Else 422.
  • 144. VerifyConditions = System.Delegate.Combine(VerifyConditions, C.Verify) 423. End If 424. End If 425. Next 426. 427. FileIndex = 0 428. For Each File As String In IO.Directory.GetFiles(Folder) 429. 'Ok indica se il file ha passato le condizioni 430. Dim Ok As Boolean = True 431. 'Se ci sono condizioni da applicare, le verifica 432. If VerifyConditions IsNot Nothing Then 433. 'Dato che nel caso di delegate multicast contenenti 434. 'rifermenti a funzione, il valore restituito è 435. 'solo quello della prima funzione e a noi interessano 436. '<b>tutti</b> i valori restituiti, dobbiamo enumerare 437. 'ogni singolo oggetto delegate presente nel 438. 'delegate multicast e invocarlo singolarmente. 439. 'Ci viene in aiuto il metodo di istanza GetInvocationList, 440. 'che restituisce un array di delegate singoli. 441. For Each C As FileFilter In VerifyConditions.GetInvocationList() 442. 'Tutte le condizioni attive devono essere verificate, 443. 'quindi bisogna usare un And 444. Ok = Ok And C(File) 445. Next 446. End If 447. 'Se le condizioni sono verificate, esegue le operazioni 448. If Ok Then 449. Try 450. DoOperations(File) 451. Catch Ex As Exception 452. Console.WriteLine("Impossibile eseguire l'operazione: " & Ex.Message) 453. End Try 454. End If 455. Next 456. 'Chiude il file di log se era aperto 457. If LogFile IsNot Nothing Then 458. LogFile.Close() 459. End If 460. 461. Console.WriteLine("Operazioni eseguite con successo!") 462. Console.ReadKey() 463. End Sub 464. 465. End Module Questo esempio molto ar tificioso è solo un assaggio delle potenzialità dei delegate (noter ete che ci sono anche molti conflitti, ad esempio se si seleziona sia copia che elimina, i file potr ebber o esser e cancellati pr ima della copia a seconda dell'or dine di invocazione). Vedr emo fr a poco come utilizzar e alcuni delegate piuttosto comuni messi a disposizione dal Fr amew or k, e scopr ir emo nella sezione B che i delegate sono il meccanismo fondamentale alla base di tutto il sistema degli ev enti. Alc uni membri importanti per i delegate multic ast La classe System.Delegate espone alcuni metodi statici pubblici, molti dei quali sono davver o utili quando si tr atta di delegate multicast. Eccone una br eve lista: Combine(A, B) o Combine(A, B, C, ...) : fonde insieme più delegate per cr ear e un unico delegate multicast invocando il quale vengono invocati tutti i metodi in esso contenuti; GetInvocationList() : funzione d'istanza che r estituisce un ar r ay di oggetti di tipo System.Delegate, i quali r appr esentano i singoli delegate che sono stati memor izzati nell'unica var iabile Remove(A, B) : r imuove l'oggetto delegate B dalla invocation list di A (ossia dalla lista di tutti i singoli delegate memor izzati in A). Si suppone che A sia multicast. Se anche B è multicast, solo l'ultimo elemento dell'invocation list di B viene r imosso da quella di A
  • 145. RemoveAll(A, B) : r imuove tutte le occor r enze degli elementi pr esenti nell'invocation list di B da quella di A. Si suppone che sia A che B siano multicast
  • 146. A36. Classi Astratte, Sigillate e Parziali Classi Astratte Le classi astr atte sono speciali classi che esistono con il solo scopo di esser e er editate da altr e classi: non possono esser e usate da sole, non espongono costr uttor i e alcuni lor o metodi sono pr ivi di un cor po. Queste sono car atter istiche molto peculiar i, e anche abbastanza str ane, che, tuttavia, nascondono un potenziale segr eto. Se qualcuno dei miei venticinque lettor i avesse avuto l'occasione di osser var e qualcuno dei miei sor genti, avr ebbe notato che in più di un occasione ho fatto uso di classi mar cate con la keyw or d MustInher it. Questa è la par ola r iser vata che si usa per r ender e as tratta una classe. L'utilizzo pr incipale delle classi astr atte è quello di for nir e un o s cheletro o un a bas e di as trazion e per altr e classi. Pr endiamo come esempio uno dei miei pr ogr ammi, che potete tr ovar e nella sezione dow nload, Totem Char ting: ci r ifer ir emo al file Char t.vb. In questo sor gente, la pr ima classe che incontr ate è definita come segue: 1. <Serializable()> _ 2. Public MustInherit Class Chart Per or a lasciamo per der e ciò che viene compr eso tr a le par entesi angolar i e focalizziamoci sulla dichiar azione nuda e cr uda. Quella che avete visto è pr opr io la dichiar azione di una classe astr atta, dove MustInher it significa appunto "deve er editar e", come r ipor tato nella definizione poco sopr a. Char t r appr esenta un gr afico: espone delle pr opr ietà (Pr oper ties, Type, Sur face, Plane, ...) e un paio di metodi Pr otected. Sar ete d'accor do con me nell'asser ir e che ogni gr afico può aver e una legenda e può contemplar e un insieme di dati limitato per cui esista un massimo: ne concludiamo che i due metodi in questione ser vono a tutti i gr afici ed è cor r etto che siano stati definiti all'inter no del cor po di Char t. Ma or a andiamo un po' più in su e tr oviamo questa singolar e dichiar azione di metodo: 1. Public MustOverride Sub Draw() Non c'è il cor po del metodo! Aiuto! L'hanno r ubato! No... Si dà il caso che nelle classi astr atte possano esister e anche metodi astr atti, ossia che devono esser e per for za r idefiniti tr amite polimor fismo nelle classi der ivate. E questo è abbastanza semplice da capir e: un gr afico deve poter esser e disegnato, quindi ogni oggetto gr afico deve espor r e il metodo Dr aw , ma c'è un piccolo inconveniente. Dato che non esiste un solo tipo di gr afico - ce ne sono molti, e nel codice di Totem Char ting vengono contemplati solo gli istogr ammi, gli ar eaogr ammi e i gr afici a disper sione - non possiamo saper e a pr ior i che codice dovr emmo usar e per effettuar e il r ender ing (ossia per disegnar e ciò che ser ve). Sappiamo, per ò, che dovr emo disegnar e qualcosa: allor a lasciamo il compito di definir e un codice adeguato alle classi der ivate (nella fattispecie, Histogr am, PieChar t, LinesChar t, Disper sionChar t). Questo è pr opr io l'utilizzo delle classi astr atte: definir e un ar chetipo, uno schema, sulla base del quale le classi che lo er editer anno dovr anno modellar e il pr opr io compor tamento. Altr a osser vazione: le classi astr atte, come dice il nome stesso, sono utilizzate per r appr esentar e concetti astr atti, che non possono concr etamente esser e istanziati: ad esempio, non ha senso un oggetto di tipo Char t, per chè non esiste un gr afico gener ico pr ivo di qualsiasi car atter istica, ma esiste solo declinato in una delle altr e for me sopr a r ipor tate. Natur almente, valgono ancor a tutte le r egole r elative agli specificator i di accesso e all'er editar ietà e sono utilizzabili tutti i meccanismi già illustr ati, compr eso l'over loading; infatti, ho dichiar ato due metodi Pr otected per chè ser vir anno alle classi der ivate. Inoltr e, una classe astr atta può anche er editar e da un'altr a classe astr atta: in questo caso, tutti i metodi mar cati con MustOver r ide dovr anno subir e una di queste sor ti: Esser e modificati tr amite polimor fismo, definendone, quindi, il cor po; Esser e r idichiar ati MustOver r ide, r imandandone ancor a la definizione. Nel secondo caso, si r imanda ancor a la definizione di un cor po valido alla "discendenza", ma c'è un piccolo ar tifizio da
  • 147. adottar e: eccone una dimostr azione nel pr ossimo esempio: 001. Module Module1 002. 003. 'Classe astratta che rappresenta un risolutore di equazioni. 004. 'Dato che di equazioni ce ne possono essere molte tipologie 005. 'differenti, non ha senso rendere questa classe istanziabile. 006. 'Provando a scrivere qualcosa come: 007. ' Dim Eq As New EquationSolver() 008. 'Vi verrà comunicato un errore, in quanto le classi 009. 'astratte sono per loro natura non istanziabili 010. MustInherit Class EquationSolver 011. 'Per lo stesso discorso fatto prima, se non conosciamo come 012. 'è fatta l'equazione che questo tipo contiene non 013. 'possiamo neppure tentare di risolverla. Perciò 014. 'ci limitiamo a dichiarare una funzione Solve come MustOverride. 015. 'Notate che il tipo restituito è un array di Single, 016. 'in quanto le soluzioni saranno spesso più di una. 017. Public MustOverride Function Solve() As Single() 018. End Class 019. 020. 'La prossima classe rappresenta un risolutore di equazioni 021. 'polinomiali. Dato che la tipologia è ben definita, 022. 'avremmo potuto anche <i>non</i> rendere astratta la classe 023. 'e, nella funzione Solve, utilizzare un Select Case per 024. 'controllare il grado dell'equazione. Ad ogni modo, è 025. 'utile vedere come si comporta l'erediterietà attraverso 026. 'più classi astratte. 027. 'Inoltre, ci ritornerà molto utile in seguito disporre 028. 'di questa classe astratta intermedia 029. MustInherit Class PolynomialEquationSolver 030. Inherits EquationSolver 031. 032. Private _Coefficients() As Single 033. 034. 'Array di Single che contiene i coefficienti dei 035. 'termini di i-esimo grado all'interno dell'equazione. 036. 'L'elemento 0 dell'array indica il coefficiente del 037. 'termine a grado massimo. 038. Public Property Coefficients() As Single() 039. Get 040. Return _Coefficients 041. End Get 042. Set(ByVal value As Single()) 043. _Coefficients = value 044. End Set 045. End Property 046. 047. 'Ecco quello a cui volevo arrivare. Se un metodo astratto 048. 'lo si vuole mantenere tale anche nella classe derivata, 049. 'non basta scrivere: 050. ' MustOverride Function Solve() As Single() 051. 'Percè in questo caso verrebbe interpretato come 052. 'un membro che non c'entra niente con MyBase.Solve, 053. 'e si genererebbe un errore in quanto stiamo tentando 054. 'di dichiarare un nuovo membro con lo stesso nome 055. 'di un membro della classe base. 056. 'Per questo motivo, dobbiamo comunque usare il polimorfismo 057. 'come se si trattasse di un normale metodo e dichiararlo 058. 'Overrides. In aggiunta a questo, deve anche essere 059. 'astratto, e perciò aggiungiamo MustOverride: 060. Public MustOverride Overrides Function Solve() As Single() 061. 062. 'Anche in questo caso usiamo il polimorfismo, ma ci riferiamo 063. 'alla semplice funzione ToString, derivata dalla classe base 064. 'di tutte le entità esistenti, System.Object. 065. 'Questa si limita a restituire una stringa che rappresenta 066. 'l'equazione a partire dai suoi coefficienti. Ad esempio: 067. ' 3x^2 + 2x^1 + 4x^0 = 0 068. 'Potete modificare il codice per eliminare le forme ridondanti 069. 'x^1 e x^0. 070. Public Overrides Function ToString() As String 071.
  • 148. Dim Result As String = "" 072. 073. For I As Int16 = 0 To Me.Coefficients.Length - 1 074. If I > 0 Then 075. Result &= " + " 076. End If 077. Result &= String.Format("{0}x^{1}", _ 078. Me.Coefficients(I), Me.Coefficients.Length - 1 - I) 079. Next 080. 081. Result &= " = 0" 082. 083. Return Result 084. End Function 085. End Class 086. 087. 'Rappresenta un risolutore di equazioni non polinomiali. 088. 'La classe non è astratta, ma non presenta alcun codice. 089. 'Per risolvere questo tipo di equazioni, è necessario 090. 'sapere qualche cosa in più rispetto al punto in cui siamo 091. 'arrivati, perciò mi limiterò a lasciare in bianco 092. Class NonPolynomialEquationSolver 093. Inherits EquationSolver 094. 095. Public Overrides Function Solve() As Single() 096. Return Nothing 097. End Function 098. End Class 099. 100. 'Rappresenta un risolutore di equazioni di primo grado. Eredita 101. 'da PolynomialEquationSolver poichè, ovviamente, si 102. 'tratta di equazioni polinomiali. In più, definisce 103. 'le proprietà a e b che sono utili per inserire i 104. 'coefficienti. Infatti, l'equazione standard è: 105. ' ax + b = 0 106. Class LinearEquationSolver 107. Inherits PolynomialEquationSolver 108. 109. Public Property a() As Single 110. Get 111. Return Me.Coefficients(0) 112. End Get 113. Set(ByVal value As Single) 114. Me.Coefficients(0) = value 115. End Set 116. End Property 117. 118. Public Property b() As Single 119. Get 120. Return Me.Coefficients(1) 121. End Get 122. Set(ByVal value As Single) 123. Me.Coefficients(1) = value 124. End Set 125. End Property 126. 127. 'Sappiamo già quanti sono i coefficienti, dato 128. 'che si tratta di equazioni lineari, quindi ridimensioniamo 129. 'l'array il prima possibile. 130. Sub New() 131. ReDim Me.Coefficients(1) 132. End Sub 133. 134. 'Funzione Overrides che sovrascrive il metodo astratto della 135. 'classe base. Avrete notato che quando scrivete: 136. ' Inherits PolynomialEquationSolver 137. 'e premete invio, questa funzione viene aggiunta automaticamente 138. 'al codice. Questa è un'utile feature dell'ambiente 139. 'di sviluppo 140. Public Overrides Function Solve() As Single() 141. If a <> 0 Then 142. Return New Single() {-b / a} 143.
  • 149. Else 144. Return Nothing 145. End If 146. End Function 147. End Class 148. 149. 'Risolutore di equazioni di secondo grado: 150. ' ax<sup>2</sup> + bx + c = 0 151. Class QuadraticEquationSolver 152. Inherits LinearEquationSolver 153. 154. Public Property c() As Single 155. Get 156. Return Me.Coefficients(2) 157. End Get 158. Set(ByVal value As Single) 159. Me.Coefficients(2) = value 160. End Set 161. End Property 162. 163. Sub New() 164. ReDim Me.Coefficients(2) 165. End Sub 166. 167. Public Overrides Function Solve() As Single() 168. If b ^ 2 - 4 * a * c >= 0 Then 169. Return New Single() { _ 170. (-b - Math.Sqrt(b ^ 2 - 4 * a * c)) / 2, _ 171. (-b + Math.Sqrt(b ^ 2 - 4 * a * c)) / 2} 172. Else 173. Return Nothing 174. End If 175. End Function 176. End Class 177. 178. 'Risolutore di equazioni di grado superiore al secondo. So 179. 'che avrei potuto inserire anche una classe relativa 180. 'alle cubiche, ma dato che si tratta di un esempio, vediamo 181. 'di accorciare il codice... 182. 'Comunque, dato che non esiste formula risolutiva per 183. 'le equazioni di grado superiore al quarto (e già, 184. 'ci mancava un'altra classe!), usiamo in questo caso 185. 'un semplice ed intuitivo metodo di approssimazione degli 186. 'zeri, il metodo dicotomico o di bisezione (che vi può 187. 'essere utile per risolvere un esercizio dell'eserciziario) 188. Class HighDegreeEquationSolver 189. Inherits PolynomialEquationSolver 190. 191. Private _Epsilon As Single 192. Private _IntervalLowerBound, _IntervalUpperBound As Single 193. 194. 'Errore desiderato: l'algoritmo si fermerà una volta 195. 'raggiunta una precisione inferiore a Epsilon 196. Public Property Epsilon() As Single 197. Get 198. Return _Epsilon 199. End Get 200. Set(ByVal value As Single) 201. _Epsilon = value 202. End Set 203. End Property 204. 205. 'Limite inferiore dell'intervallo in cui cercare la soluzione 206. Public Property IntervalLowerBound() As Single 207. Get 208. Return _IntervalLowerBound 209. End Get 210. Set(ByVal value As Single) 211. _IntervalLowerBound = value 212. End Set 213. End Property 214. 215.
  • 150. 'Limite superiore dell'intervallo in cui cercare la soluzione 216. Public Property IntervalUpperBound() As Single 217. Get 218. Return _IntervalUpperBound 219. End Get 220. Set(ByVal value As Single) 221. _IntervalUpperBound = value 222. End Set 223. End Property 224. 225. 226. 'Valuta la funzione polinomiale. Dati i coefficienti immessi, 227. 'noi disponiamo del polinomio p(x), quindi possiamo calcolare 228. 'i valori che esso assume per ogni x 229. Private Function EvaluateFunction(ByVal x As Single) As Single 230. Dim Result As Single = 0 231. 232. For I As Int16 = 0 To Me.Coefficients.Length - 1 233. Result += Me.Coefficients(I) * x ^ (Me.Coefficients.Length - 1 - I) 234. Next 235. 236. Return Result 237. End Function 238. 239. Public Overrides Function Solve() As Single() 240. Dim a, b, c As Single 241. Dim fa, fb, fc As Single 242. Dim Interval As Single = 100 243. Dim I As Int16 = 0 244. Dim Result As Single 245. 246. a = IntervalLowerBound 247. b = IntervalUpperBound 248. 249. 'Non esiste uno zero tra a e b se f(a) e f(b) hanno 250. 'lo stesso segno 251. If EvaluateFunction(a) * EvaluateFunction(b) > 0 Then 252. Return Nothing 253. End If 254. 255. Do 256. 'c è il punto medio tra a e b 257. c = (a + b) / 2 258. 'Calcola f(a), f(b) ed f(c) 259. fa = EvaluateFunction(a) 260. fb = EvaluateFunction(b) 261. fc = EvaluateFunction(c) 262. 263. 'Se uno tra f(a), f(b) e f(c) vale zero, allora abbiamo 264. 'trovato una soluzione perfetta, senza errori, ed 265. 'usciamo direttamente dal ciclo 266. If fa = 0 Then 267. c = a 268. Exit Do 269. End If 270. If fb = 0 Then 271. c = b 272. Exit Do 273. End If 274. If fc = 0 Then 275. Exit Do 276. End If 277. 278. 'Altrimenti, controlliamo quale coppia di valori scelti 279. 'tra f(a), f(b) ed f(c) ha segni discorsi: lo zero si troverà 280. 'tra le ascisse di questi 281. If fa * fc < 0 Then 282. b = c 283. Else 284. a = c 285. End If 286. Loop Until Math.Abs(a - b) < Me.Epsilon 287.
  • 151. 'Cicla finchè l'ampiezza dell'intervallo non è 288. 'sufficientemente piccola, quindi assume come zero più 289. 'probabile il punto medio tra a e b: 290. Result = c 291. 292. Return New Single() {Result} 293. End Function 294. End Class 295. 296. 297. Sub Main() 298. 'Contiene un generico risolutore di equazioni. Non sappiamo ancora 299. 'quale tipologia di equazione dovremo risolvere, ma sappiamo per 300. 'certo che lo dovremo fare, ed EquationSolver è la classe 301. 'base di tutti i risolutori che espone il metodo Solve. 302. Dim Eq As EquationSolver 303. Dim x() As Single 304. Dim Cmd As Char 305. 306. Console.WriteLine("Scegli una tipologia di equazione: ") 307. Console.WriteLine(" l - lineare;") 308. Console.WriteLine(" q - quadratica;") 309. Console.WriteLine(" h - di grado superiore al secondo;") 310. Console.WriteLine(" e - non polinomiale;") 311. Cmd = Console.ReadKey().KeyChar 312. Console.Clear() 313. 314. If Cmd <> "e" Then 315. 'Ancora, sappiamo che si tratta di un'equazione polinomiale 316. 'ma non di quale grado 317. Dim Poly As PolynomialEquationSolver 318. 319. 'Ottiene i dati relativi a ciascuna equazione 320. Select Case Cmd 321. Case "l" 322. Dim Linear As New LinearEquationSolver() 323. Poly = Linear 324. Case "q" 325. Dim Quadratic As New QuadraticEquationSolver() 326. Poly = Quadratic 327. Case "h" 328. Dim High As New HighDegreeEquationSolver() 329. Dim CoefNumber As Int16 330. Console.WriteLine("Inserire il numero di coefficienti: ") 331. CoefNumber = Console.ReadLine 332. ReDim High.Coefficients(CoefNumber - 1) 333. Console.WriteLine("Inserire i limti dell'intervallo in cui cercare gli zeri:") 334. High.IntervalLowerBound = Console.ReadLine 335. High.IntervalUpperBound = Console.ReadLine 336. Console.WriteLine("Inserire la precisione (epsilon):") 337. High.Epsilon = Console.ReadLine 338. Poly = High 339. End Select 340. 341. 'A questo punto la variabile Poly contiene sicuramente un oggetto 342. '(LinearEquationSolver, QuadraticEquationSolver oppure 343. 'HighDegreeEquationSolver), anche se non sappiamo quale. Tuttavia, 344. 'tutti questi sono pur sempre polinomiali e perciò tutti 345. 'hanno bisogno di sapere i coefficienti del polinomio. 346. 'Ecco che allora possiamo usare Poly con sicurezza percè 347. 'sicuramente contiene un oggetto e la proprietà Coefficients 348. 'è stata definita proprio nella classe PolynomialEquationSolver. 349. '<b>N.B.: ricordate tutto quello che abbiamo detto sull'assegnamento 350. ' di un oggetto di classe derivata a uno di classe base!</b> 351. Console.WriteLine("Inserire i coefficienti: ") 352. For I As Int16 = 1 To Poly.Coefficients.Length - 1 353. Console.Write("a{0} = ", Poly.Coefficients.Length - I) 354. Poly.Coefficients(I - 1) = Console.ReadLine 355. Next 356. 357. 'Assegnamo Poly a Eq. Osservate che siamo andati via via dal 358.
  • 152. 'caso più particolare al più generale: 359. ' - Abbiamo creato un oggetto specifico per un certo grado 360. ' di un'equazione polinomiale (Linear, Quadratic, High); 361. ' - Abbiamo messo quell'oggetto in uno che si riferisce 362. ' genericamente a tutti i polinomi; 363. ' - Infine, abbiamo posto quest'ultimo in uno ancora più 364. ' generale che si riferisce a tutte le equazioni; 365. 'Questo percorso porta da oggetto molto specifici e ricchi di membri 366. '(tante proprietà e tanti metodi), a tipi molto generali 367. 'e poveri di membri (nel caso di Eq, un solo metodo). 368. Eq = Poly 369. Else 370. 'Inseriamo in Eq un nuovo oggetto per risolvere equazioni non 371. 'polinomiali, anche se il codice è al momento vuoto 372. Eq = New NonPolynomialEquationSolver 373. Console.WriteLine("Non implementato") 374. End If 375. 376. 'Risolviamo l'equazione. Richiamare la funzione Solve da un oggetto 377. 'EquationSolver potrebbe non dirvi nulla, ma ricordate che dentro Eq 378. 'è memorizzato un oggetto più specifico in cui 379. 'è stata definita la funzione Solve(). Per questo motivo, 380. 'anche se Eq è di tipo classe base, purtuttavia contiene 381. 'al proprio interno un oggetto di tipo classe derivata, ed 382. 'è questo che conta: viene usato il metodo Solve della classe 383. 'derivata. 384. 'Se ci pensate bene, vi verrà più spontaneo capire, 385. 'poiché noi, ora, stiamo guardando ATTRAVERSO il tipo 386. 'EquationSolver un oggetto di altro tipo. È come osservare 387. 'attraverso filtri via via sempre più fitti (cfr 388. 'immagine seguente) 389. x = Eq.Solve() 390. 391. If x IsNot Nothing Then 392. Console.WriteLine("Soluzioni trovate: ") 393. For Each s As Single In x 394. Console.WriteLine(s) 395. Next 396. Else 397. Console.WriteLine("Nessuna soluzione") 398. End If 399. 400. Console.ReadKey() 401. End Sub 402. End Module Eccovi un'immagine dell'ultimo commento:
  • 153. Il piano r osso è l'oggetto che r ealmente c'è in memor ia (ad esempio, Linear EquationSolver ); il piano blu con tr e aper tur e è ciò che r iusciamo a veder e quando l'oggetto viene memor izzato in una classe astr atta PolynomialEquationSolver ; il piano blu iniziale, invece, è ciò a cui possiamo acceder e attr aver so un EquationSolver : il fascio di luce indica le nostr e possibilità di accesso. È pr opr io il caso di dir e che c'è molto di più di ciò che si vede! Classi Sigillate Le classi sigillate sono esattamente l'opposto di quelle astr atte, ossia non possono mai esser e er editate. Si dichiar ano con la keyw or d NotInher itable: 1. NotInheritable Class Example 2. '... 3. End Class Allo stesso modo, penser ete voi, i membr i che non possono subir e over loading sar anno mar cati con qualcosa tipo NotOver r idable... In par te esatto, ma in par te er r ato. La keyw or d NotOver r idable si può applicar e solo e soltanto a metodi già modificati tr amite polimor fismo, ossia Over r ides. 01. Class A 02. Sub DoSomething() 03. '... 04. End Sub 05. End Class 06. 07. Class B 08. Inherits A 09. 10. 'Questa procedura sovrascrive la precedente versione 11. 'di DoSomething dichiarata in A, ma preclude a tutte le 12. 'classi derivate da B la possibilità di fare lo stesso 13. NotOverridable Overrides Sub DoSomething() 14. '... 15. End Sub 16. End Class Inoltr e, le classi sigillate no n possono mai espor r e membr i sigillati, anche per chè tutti i lor o membr i lo sono implicitamente (se una classe non può esser e er editata, ovviamente non si potr anno r idefinir e i membr i con polimor fismo). Classi Parziali Una classe si dice par ziale quando il suo cor po è suddiviso su più files. Si tr atta solamento di un'utilità pr atica che ha poco a che veder e con la pr ogr ammazione ad oggetti. Mi sembr ava, per ò, or dinato espor r e tutte le keyw or d associate alle classi in un solo capitolo. Semplicemente, una classe par ziale si dichiar a in questo modo: 1. Partial Class [Nome] 2. '... 3. End Class È sufficiente dichiar ar e una classe come par ziale per chè il compilator e associ, in fase di assemblaggio, tutte le classi con lo stesso nome in file diver si a quella definizione. Ad esempio: 01. 'Nel file Codice1.vb : 02. Partial Class A 03. Sub One() 04.
  • 154. '... 05. End Sub 06. End Class 07. 08. 'Nel file Codice2.vb 09. Class A 10. Sub Two() 11. '... 12. End Sub 13. End Class 14. 15. 'Nel file Codice3.vb 16. Class A 17. Sub Three() 18. '... 19. End Sub 20. End Class 21. 22. 'Tutte le classi A vengono compilate come un'unica classe 23. 'perchè una possiede la keyword Partial: 24. Class A 25. Sub One() 26. '... 27. End Sub 28. 29. Sub Two() 30. '... 31. End Sub 32. 33. Sub Three() 34. '... 35. End Sub 36. End Class
  • 155. A37. Le Interfacce Sc opo delle Interfac c e Le inter facce sono un'entità davver o singolar e all'inter no del .NET Fr amew or k. La lor o funzione è assimilabile a quella delle classi astr atte, ma il modo con cui esse la svolgono è molto diver so da ciò che abbiamo visto nel capitolo pr ecedente. Il pr incipale scopo di un'inter faccia è definir e lo scheletr o di una classe; potr ebbe esser e scher zosamente assimilata alla r icetta con cui si pr epar a un dolce. Quello che l'inter faccia X fa, ad esempio, consiste nel dir e che per costr uir e una classe Y che r ispetti "la r icetta" descr itta in X ser vono una pr opr ietà Id di tipo Integer , una funzione GetSomething senza par ametr i che r estituisce una str inga e una pr ocedur a DoSomething con un singolo par ametr o Double. Tutte le classi che avr anno intenzione di seguir e i pr ecetti di X (in ger go im plem entar e X) dovr anno definir e, allo stesso modo, quella pr opr ietà di quel tipo e quei metodi con quelle specifiche signatur e (il nome ha impor tanza r elativa). Faccio subito un esempio. Fino ad or a, abbiamo visto essenzialmente due tipi di collezione: gli Ar r ay e gli Ar r ayList. Sia per l'uno che per l'altr o, ho detto che è possibile eseguir e un'iter azione con il costr utto For Each: 01. Dim Ar() As Int32 = {1, 2, 3, 4, 5, 6} 02. Dim Al As New ArrayList 03. 04. For I As Int32 = 1 To 40 05. Al.Add(I) 06. Next 07. 08. 'Stampa i valori di Ar: 09. For Each K As Int32 In Ar 10. Console.WriteLine(K) 11. Next 12. 'Stampa i valori di Al 13. For Each K As Int32 In Al 14. Console.WriteLine(K) 15. Next Ma il sistema come fa a saper e che Ar e Al sono degli insiemi di valor i? Dopotutto, il lor o nome è significativo solo per noi pr ogr ammator i, mentr e per il calcolator e non è altr o che una sequenza di car atter i. Allo stesso modo, il codice di Ar r ay e Ar r ayList, definito dai pr ogr ammator i che hanno scr itto il Fr amew or k, è intelligibile solo agli uomini, per chè al computer non comunica nulla sullo scopo per il quale è stato scr itto. Allor a, siamo al punto di par tenza: nelle classi Ar r ay e Ar r ayList non c'è nulla che possa far "capir e" al pr ogr amma che quelli sono a tutti gli effetti delle collezioni e che, quindi, sono iter abili; e, anche se in qualche str ano modo l'elabor ator e lo potesse capir e, non "sapr ebbe" (in quanto entità non senziente) come far per estr ar r e singoli dati e dar celi uno in fila all'altr o. Ecco che entr ano in scena le inter facce: tutte le classi che r appr esentano un insieme o una collezione di elementi implemen tan o l'inter faccia IEnumer able, la quale, se potesse par lar e, dir ebbe "Guar da che questa classe è una collezione, tr attala di conseguenza!". Questa inter faccia obbliga le classi dalle quali è implementata a definir e alcuni metodi che ser vono per l'enumer azione (Cur r ent, MoveNex t e Reset) e che vedr emo nei pr ossimi capitoli. In conclusione, quindi, il For Each pr ima di tutto contr olla che l'oggetto posto dopo la clausola "In" implementi l'inter faccia IEnumer able. Quindi r ichiama il metodo Reset per por si sul pr imo elemento, poi deposita in K il valor e esposto dalla pr opr ietà Cur r ent, esegue il codice contenuto nel pr opr io cor po e, una volta ar r ivato a Nex t, esegue il metodo MoveNex t per avanzar e al pr ossimo elemento. Il For Each "è sicur o" dell'esistenza di questi membr i per chè l'inter faccia IEnumer able ne impone la definizione. Riassumendo, le inter facce hanno il compito di infor mar e il sistema su quali siano le car atter istiche e i compiti di una classe. Per questo motivo, il lor o nomi ter minano spesso in "-able", come ad esempio IEnumer able, IEquatable, ICompr able, che ci dicono "- è enumer abile", "- è eguagliabile", "- è compar abile", "è ... qualcosa".
  • 156. Dic hiarazione e implementazione La sintassi usata per dichiar ar e un'inter faccia è la seguente: 1. Interface [Nome] 2. 'Membri 3. End Interface I membr i delle inter facce, tuttavia, sono un po' diver si dai membr i di una classe, e nello scr iver li bisogna r ispettar e queste r egole: Nel caso di metodi, pr opr ietà od eventi, il cor po non va specificato; Non si possono mai usar e gli specificator i di accesso; Si possono comunque usar e dei modificator i come Shar ed, ReadOnly e Wr iteOnly. Il pr imo ed il secondo punto sar anno ben compr esi se ci si soffer ma a pensar e che l'inter faccia ha il solo scopo di definir e quali membr i una classe debba implementar e: per questo motivo, non se ne può scr iver e il cor po, dato che spetta espr essamente alle classi implementanti, e non ci si pr eoccupa dello specificator e di accesso, dato che si sta specificando solo il "cosa" e non il "come". Ecco alcuni semplici esempi di dichiar azioni: 01. 'Questa interfaccia dal nome improbabile indica che 02. 'la classe che la implementa rappresenta qualcosa di 03. '"identificabile" e per questo espone una proprietà Integer Id 04. 'e una funzione ToString. Id e ToString, infatti, sono gli 05. 'elementi più utili per identificare qualcosa, prima in 06. 'base a un codice univoco e poi grazie ad una rappresentazione 07. 'comprensibile dall'uomo 08. Interface IIdentifiable 09. ReadOnly Property Id() As Int32 10. Function ToString() As String 11. End Interface 12. 13. 'La prossima interfaccia, invece, indica qualcosa di resettabile 14. 'e obbliga le classi implementanti a esporre il metodo Reset 15. 'e la proprietà DefaultValue, che dovrebbe rappresentare 16. 'il valore di default dell'oggetto. Dato che non sappiamo ora 17. 'quali classi implementeranno questa interfaccia, dobbiamo 18. 'per forza usare un tipo generico come Object per rappresentare 19. 'un valore reference. Vedremo come aggirare questo ostacolo 20. 'fra un po', con i Generics 21. Interface IResettable 22. Property DefaultValue() As Object 23. Sub Reset() 24. End Interface 25. 26. 'Come avete visto, i nomi di interfaccia iniziano per convenzione 27. 'con la lettera I maiuscola Or a che sappiamo come dichiar ar e un'inter faccia, dobbiamo scopr ir e come usar la. Per implementar e un'inter faccia in una classe, si usa questa sintassi: 1. Class Example 2. Implements [Nome Interfaccia] 3. 4. [Membro] Implements [Nome Interfaccia].[Membro] 5. End Class Si capisce meglio con un esempio: 01. Module Module1 02. Interface IIdentifiable 03. ReadOnly Property Id() As Int32 04. Function ToString() As String 05. End Interface 06. 07.
  • 157. 'Rappresenta un pacco da spedire 08. Class Pack 09. 'Implementa l'interfaccia IIdentifiable, in quanto un pacco 10. 'dovrebbe poter essere ben identificato 11. Implements IIdentifiable 12. 13. 'Notate bene che l'interfaccia ci obbliga a definire una 14. 'proprietà, ma non ci obbliga a definire un campo 15. 'ad essa associato 16. Private _Id As Int32 17. Private _Destination As String 18. Private _Dimensions(2) As Single 19. 20. 'La classe definisce una proprietà id di tipo Integer 21. 'e la associa all'omonima presente nell'interfaccia in 22. 'questione. Il legame tra questa proprietà Id e quella 23. 'presenta nell'interfaccia è dato solamente dalla 24. 'clausola (si chiama così in gergo) "Implements", 25. 'la quale avvisa il sistema che il vincolo imposto 26. 'è stato soddisfatto. 27. 'N.B.: il fatto che il nome di questa proprietà sia uguale 28. 'a quella definita in IIdentifiable non significa nulla. 29. 'Avremmo potuto benissimo chiamarla "Pippo" e associarla 30. 'a Id tramite il codice "Implements IIdentifiable.Id", ma 31. 'ovviamente sarebbe stata una palese idiozia XD 32. Public ReadOnly Property Id() As Integer Implements IIdentifiable.Id 33. Get 34. Return _Id 35. End Get 36. End Property 37. 38. 'Destinazione del pacco. 39. 'Il fatto che l'interfaccia ci obblighi a definire quei due 40. 'membri non significa che non possiamo definirne altri 41. Public Property Destination() As String 42. Get 43. Return _Destination 44. End Get 45. Set(ByVal value As String) 46. _Destination = value 47. End Set 48. End Property 49. 50. 'Piccolo ripasso delle proprietà indicizzate e 51. 'della gestione degli errori 52. Public Property Dimensions(ByVal Index As Int32) As Single 53. Get 54. If (Index >= 0) And (Index < 3) Then 55. Return _Dimensions(Index) 56. Else 57. Throw New IndexOutOfRangeException() 58. End If 59. End Get 60. Set(ByVal value As Single) 61. If (Index >= 0) And (Index < 3) Then 62. _Dimensions(Index) = value 63. Else 64. Throw New IndexOutOfRangeException() 65. End If 66. End Set 67. End Property 68. 69. Public Overrides Function ToString() As String Implements IIdentifiable.ToString 70. Return String.Format("{0}: Pacco {1}x{2}x{3}, Destinazione: {4}", _ 71. Me.Id, Me.Dimensions(0), Me.Dimensions(1), _ 72. Me.Dimensions(2), Me.Destination) 73. End Function 74. End Class 75. 76. Sub Main() 77. '... 78. End Sub 79.
  • 158. End Module Or a che abbiamo implementato l'inter faccia nella classe Pack, tuttavia, non sappiamo che far cene. Siamo a conoscenza del fatto che gli oggetti Pack sar anno sicur amente identificabili, ma nulla di più. Ritor niamo, allor a, all'esempio del pr imo par agr afo: cos'è che r ende ver amente utile IEnumer able, al di là del fatto di r ender e funzionante il For Each? Si applica a qualsiasi collezione o insieme, non impor ta di quale natur a o per quali scopi, non impor ta nemmeno il codice che sottende all'enumer azione: l'impor tante è che una vastissima gamma di oggetti possano esser e r icondotti ad un solo ar chetipo (io ne ho nominati solo due, ma ce ne sono a iosa). Allo stesso modo, potr emo usar e IIdentifiable per manipolar e una gr an quantità di dati di natur a differ ente. Ad esempio, il codice di sopr a potr ebbe esser e sviluppato per cr ear e un sistema di gestione di un ufficio postale. Eccone un esempio: 001. Module Module1 002. 003. Interface IIdentifiable 004. ReadOnly Property Id() As Int32 005. Function ToString() As String 006. End Interface 007. 008. Class Pack 009. Implements IIdentifiable 010. 011. Private _Id As Int32 012. Private _Destination As String 013. Private _Dimensions(2) As Single 014. 015. Public ReadOnly Property Id() As Integer Implements IIdentifiable.Id 016. Get 017. Return _Id 018. End Get 019. End Property 020. 021. Public Property Destination() As String 022. Get 023. Return _Destination 024. End Get 025. Set(ByVal value As String) 026. _Destination = value 027. End Set 028. End Property 029. 030. Public Property Dimensions(ByVal Index As Int32) As Single 031. Get 032. If (Index >= 0) And (Index < 3) Then 033. Return _Dimensions(Index) 034. Else 035. Throw New IndexOutOfRangeException() 036. End If 037. End Get 038. Set(ByVal value As Single) 039. If (Index >= 0) And (Index < 3) Then 040. _Dimensions(Index) = value 041. Else 042. Throw New IndexOutOfRangeException() 043. End If 044. End Set 045. End Property 046. 047. Sub New(ByVal Id As Int32) 048. _Id = Id 049. End Sub 050. 051. Public Overrides Function ToString() As String Implements IIdentifiable.ToString 052. Return String.Format("{0:0000}: Pacco {1}x{2}x{3}, Destinazione: {4}", _ 053. Me.Id, Me.Dimensions(0), Me.Dimensions(1), _ 054. Me.Dimensions(2), Me.Destination) 055. End Function 056. End Class 057. 058.
  • 159. Class Telegram 059. Implements IIdentifiable 060. 061. Private _Id As Int32 062. Private _Recipient As String 063. Private _Message As String 064. 065. Public ReadOnly Property Id() As Integer Implements IIdentifiable.Id 066. Get 067. Return _Id 068. End Get 069. End Property 070. 071. Public Property Recipient() As String 072. Get 073. Return _Recipient 074. End Get 075. Set(ByVal value As String) 076. _Recipient = value 077. End Set 078. End Property 079. 080. Public Property Message() As String 081. Get 082. Return _Message 083. End Get 084. Set(ByVal value As String) 085. _Message = value 086. End Set 087. End Property 088. 089. Sub New(ByVal Id As Int32) 090. _Id = Id 091. End Sub 092. 093. Public Overrides Function ToString() As String Implements IIdentifiable.ToString 094. Return String.Format("{0:0000}: Telegramma per {1} ; Messaggio = {2}", _ 095. Me.Id, Me.Recipient, Me.Message) 096. End Function 097. End Class 098. 099. Class MoneyOrder 100. Implements IIdentifiable 101. 102. Private _Id As Int32 103. Private _Recipient As String 104. Private _Money As Single 105. 106. Public ReadOnly Property Id() As Integer Implements IIdentifiable.Id 107. Get 108. Return _Id 109. End Get 110. End Property 111. 112. Public Property Recipient() As String 113. Get 114. Return _Recipient 115. End Get 116. Set(ByVal value As String) 117. _Recipient = value 118. End Set 119. End Property 120. 121. Public Property Money() As Single 122. Get 123. Return _Money 124. End Get 125. Set(ByVal value As Single) 126. _Money = value 127. End Set 128. End Property 129. 130.
  • 160. Sub New(ByVal Id As Int32) 131. _Id = Id 132. End Sub 133. 134. Public Overrides Function ToString() As String Implements IIdentifiable.ToString 135. Return String.Format("{0:0000}: Vaglia postale per {1} ; Ammontare = {2}€", _ 136. Me.Id, Me.Recipient, Me.Money) 137. End Function 138. End Class 139. 140. 'Classe che elabora dati di tipo IIdentifiable, ossia qualsiasi 141. 'oggetto che implementi tale interfaccia 142. Class PostalProcessor 143. 'Tanto per tenersi allenati coi delegate, ecco una 144. 'funzione delegate che funge da filtro per i vari id 145. Public Delegate Function IdSelector(ByVal Id As Int32) As Boolean 146. 147. Private _StorageCapacity As Int32 148. Private _NextId As Int32 = 0 149. 'Un array di interfacce. Quando una variabile viene 150. 'dichiarata come di tipo interfaccia, ciò 151. 'che può contenere è qualsiasi oggetto 152. 'che implementi quell'interfaccia. Per lo stesso 153. 'discorso fatto nel capitolo precedente, noi 154. 'possiamo vedere <i>attraverso</i> l'interfaccia 155. 'solo quei membri che essa espone direttamente, anche 156. 'se il contenuto vero e proprio è qualcosa 157. 'di più 158. Private Storage() As IIdentifiable 159. 160. 'Capacità del magazzino. Assumeremo che tutti 161. 'gli oggetti rappresentati dalle classi Pack, Telegram 162. 'e MoneyOrder vadano in un magazzino immaginario che, 163. 'improbabilmente, riserva un solo posto per ogni 164. 'singolo elemento 165. Public Property StorageCapacity() As Int32 166. Get 167. Return _StorageCapacity 168. End Get 169. Set(ByVal value As Int32) 170. _StorageCapacity = value 171. ReDim Preserve Storage(value) 172. End Set 173. End Property 174. 175. 'Modifica od ottiene un riferimento all'Index-esimo 176. 'oggetto nell'array Storage 177. Public Property Item(ByVal Index As Int32) As IIdentifiable 178. Get 179. If (Index >= 0) And (Index < Storage.Length) Then 180. Return Me.Storage(Index) 181. Else 182. Throw New IndexOutOfRangeException() 183. End If 184. End Get 185. Set(ByVal value As IIdentifiable) 186. If (Index >= 0) And (Index < Storage.Length) Then 187. Me.Storage(Index) = value 188. Else 189. Throw New IndexOutOfRangeException() 190. End If 191. End Set 192. End Property 193. 194. 'Restituisce la prima posizione libera nell'array 195. 'Storage. Anche se in questo esempio non l'abbiamo 196. 'contemplato, gli elementi possono anche essere rimossi 197. 'e quindi lasciare un posto libero nell'array 198. Public ReadOnly Property FirstPlaceAvailable() As Int32 199. Get 200. For I As Int32 = 0 To Me.Storage.Length - 1 201. If Me.Storage(I) Is Nothing Then 202.
  • 161. Return I 203. End If 204. Next 205. Return (-1) 206. End Get 207. End Property 208. 209. 'Tutti gli oggetti che inizializzeremo avranno bisogno 210. 'di un id: ce lo fornisce la stessa classe Processor 211. 'tramite questa proprietà che si autoincrementa 212. Public ReadOnly Property NextId() As Int32 213. Get 214. _NextId += 1 215. Return _NextId 216. End Get 217. End Property 218. 219. 220. 'Due possibili costruttori: uno che accetta un insieme 221. 'già formato di elementi... 222. Public Sub New(ByVal Items() As IIdentifiable) 223. Me.Storage = Items 224. _SorageCapacity = Items.Length 225. End Sub 226. 227. '... e uno che accetta solo la capacità del magazzino 228. Public Sub New(ByVal Capacity As Int32) 229. Me.StorageCapacity = Capacity 230. End Sub 231. 232. 'Stampa a schermo tutti gli elementi che la funzione 233. 'contenuta nel parametro Selector di tipo delegate 234. 'considera validi (ossia tutti quelli per cui 235. 'Selector.Invoke restituisce True) 236. Public Sub PrintByFilter(ByVal Selector As IdSelector) 237. For Each K As IIdentifiable In Storage 238. If K Is Nothing Then 239. Continue For 240. End If 241. If Selector.Invoke(K.Id) Then 242. Console.WriteLine(K.ToString()) 243. End If 244. Next 245. End Sub 246. 247. 'Stampa l'oggetto con Id specificato 248. Public Sub PrintById(ByVal Id As Int32) 249. For Each K As IIdentifiable In Storage 250. If K Is Nothing Then 251. Continue For 252. End If 253. If K.Id = Id Then 254. Console.WriteLine(K.ToString()) 255. Exit For 256. End If 257. Next 258. End Sub 259. 260. 'Cerca tutti gli elementi che contemplano all'interno 261. 'della propria descrizione la stringa Str e li 262. 'restituisce come array di Id 263. Public Function SearchItems(ByVal Str As String) As Int32() 264. Dim Temp As New ArrayList 265. 266. For Each K As IIdentifiable In Storage 267. If K Is Nothing Then 268. Continue For 269. End If 270. If K.ToString().Contains(Str) Then 271. Temp.Add(K.Id) 272. End If 273. Next 274.
  • 162. 275. Dim Result(Temp.Count - 1) As Int32 276. For I As Int32 = 0 To Temp.Count - 1 277. Result(I) = Temp(I) 278. Next 279. 280. Temp.Clear() 281. Temp = Nothing 282. 283. Return Result 284. End Function 285. End Class 286. 287. Private Processor As New PostalProcessor(10) 288. Private Cmd As Char 289. Private IdFrom, IdTo As Int32 290. 291. Function SelectId(ByVal Id As Int32) As Boolean 292. Return (Id >= IdFrom) And (Id <= IdTo) 293. End Function 294. 295. Sub InsertItems(ByVal Place As Int32) 296. Console.WriteLine("Scegliere la tipologia di oggetto:") 297. Console.WriteLine(" p - pacco;") 298. Console.WriteLine(" t - telegramma;") 299. Console.WriteLine(" v - vaglia postale;") 300. Cmd = Console.ReadKey().KeyChar 301. 302. Console.Clear() 303. Select Case Cmd 304. Case "p" 305. Dim P As New Pack(Processor.NextId) 306. Console.WriteLine("Pacco - Id:{0:0000}", P.Id) 307. Console.Write("Destinazione: ") 308. P.Destination = Console.ReadLine 309. Console.Write("Larghezza: ") 310. P.Dimensions(0) = Console.ReadLine 311. Console.Write("Lunghezza: ") 312. P.Dimensions(1) = Console.ReadLine 313. Console.Write("Altezza: ") 314. P.Dimensions(2) = Console.ReadLine 315. Processor.Item(Place) = P 316. Case "t" 317. Dim T As New Telegram(Processor.NextId) 318. Console.WriteLine("Telegramma - Id:{0:0000}", T.Id) 319. Console.Write("Destinatario: ") 320. T.Recipient = Console.ReadLine 321. Console.Write("Messaggio: ") 322. T.Message = Console.ReadLine 323. Processor.Item(Place) = T 324. Case "v" 325. Dim M As New MoneyOrder(Processor.NextId) 326. Console.WriteLine("Vaglia - Id:{0:0000}", M.Id) 327. Console.Write("Beneficiario: ") 328. M.Recipient = Console.ReadLine 329. Console.Write("Somma: ") 330. M.Money = Console.ReadLine 331. Processor.Item(Place) = M 332. Case Else 333. Console.WriteLine("Comando non riconosciuto.") 334. Console.ReadKey() 335. Exit Sub 336. End Select 337. 338. Console.WriteLine("Inserimento eseguito!") 339. Console.ReadKey() 340. End Sub 341. 342. Sub ProcessData() 343. Console.WriteLine("Selezionare l'operazione:") 344. Console.WriteLine(" c - cerca;") 345. Console.WriteLine(" v - visualizza;") 346.
  • 163. Cmd = Console.ReadKey().KeyChar 347. 348. Console.Clear() 349. Select Case Cmd 350. Case "c" 351. Dim Str As String 352. Console.WriteLine("Inserire la parola da cercare:") 353. Str = Console.ReadLine 354. 355. Dim Ids() As Int32 = Processor.SearchItems(Str) 356. Console.WriteLine("Trovati {0} elementi. Visualizzare? (y/n)", Ids.Length) 357. Cmd = Console.ReadKey().KeyChar 358. Console.WriteLine() 359. 360. If Cmd = "y" Then 361. For Each Id As Int32 In Ids 362. Processor.PrintById(Id) 363. Next 364. End If 365. Case "v" 366. Console.WriteLine("Visualizzare gli elementi") 367. Console.Write("Da Id: ") 368. IdFrom = Console.ReadLine 369. Console.Write("A Id: ") 370. IdTo = Console.ReadLine 371. Processor.PrintByFilter(AddressOf SelectId) 372. Case Else 373. Console.WriteLine("Comando sconosciuto.") 374. End Select 375. 376. Console.ReadKey() 377. End Sub 378. 379. Sub Main() 380. Do 381. Console.WriteLine("Gestione ufficio") 382. Console.WriteLine() 383. Console.WriteLine("Selezionare l'operazione da effettuare:") 384. Console.WriteLine(" i - inserimento oggetti;") 385. Console.WriteLine(" m - modifica capacità magazzino;") 386. Console.WriteLine(" p - processa i dati;") 387. Console.WriteLine(" e - esci.") 388. Cmd = Console.ReadKey().KeyChar 389. 390. Console.Clear() 391. Select Case Cmd 392. Case "i" 393. Dim Index As Int32 = Processor.FirstPlaceAvailable 394. 395. Console.WriteLine("Inserimento oggetti in magazzino") 396. Console.WriteLine() 397. 398. If Index > -1 Then 399. InsertItems(Index) 400. Else 401. Console.WriteLine("Non c'è più spazio in magazzino!") 402. Console.ReadKey() 403. End If 404. Case "m" 405. Console.WriteLine("Attuale capacità: " & Processor.StorageCapacity) 406. Console.WriteLine("Inserire una nuova dimensione: ") 407. Processor.StorageCapacity = Console.ReadLine 408. Console.WriteLine("Operazione effettuata.") 409. Console.ReadKey() 410. Case "p" 411. ProcessData() 412. End Select 413. Console.Clear() 414. Loop Until Cmd = "e" 415. End Sub 416. End Module
  • 164. Avevo in mente di definir e anche un'altr a inter faccia, IPayable, per calcolar e anche il costo di spedizione di ogni pezzo: volevo far notar e come, sebbene il costo vada calcolato in manier a diver sa per i tr e tipi di oggetto (in base alle dimensioni per il pacco, in base al numer o di par ole per il telegr amma e in base all'ammontar e inviato per il vaglia), bastasse r ichiamar e una funzione attr aver so l'inter faccia per ottener e il r isultato. Poi ho consider ato che un esempio di 400 r ighe er a già abbastanza. Ad ogni modo, user ò adesso quel'idea in uno spezzone tr atto dal pr ogr amma appena scr itto per mostr ar e l'uso di inter facce multiple: 01. Module Module1 02. '... 03. 04. Interface IPayable 05. Function CalculateSendCost() As Single 06. End Interface 07. 08. Class Telegram 09. 'Nel caso di più interfacce, le si separa con la virgola 10. Implements IIdentifiable, IPayable 11. 12. '... 13. 14. Public Function CalculateSendCost() As Single Implements IPayable.CalculateSendCost 15. 'Come vedremo nel capitolo dedicato alle stringhe, 16. 'la funzione Split(c) spezza la stringa in tante 17. 'parti, divise dal carattere c, e le restituisce 18. 'sottoforma di array. In questo caso, tutte le sottostringhe 19. 'separate da uno spazio sono all'incirca tante 20. 'quanto il numero di parole nella frase 21. Select Case Me.Message.Split(" ").Length 22. Case Is <= 20 23. Return 4.39 24. Case Is <= 50 25. Return 6.7 26. Case Is <= 100 27. Return 10.3 28. Case Is <= 200 29. Return 19.6 30. Case Is <= 500 31. Return 39.75 32. End Select 33. End Function 34. End Class 35. 36. '... 37. End Class Definizione di tipi in un'interfac c ia Così come è possibile dichiar ar e una nuova classe all'inter no di un'altr a, o una str uttur a in una classe, o un'inter faccia in una classe, o una str uttur a in una str uttur a, o tutte le altr e possibili combinazioni, è anche possibile dichiar ar e un nuovo tipo in un'inter faccia. In questo caso, solo le classi che implementer anno quell'inter faccia sar anno in gr ado di usar e quel tipo. Ad esempio: 01. Interface ISaveable 02. Structure FileInfo 03. 'Assumiamo per brevità che queste variabili Public 04. 'siano in realtà proprietà 05. Public Path As String 06. 'FileAttribues è un enumeratore su bit che contiene 07. 'informazioni sugli attributi di un file (nascosto, a sola 08. 'lettura, archivio, compresso, eccetera...) 09. Public Attributes As FileAttributes 10. End Structure 11. Property SaveInfo() As FileInfo 12. Sub Save() 13.
  • 165. End Interface 14. 15. Class A 16. Private _SaveInfo As ISaveable.FileInfo 'SBAGLIATO! 17. '... 18. End Class 19. 20. 21. Class B 22. Implements ISaveable 23. 24. Private _SaveInfo As ISaveable.FileInfo 'GIUSTO 25. 26. '... 27. End Class Ereditarietà, polimorfismo e overloading per le interfac c e Anche le inter facce possono er editar e da un'altr a inter faccia base. In questo caso, dato che in un'inter faccia non si possono usar e specificator i di accesso, la classe der ivata acquisisce tutti i membr i di quella base: 1. Interface A 2. Property PropA() As Int32 3. End Interface 4. 5. Interface B 6. Inherits A 7. Sub SubB() 8. End Interface Non si può usar e il polimor fismo per chè non c'è nulla da r idefinir e, in quanto i metodi non hanno un cor po. Si può, invece, usar e l'over loading come si fa di consueto: non ci sono differ enze significative in questo ambito. Perc hè preferire un'interfac c ia a una c lasse astratta La differ enza sostanziale tr a una classe astr atta e un'inter faccia è che la pr ima definisce l'es s en za di un oggetto (che cosa è), mentr e la seconda ne indica il comportamen to (che cosa fa). Inoltr e una classe astr atta è in gr ado di definir e membr i che ver r anno acquisiti dalla classe der ivata, e quindi dichiar a delle funzionalità di base er editabili da tutti i discendenti; l'inter faccia, al contr ar io, "or dina" a chi la implementa di definir e un cer to membr o con una cer ta funzione, ma non for nisce alcun codice di base, né alcuna dir ettiva su come un dato compito debba esser e svolto. Ecco che, sulla base di queste osser vazioni, possiamo individuar e alcune casistiche in cui sia meglio l'una o l'altr a: Quando esistono compor tamenti comuni : inter facce Quando esistono classi non r iconducibili ad alcun ar chetipo o classe base: inter facce Quando tutte le classi hanno fondamentalmente la stessa essenza : classe astr atta Quando tutte le classi, assimilabili ad un unico ar chetipo, hanno bisogno di implementar e la stessa funzionalità o gli stessi membr i : classe astr atta
  • 166. A38. Utilizzo delle Interfacce - Parte I L'aspetto più inter essante e sicur amente più utile delle inter facce è che il lor o utilizzo è fondamentale per l'uso di alcuni costr utti par ticolar i, quali il For Each e l'Using, e per molte altr e funzioni e pr ocedur e che inter vengono nella gestione delle collezioni. Impar ar e a manipolar e con facilità questo str umento per metter à di scr iver e non solo meno codice, più efficace e r iusabile, ma anche di impostar e l'applicazione in una manier a solida e r obusta. IComparable e IComparer Un oggetto che implementa ICompar able comunica implicitamente al .NET Fr amew or k che può esser e confr ontato con altr i oggetti, stabilendo se uno di essi è maggior e, minor e o uguale all'altr o e abilitando in questo modo l'or dinamento automatico attr aver so il metodo Sor t di una collection. Infatti, tale metodo confr onta uno ad uno ogni elemento di una collezione o di un ar r ay e tr amite la funzione Compar eTo che ogni inter faccia ICompar able espone e li or dina in or dine cr escente o decr escente. Compar eTo è una funzione di istanza che implementa ICompar able.Compar eTo e ha dei r isultati pr edefiniti: r estituisce 1 se l'oggetto passato come par ametr o è minor e dell'oggetto dalla quale viene r ichiamata, 0 se è uguale e -1 se è maggior e. Ad esempio, questo semplice pr ogr amma illustr a il funzionamento di Compar eTo e Sor t: 01. Module Module1 02. Sub Main() 03. Dim A As Int32 04. 05. Console.WriteLine("Inserisci un numero intero:") 06. A = Console.ReadLine 07. 08. 'Tutti i tipi di base espongono il metodo CompareTo, poichè 09. 'tutti implementano l'interfaccia IComparable: 10. If A.CompareTo(10) = 1 Then 11. Console.WriteLine(A & " è maggiore di 10") 12. ElseIf A.CompareTo(10) = 0 Then 13. Console.WriteLine(A & " è uguale a 10") 14. Else 15. Console.WriteLine(A & " è minore di 10") 16. End If 17. 18. 'Il fatto che i tipi di base siano confrontabili implica 19. 'che si possano ordinare tramite il metodo Sort di una 20. 'qualsiasi collezione o array di elementi 21. Dim B() As Int32 = {1, 5, 2, 8, 10, 56} 22. 'Ordina l'array 23. Array.Sort(B) 24. 'E visualizza i numeri in ordine crescente 25. For I As Int16 = 0 To UBound(B) 26. Console.WriteLine(B(I)) 27. Next 28. 29. 'Anche String espone questo metodo, quindi si può ordinare 30. 'alfabeticamente un insieme di stringhe: 31. Dim C As New ArrayList 32. C.Add("Banana") 33. C.Add("Zanzara") 34. C.Add("Anello") 35. C.Add("Computer") 36. 'Ordina l'insieme 37. C.Sort() 38. For I As Int16 = 0 To C.Count - 1 39. Console.WriteLine(C(I)) 40. Next 41. 42. Console.ReadKey() 43.
  • 167. End Sub 44. End Module Dopo aver immesso un input, ad esempio 8, avr emo la seguente scher mata: Inserire un numero intero: 8 8 è minore di 10 1 2 5 8 10 56 Anello Banana Computer Zanzara Come si osser va, tutti gli elementi sono stati or dinati cor r ettamente. Or a che abbiamo visto la potenza di ICompar able, vediamo di capir e come implementar la. L'esempio che pr ender ò come r ifer imento or a pone una semplice classe Per son, di cui si è già par lato addietr o, e or dina un Ar r ayList di questi oggetti pr endendo come r ifer imento il nome completo: 01. Module Module1 02. Class Person 03. Implements IComparable 04. Private _FirstName, _LastName As String 05. Private ReadOnly _BirthDay As Date 06. 07. Public Property FirstName() As String 08. Get 09. Return _FirstName 10. End Get 11. Set(ByVal Value As String) 12. If Value <> "" Then 13. _FirstName = Value 14. End If 15. End Set 16. End Property 17. 18. Public Property LastName() As String 19. Get 20. Return _LastName 21. End Get 22. Set(ByVal Value As String) 23. If Value <> "" Then 24. _LastName = Value 25. End If 26. End Set 27. End Property 28. 29. Public ReadOnly Property BirthDay() As Date 30. Get 31. Return _BirthDay 32. End Get 33. End Property 34. 35. Public ReadOnly Property CompleteName() As String 36. Get 37. Return _FirstName & " " & _LastName 38. End Get 39. End Property 40. 41. 'Per definizione, purtroppo, CompareTo deve sempre usare 42. 'un parametro di tipo Object: risolveremo questo problema 43.
  • 168. 'più in là utilizzando i Generics 44. Public Function CompareTo(ByVal obj As Object) As Integer _ 45. Implements IComparable.CompareTo 46. 'Un oggetto non-nothing (questo) è sempre maggiore di 47. 'un oggetto Nothing (ossia obj) 48. If obj Is Nothing Then 49. Return 1 50. End If 51. 'Tenta di convertire obj in Person 52. Dim P As Person = DirectCast(obj, Person) 53. 'E restituisce il risultato dell'operazione di 54. 'comparazione tra stringhe dei rispettivi nomi 55. Return String.Compare(Me.CompleteName, P.CompleteName) 56. End Function 57. 58. Sub New(ByVal FirstName As String, ByVal LastName As String, _ 59. ByVal BirthDay As Date) 60. Me.FirstName = FirstName 61. Me.LastName = LastName 62. Me._BirthDay = BirthDay 63. End Sub 64. End Class 65. 66. Sub Main() 67. 'Crea un array di oggetti Person 68. Dim Persons() As Person = _ 69. {New Person("Marcello", "Rossi", Date.Parse("10/10/1992")), _ 70. New Person("Guido", "Bianchi", Date.Parse("01/12/1980")), _ 71. New Person("Bianca", "Brega", Date.Parse("23/06/1960")), _ 72. New Person("Antonio", "Felice", Date.Parse("16/01/1930"))} 73. 74. 'E li ordina, avvalendosi di IComparable.CompareTo 75. Array.Sort(Persons) 76. 77. For I As Int16 = 0 To UBound(Persons) 78. Console.WriteLine(Persons(I).CompleteName) 79. Next 80. 81. Console.ReadKey() 82. End Sub 83. End Module Dato che il nome viene pr ima del congnome, la lista sar à: Antonio, Bianca, Guido, Mar cello. E se si volesse or dinar e la lista di per sone in base alla data di nascita? Non è possibile definir e due ver sioni di Compar eTo, poichè devono aver e la stessa signatur e, e cr ear e due metodi che or dinino l'ar r ay sar ebbe scomodo: è qui che entr a in gioco l'inter faccia ICompar er . Essa r appr esenta un oggetto che deve eseguir e la compar azione tr a due altr i oggetti, facendo quindi da tramite nell'or dinamento. Dato che Sor t accetta in una delle sue ver sioni un oggetto ICompar er , è possibile or dinar e una lista di elementi con qualsiasi cr iter io si voglia semplicemente cambiando il par ametr o. Ad esempio, in questo sor gente scr ivo una classe Bir thDayCompar er che per mette di or dinar e oggetti Per son in base all'anno di nascita: 01. Module Module2 02. 'Questa classe fornisce un metodo per comparare oggetti Person 03. 'utilizzando la proprietà BirthDay. 04. 'Per convenzione, classi che implementano IComparer dovrebbero 05. 'avere un suffisso "Comparer" nel nome. 06. 'Altra osservazione: se ci sono molte interfacce il cui nome 07. 'termina in "-able", definendo una caratteristica dell'oggetto 08. 'che le implementa (ad es.: un oggetto enumerabile, 09. 'comparabile, distruggibile, ecc...), ce ne sono altrettante 10. 'che terminano in "-er", indicando, invece, un oggetto 11. 'che "fa" qualcosa di specifico. 12. 'Nel nostro esempio, oggetti di tipo BirthDayComparer 13. 'hanno il solo scopo di comparare altre oggetti 14. Class BirthDayComparer 15. 'Implementa l'interfaccia 16. Implements IComparer 17. 18.
  • 169. 'Anche questa funzione deve usare parametri object 19. Public Function Compare(ByVal x As Object, ByVal y As Object) _ 20. As Integer Implements System.Collections.IComparer.Compare 21. 'Se entrambi gli oggetti sono Nothing, allora sono 22. 'uguali 23. If x Is Nothing And y Is Nothing Then 24. Return 0 25. ElseIf x Is Nothing Then 26. 'Se x è Nothing, y è maggiore 27. Return -1 28. ElseIf y Is Nothing Then 29. 'Se y è Nothing, x è maggiore 30. Return 1 31. Else 32. Dim P1 As Person = DirectCast(x, Person) 33. Dim P2 As Person = DirectCast(y, Person) 34. 'Compara le date 35. Return Date.Compare(P1.BirthDay, P2.BirthDay) 36. End If 37. End Function 38. End Class 39. 40. Sub Main() 41. Dim Persons() As Person = _ 42. {New Person("Marcello", "Rossi", Date.Parse("10/10/1992")), _ 43. New Person("Guido", "Bianchi", Date.Parse("01/12/1980")), _ 44. New Person("Bianca", "Brega", Date.Parse("23/06/1960")), _ 45. New Person("Antonio", "Felice", Date.Parse("16/01/1930"))} 46. 47. 'Ordina gli elementi utilizzando il nuovo oggetto 48. 'inizializato in linea BirthDayComparer 49. Array.Sort(Persons, New BirthDayComparer()) 50. 51. For I As Int16 = 0 To UBound(Persons) 52. Console.WriteLine(Persons(I).CompleteName) 53. Next 54. 55. Console.ReadKey() 56. End Sub 57. End Module Usando questo meccanismo è possibile or dinar e qualsiasi tipo di lista o collezione fin'or a analizzata (tr anne Sor tedList, che si or dina automaticamente), in modo semplice e veloce, par ticolar mente utile nell'ambito delle liste visuali a colonne, come vedr emo nei capitoli sulle ListView . IDisposable Nel capitolo sui distr uttor i si è visto come sia possibile utilizzar e il costr utto Using per gestir e un oggetto e poi distr ugger lo in poche r ighe di codice. Ogni classe che espone il metodo Dispose deve obbligator iamente implementar e anche l'inter faccia IDisposable, la quale comunica implicitamente che essa ha questa car atter istica. Dato che già molti esempi sono stati fatti sull'ar gomento distr uttor i, eviter ò di tr attar e nuovamente Dispose in questo capitolo.
  • 170. A39. Utilizzo delle Interfacce - Parte II IEnumerable e IEnumerator Una classe che implementa IEnumer able diventa enum er abile agli occhi del .NET Fr amew or k: ciò significa che si può usar e su di essa un costr utto For Each per scor r er ne tutti gli elementi. Di solito questo tipo di classe r appr esenta una collezione di elementi e per questo motivo il suo nome, secondo le convenzioni, dovr ebbe ter minar e in "Collection". Un motivo per costr uir e una nuova collezione al posto di usar e le classiche liste può consister e nel voler definir e nuovi metodi o pr opr ietà per modificar la. Ad esempio, si potr ebbe scr iver e una nuova classe Per sonCollection che per mette di r aggr uppar e ed enumer ar e le per sone ivi contenute e magar i calcolar e anche l'età media. 01. Module Module1 02. Class PersonCollection 03. Implements IEnumerable 04. 'La lista delle persone 05. Private _Persons As New ArrayList 06. 07. 'Teoricamente, si dovrebbero ridefinire tutti i metodi 08. 'di una collection comune, ma per mancanza di spazio, 09. 'accontentiamoci 10. Public ReadOnly Property Persons() As ArrayList 11. Get 12. Return _Persons 13. End Get 14. End Property 15. 16. 'Restituisce l'età media. TimeSpan è una struttura che si 17. 'ottiene sottraendo fra loro due oggetti date e indica un 18. 'intervallo di tempo 19. Public ReadOnly Property AverageAge() As String 20. Get 21. 'Variabile temporanea 22. Dim Temp As TimeSpan 23. 'Somma tutte le età 24. For Each P As Person In _Persons 25. Temp = Temp.Add(Date.Now - P.BirthDay) 26. Next 27. 'Divide per il numero di persone 28. Temp = TimeSpan.FromSeconds(Temp.TotalSeconds / _Persons.Count) 29. 30. 'Dato che TimeSpan può contenere al massimo 31. 'giorni e non mesi o anni, dobbiamo fare qualche 32. 'calcolo 33. Dim Years As Int32 34. 'Gli anni, ossia il numero dei giorni fratto 365 35. 'Divisione intera 36. Years = Temp.TotalDays 365 37. 'Sottrae gli anni: da notare che 38. '(Temp.TotalDays 365) * 365) non è un passaggio 39. 'inutile. Infatti, per determinare il numero di 40. 'giorni che rimangono, bisogna prendere la 41. 'differenza tra il numero totale di giorni e 42. 'il multiplo più vicino di 365 43. Temp = _ 44. Temp.Subtract(TimeSpan.FromDays((Temp.TotalDays 365) * 365)) 45. 46. Return Years & " anni e " & CInt(Temp.TotalDays) & " giorni" 47. End Get 48. End Property 49. 50. 'La funzione GetEnumerator restituisce un oggetto di tipo 51. 'IEnumerator che vedremo fra breve: esso permette di 52. 'scorrere ogni elemento ordinatamente, dall'inizio 53. 'alla fine. In questo caso, poichè non abbiamo ancora 54. 'analizzato questa interfaccia, ci limitiamo a restituisce 55.
  • 171. 'l'IEnumerator predefinito per un ArrayList 56. Public Function GetEnumerator() As IEnumerator _ 57. Implements IEnumerable.GetEnumerator 58. Return _Persons.GetEnumerator 59. End Function 60. End Class 61. 62. Sub Main() 63. Dim Persons As New PersonCollection 64. With Persons.Persons 65. .Add(New Person("Marcello", "Rossi", Date.Parse("10/10/1992"))) 66. .Add(New Person("Guido", "Bianchi", Date.Parse("01/12/1980"))) 67. .Add(New Person("Bianca", "Brega", Date.Parse("23/06/1960"))) 68. .Add(New Person("Antonio", "Felice", Date.Parse("16/01/1930"))) 69. End With 70. 71. For Each P As Person In Persons 72. Console.WriteLine(P.CompleteName) 73. Next 74. Console.WriteLine("Età media: " & Persons.AverageAge) 75. '> 41 anni e 253 giorni 76. 77. Console.ReadKey() 78. End Sub 79. End Module Come si vede dall'esempio, è lecito usar e Per sonCollection nel costr utto For Each: l'iter azione viene svolta dal pr imo elemento inser ito all'ultimo, poichè l'IEnumer ator dell'Ar r ayList oper a in questo modo. Tuttavia, cr eando una diver sa classe che implementa IEnumer ator si può scor r er e la collezione in qualsiasi modo: dal più giovane al più vecchio, al pr imo all'ultimo, dall'ultimo al pr imo, a caso, saltandone alcuni, a seconda dell'or a di cr eazione ecceter a. Quindi in questo modo si può per sonalizzar e la pr opr ia collezione. Ciò che occor r e per costr uir e cor r ettamente una classe basata su IEnumer ator sono tr e metodi fondamentali definiti nell'inter faccia: MoveNex t è una funzione che r estituisce Tr ue se esiste un elemento successivo nella collezione (e in questo caso lo imposta come elemento cor r ente), altr imenti False; Cur r ent è una pr opr ietà ReadOnly di tipo Object che r estituisce l'elemento cor r ente; Reset è una pr ocedur a senza par ametr i che r esetta il contator e e fa iniziar e il ciclo daccapo. Quest'ultimo metodo non viene mai utilizzato, ma nell'esempio che segue ne scr iver ò comunque il cor po: 001. Module Module1 002. Class PersonCollection 003. Implements IEnumerable 004. 'La lista delle persone 005. Private _Persons As New ArrayList 006. 007. 'Questa classe ha il compito di scorrere ordinatamente gli 008. 'elementi della lista, dal più vecchio al più giovane 009. Private Class PersonAgeEnumerator 010. Implements IEnumerator 011. 012. 'Per enumerare gli elementi, la classe ha bisogno di un 013. 'riferimento ad essi: perciò si deve dichiarare ancora 014. 'un nuovo ArrayList di Person. Questo passaggio è 015. 'facoltativo nelle classi nidificate come questa, ma è 016. 'obbligatorio in tutti gli altri casi 017. Private Persons As New ArrayList 018. 'Per scorrere la collezione, si userà un comune indice 019. Private Index As Int32 020. 021. 'Essendo una normalissima classe, è lecito definire un 022. 'costruttore, che in questo caso inizializza la 023. 'collezione 024. Sub New(ByVal Persons As ArrayList) 025. 'Ricordate: poichè ArrayList deriva da Object, è 026. 'un tipo reference. Assegnare Persons a Me.Persons 027. 'equivale ad assegnarne l'indirizzo e quindi ogni 028. 'modifica su questo arraylist privato si rifletterà 029. 'su quello passato come parametro. Si può 030. 'evitare questo problema clonando la lista 031.
  • 172. Me.Persons = Persons.Clone 032. 'MoveNext viene richiamato prima di usare Current, 033. 'quindi Index verrà incrementata subito. 034. 'Per farla diventare 0 al primo ciclo la si 035. 'deve impostare a -1 036. Index = -1 037. 038. 'Dato che l'enumeratore deve scorrere la lista 039. 'secondo l'anno di nascita, bisogna prima ordinarla 040. Me.Persons.Sort(New BirthDayComparer) 041. End Sub 042. 043. 'Restituisce l'elemento corrente 044. Public ReadOnly Property Current() As Object _ 045. Implements System.Collections.IEnumerator.Current 046. Get 047. Return Persons(Index) 048. End Get 049. End Property 050. 051. 'Restituisce True se esiste l'elemento successivo e lo 052. 'imposta, altrimenti False 053. Public Function MoveNext() As Boolean _ 054. Implements System.Collections.IEnumerator.MoveNext 055. If Index = Persons.Count - 1 Then 056. Return False 057. Else 058. Index += 1 059. Return True 060. End If 061. End Function 062. 063. 'Resetta il ciclo 064. Public Sub Reset() _ 065. Implements System.Collections.IEnumerator.Reset 066. Index = -1 067. End Sub 068. End Class 069. 070. Public ReadOnly Property Persons() As ArrayList 071. Get 072. Return _Persons 073. End Get 074. End Property 075. 076. Public ReadOnly Property AverageAge() As String 077. Get 078. Dim Temp As TimeSpan 079. For Each P As Person In _Persons 080. Temp = Temp.Add(Date.Now - P.BirthDay) 081. Next 082. Temp = TimeSpan.FromSeconds(Temp.TotalSeconds / _Persons.Count) 083. 084. Dim Years As Int32 085. Years = Temp.TotalDays 365 086. Temp = _ 087. Temp.Subtract(TimeSpan.FromDays((Temp.TotalDays 365) * 365)) 088. Return Years & " anni e " & CInt(Temp.TotalDays) & " giorni" 089. End Get 090. End Property 091. 092. 'La funzione GetEnumerator restituisce ora un oggetto di 093. 'tipo IEnumerator che abbiamo definito in una classe 094. 'nidificata e il ciclo For Each scorrerà quindi 095. 'dal più vecchio al più giovane 096. Public Function GetEnumerator() As IEnumerator _ 097. Implements IEnumerable.GetEnumerator 098. Return New PersonAgeEnumerator(_Persons) 099. End Function 100. End Class 101. 102. Sub Main() 103.
  • 173. Dim Persons As New PersonCollection 104. With Persons.Persons 105. .Add(New Person("Marcello", "Rossi", Date.Parse("10/10/1992"))) 106. .Add(New Person("Guido", "Bianchi", Date.Parse("01/12/1980"))) 107. .Add(New Person("Bianca", "Brega", Date.Parse("23/06/1960"))) 108. .Add(New Person("Antonio", "Felice", Date.Parse("16/01/1930"))) 109. End With 110. 111. 'Enumera ora per data di nascita, ma senza modificare 112. 'l'ordine degli elementi 113. For Each P As Person In Persons 114. Console.WriteLine(P.BirthDay.ToShortDateString & ", " & _ 115. P.CompleteName) 116. Next 117. 118. 'Stampa la prima persona, dimostrando che l'ordine 119. 'della lista è intatto 120. Console.WriteLine(Persons.Persons(0).CompleteName) 121. 122. Console.ReadKey() 123. End Sub 124. End Module ICloneable Come si è visto nell'esempio appena scr itto, si pr esentano alcune difficoltà nel manipolar e oggetti di tipo Refer ence, in quanto l'assegnazione di questi cr eer ebbe due istanze che puntano allo stesso oggetto piuttosto che due oggetti distinti. È in questo tipo di casi che il metodo Clone e l'inter faccia ICloneable assumono un gr an valor e. Il pr imo per mette di eseguir e una copia dell'oggetto, cr eando un nuo v o og g etto a tutti gli effetti, totalmente disgiunto da quello di par tenza: questo per mette di non intaccar ne accidentalmente l'integr ità. Una dimostr azione: 01. Module Esempio 02. Sub Main() 03. 'Il tipo ArrayList espone il metodo Clone 04. Dim S1 As New ArrayList 05. Dim S2 As New ArrayList 06. 07. S2 = S1 08. 09. 'Verifica che S1 e S2 puntano lo stesso oggetto 10. Console.WriteLine(S1 Is S2) 11. '> True 12. 13. 'Clona l'oggetto 14. S2 = S1.Clone 15. 'Verifica che ora S2 referenzia un oggetto differente, 16. 'ma di valore identico a S1 17. Console.WriteLine(S1 Is S2) 18. '> False 19. 20. Console.ReadKey() 21. End Sub 22. End Module L'inter faccia, invece, come accadeva per IEnumer able e ICompar able, indica al .NET Fr amew or k che l'oggetto è clonabile. Questo codice mostr a la funzione Close all'oper a: 01. Module Module1 02. Class UnOggetto 03. Implements ICloneable 04. Private _Campo As Int32 05. 06. Public Property Campo() As Int32 07. Get 08. Return _Campo 09. End Get 10.
  • 174. Set(ByVal Value As Int32) 11. _Campo = Value 12. End Set 13. End Property 14. 15. 'Restituisce una copia dell'oggetto 16. Public Function Clone() As Object Implements ICloneable.Clone 17. 'La funzione Protected MemberwiseClone, ereditata da 18. 'Object, esegue una copia superficiale dell'oggetto, 19. 'come spiegherò fra poco: è quello che 20. 'serve in questo caso 21. Return Me.MemberwiseClone 22. End Function 23. 24. 'L'operatore = permette di definire de due oggetti hanno un 25. 'valore uguale 26. Shared Operator =(ByVal O1 As UnOggetto, ByVal O2 As UnOggetto) As _ 27. Boolean 28. Return O1.Campo = O2.Campo 29. End Operator 30. 31. Shared Operator <>(ByVal O1 As UnOggetto, ByVal O2 As UnOggetto) As _ 32. Boolean 33. Return Not (O1 = O2) 34. End Operator 35. End Class 36. 37. Sub Main() 38. Dim O1 As New UnOggetto 39. Dim O2 As UnOggetto = O1.Clone 40. 41. 'I due oggetti NON sono lo stesso oggetto: il secondo 42. 'è solo una copia, disgiunta da O1 43. Console.WriteLine(O1 Is O2) 44. '> False 45. 46. 'Tuttavia hanno lo stesso identico valore 47. Console.WriteLine(O1 = O2) 48. '> True 49. 50. Console.ReadKey() 51. End Sub 52. End Module Or a, è impor tante distinguer e due tipi di copia: quella Shallo w e quella Deep. La pr ima cr ea una copia super ficiale dell'oggetto, ossia si limita a clonar e tutti i campi. La seconda, invece, è in gr ado di eseguir e questa oper azione anche su tutti gli oggetti inter ni e i r ifer imenti ad altr i oggetti: così, se si ha una classe Per son che al pr opr io inter no contiene il campo Childer n, di tipo ar r ay di Per son, la copia Shallow cr eer à un clone della classe in cui Childr en punta sempr e allo stesso oggetto, mentr e una copia Deep cloner à anche Childr en. Si nota meglio con un gr afico: le fr ecce ver di indicano oggetti clonati, mentr e la fr eccia ar ancio si r ifer isce allo stesso oggetto.
  • 175. Non è possibile specificar e nella dichiar azione di Clone quale tipo di copia ver r à eseguita, quindi tutto viene lasciato all'ar bitr io del pr ogr ammator e. Dal codice sopr a scr itto, si nota che Clone deve r estituir e per for za un tipo Object. In questo caso, il metodo si dice a tipizzazio ne debo le, ossia ser ve un oper ator e di cast per conver tir lo nel tipo desider ato; per cr ear ne una ver sione a tipizzazio ne fo r te è necessar io scr iver e una funzione che r estituisca, ad esempio, un tipo Per son. Quest'ultima ver sione avr à il nome Clone, mentr e quella che implementa ICloneable.Clone() avr à un nome differ ente, come CloneMe().
  • 176. A40. Le librerie di classi Cer te volte accade che non si voglia scr iver e un pr ogr amma, ma piuttosto un insieme di utilità per gestir e un cer to tipo di infor mazioni. In questi casi, si scr ive una libr er ia di classi, ossia un insieme, appunto, di namespace, classi e tipi che ser vono ad un deter minato scopo. Potete tr ovar e un esempio tr a i sor genti della sezione Dow nload: mi r ifer isco a Mp3 Deep Analyzer , una libr er ia di classi che for nisce str umenti per legger e e scr iver e tag ID3 nei file mp3 (per ulter ior i infor mazioni sull'ar gomento, consultar e la sezione FFS). Con quel pr ogetto non ho voluto scr iver e un pr ogr amma che svolgesse quei compiti, per chè da solo sar ebe stato poco utile, ma piuttosto metter e a disposizione anche agli altr i pr ogr ammator i un modo semplice per manipolar e quel tipo di infor mazioni. Così facendo, uno potr ebbe usar e le funzioni di quella libr er ia in un pr opr io pr ogr amma. Le libr er ie, quindi, sono un inventar io di classi scr itto appositamente per esser e r iusato. Creare una nuova libreria di c lassi Per cr ear e una libr er ia, cliccate su File > New Pr oject e, invece si selezionar e la solita "Console Application", selezionate "Class Libr ar y". Una volta inizializzato il pr ogetto, vi tr over ete di fr onte a un codice pr eimpostato diver so dal solito: 1. Class Class1 2. 3. End Class Noter ete, inoltr e, che, pr emendo F5, vi ver r à comunicato un er r or e: non stiamo scr ivendo un pr ogr amma, infatti, ma solo una libr er ia, che quindi non può esser e "eseguita" (non avr ebbe senso neanche pensar e di far lo). Per far e un esempio, significativo, r ipr endiamo il codice di esempio del capitolo sulle inter facce e scor por iamolo dal pr ogr amma, estr aendone solo le classi: 001. Namespace PostalManagement 002. 003. Public Interface IIdentifiable 004. ReadOnly Property Id() As Int32 005. Function ToString() As String 006. End Interface 007. 008. Public Class Pack 009. Implements IIdentifiable 010. 011. Private _Id As Int32 012. Private _Destination As String 013. Private _Dimensions(2) As Single 014. 015. Public ReadOnly Property Id() As Integer Implements IIdentifiable.Id 016. Get 017. Return _Id 018. End Get 019. End Property 020. 021. Public Property Destination() As String 022. Get 023. Return _Destination 024. End Get 025. Set(ByVal value As String) 026. _Destination = value 027. End Set 028. End Property 029. 030. Public Property Dimensions(ByVal Index As Int32) As Single 031. Get 032.
  • 177. If (Index >= 0) And (Index < 3) Then 033. Return _Dimensions(Index) 034. Else 035. Throw New IndexOutOfRangeException() 036. End If 037. End Get 038. Set(ByVal value As Single) 039. If (Index >= 0) And (Index < 3) Then 040. _Dimensions(Index) = value 041. Else 042. Throw New IndexOutOfRangeException() 043. End If 044. End Set 045. End Property 046. 047. Public Sub New(ByVal Id As Int32) 048. _Id = Id 049. End Sub 050. 051. Public Overrides Function ToString() As String Implements IIdentifiable.ToString 052. Return String.Format("{0:0000}: Pacco {1}x{2}x{3}, Destinazione: {4}", _ 053. Me.Id, Me.Dimensions(0), Me.Dimensions(1), _ 054. Me.Dimensions(2), Me.Destination) 055. End Function 056. End Class 057. 058. Public Class Telegram 059. Implements IIdentifiable 060. 061. Private _Id As Int32 062. Private _Recipient As String 063. Private _Message As String 064. 065. Public ReadOnly Property Id() As Integer Implements IIdentifiable.Id 066. Get 067. Return _Id 068. End Get 069. End Property 070. 071. Public Property Recipient() As String 072. Get 073. Return _Recipient 074. End Get 075. Set(ByVal value As String) 076. _Recipient = value 077. End Set 078. End Property 079. 080. Public Property Message() As String 081. Get 082. Return _Message 083. End Get 084. Set(ByVal value As String) 085. _Message = value 086. End Set 087. End Property 088. 089. Public Sub New(ByVal Id As Int32) 090. _Id = Id 091. End Sub 092. 093. Public Overrides Function ToString() As String Implements IIdentifiable.ToString 094. Return String.Format("{0:0000}: Telegramma per {1} ; Messaggio = {2}", _ 095. Me.Id, Me.Recipient, Me.Message) 096. End Function 097. End Class 098. 099. Public Class MoneyOrder 100. Implements IIdentifiable 101. 102. Private _Id As Int32 103. Private _Recipient As String 104.
  • 178. Private _Money As Single 105. 106. Public ReadOnly Property Id() As Integer Implements IIdentifiable.Id 107. Get 108. Return _Id 109. End Get 110. End Property 111. 112. Public Property Recipient() As String 113. Get 114. Return _Recipient 115. End Get 116. Set(ByVal value As String) 117. _Recipient = value 118. End Set 119. End Property 120. 121. Public Property Money() As Single 122. Get 123. Return _Money 124. End Get 125. Set(ByVal value As Single) 126. _Money = value 127. End Set 128. End Property 129. 130. Public Sub New(ByVal Id As Int32) 131. _Id = Id 132. End Sub 133. 134. Public Overrides Function ToString() As String Implements IIdentifiable.ToString 135. Return String.Format("{0:0000}: Vaglia postale per {1} ; Ammontare = {2}€", _ 136. Me.Id, Me.Recipient, Me.Money) 137. End Function 138. End Class 139. 140. Public Class PostalProcessor 141. Public Delegate Function IdSelector(ByVal Id As Int32) As Boolean 142. 143. Private _StorageCapacity As Int32 144. Private _NextId As Int32 = 0 145. Private Storage() As IIdentifiable 146. 147. Public Property StorageCapacity() As Int32 148. Get 149. Return _StorageCapacity 150. End Get 151. Set(ByVal value As Int32) 152. _StorageCapacity = value 153. ReDim Preserve Storage(value) 154. End Set 155. End Property 156. 157. Public Property Item(ByVal Index As Int32) As IIdentifiable 158. Get 159. If (Index >= 0) And (Index < Storage.Length) Then 160. Return Me.Storage(Index) 161. Else 162. Throw New IndexOutOfRangeException() 163. End If 164. End Get 165. Set(ByVal value As IIdentifiable) 166. If (Index >= 0) And (Index < Storage.Length) Then 167. Me.Storage(Index) = value 168. Else 169. Throw New IndexOutOfRangeException() 170. End If 171. End Set 172. End Property 173. 174. Public ReadOnly Property FirstPlaceAvailable() As Int32 175. Get 176.
  • 179. For I As Int32 = 0 To Me.Storage.Length - 1 177. If Me.Storage(I) Is Nothing Then 178. Return I 179. End If 180. Next 181. Return (-1) 182. End Get 183. End Property 184. 185. Public ReadOnly Property NextId() As Int32 186. Get 187. _NextId += 1 188. Return _NextId 189. End Get 190. End Property 191. 192. 193. Public Sub New(ByVal Items() As IIdentifiable) 194. Me.Storage = Items 195. _StorageCapacity = Items.Length 196. End Sub 197. 198. Public Sub New(ByVal Capacity As Int32) 199. Me.StorageCapacity = Capacity 200. End Sub 201. 202. Public Sub PrintByFilter(ByVal Selector As IdSelector) 203. For Each K As IIdentifiable In Storage 204. If K Is Nothing Then 205. Continue For 206. End If 207. If Selector.Invoke(K.Id) Then 208. Console.WriteLine(K.ToString()) 209. End If 210. Next 211. End Sub 212. 213. Public Sub PrintById(ByVal Id As Int32) 214. For Each K As IIdentifiable In Storage 215. If K Is Nothing Then 216. Continue For 217. End If 218. If K.Id = Id Then 219. Console.WriteLine(K.ToString()) 220. Exit For 221. End If 222. Next 223. End Sub 224. 225. Public Function SearchItems(ByVal Str As String) As Int32() 226. Dim Temp As New ArrayList 227. 228. For Each K As IIdentifiable In Storage 229. If K Is Nothing Then 230. Continue For 231. End If 232. If K.ToString().Contains(Str) Then 233. Temp.Add(K.Id) 234. End If 235. Next 236. 237. Dim Result(Temp.Count - 1) As Int32 238. For I As Int32 = 0 To Temp.Count - 1 239. Result(I) = Temp(I) 240. Next 241. 242. Temp.Clear() 243. Temp = Nothing 244. 245. Return Result 246. End Function 247. End Class 248.
  • 180. 249. End Namespace Notate che ho r acchiuso tutto in un namespace e ho anche messo lo scope Public a tutti i membr i non pr ivati. Se non avessi messo Public, infatti, i membr i senza scope sar ebber o stati automaticamente mar cati con Fr iend. Suppongo vi r icor diate che Fr iend r ende accessibile un membr o solo dalle classi appar tenenti allo stesso assembly (in questo caso, allo stesso pr ogetto): questo equivale a dir e che tutti i membr i Fr iend non sar anno accessibili al di fuor i della libr er ia e quindi chi la user à non potr à acceder vi. Ovviamente, dato che il tutto si basa sull'inter faccia IIdentifiable non potevo pr ecluder ne l'accesso agli utenti della libr er ia, e allo stesso modo i costr uttor i senza Public sar ebber o stati inaccessibili e non si sar ebbe potuto istanziar e alcun oggetto. Ecco che concludiamo la lista di tutti gli specificator i di accesso con Pr otected Fr iend: un membr o dichiar ato Pr otected Fr iend sar à accessibile solo ai membr i delle classi der ivate appar tenenti allo stesso assembly. Per r icapitolar vi tutti gli scope, ecco uno schema dove le fr ecce ver di indicano gli unici accessi consentiti: Importare la libreria in un altro progetto Una volta compilata la libr er ia, al posto dell'eseguibile, nella sottocar tella binRelease del vostr o pr ogetto, si tr over à un file con estensione *.dll. Per usar e le classi contenute in questa libr er ia (o r ifer im ento , nome tecnico che si confonde spesso con i nomi comuni), bisogna impor tar la nel pr ogetto cor r ente. Per far e questo, nel Solution Ex plor er (la finestr a che mostr a tutti gli elementi del pr ogetto) cliccate col pulsante destr o sul nome del pr ogetto e selezionate "Add Refer ence" ("Aggiungi r ifer imento"):
  • 181. Quindi r ecatevi fino alla car tella della libr er ia cr eata, selezionate il file e pr emete OK (nell'esempio c'è una delle libr er ie che ho scr itto e che potete tr ovar e nella sezione Dow nload): or a il r ifer imento è stato aggiunto al pr ogetto, ma non potete ancor a usar e le classi della libr er ia. Pr ima dovete "dir e" al compilator e che nel codice che sta per esser e letto potr este far e r ifer imento ad esse. Questo si fa "impor tando" il namespace, con il codice: 1. Imports [Nome Libreria].[Nome Namespace] Io ho chiamato la libr er ia con lo stesso nome del namespace, ma potete usar e anche nomi diver si, poiché in una libr er ia ci possono esser e tanti namespace differ enti: 1. Imports PostalManagement.PostalManagement Impor ts è una "dir ettiva", ossia non costituisce codice eseguibile, ma infor ma il compilator e che alcune classi del sor gente potr ebber o appar tener e a questo namespace (omettendo questa r iga, dovr ete scr iver e ogni volta PostalManagement.Pack, ad esempio, per usar e la classe Pack, per chè altr imenti il compilator e non sar ebbe in gr ado di
  • 182. tr ovar e il name Pack nel contesto cor r ente). Ecco un esempio: 01. Imports PostalManagement.PostalManagement 02. 03. Module Module1 04. 05. Sub Main() 06. Dim P As New PostalProcessor(10) 07. Dim Pk As New Pack(P.NextId) 08. 09. P.Item(P.FirstPlaceAvailable) = Pk 10. '... 11. End Sub 12. 13. End Module che equivale a: 01. Module Module1 02. 03. Sub Main() 04. Dim P As New PostalManagement.PostalManagement.PostalProcessor(10) 05. Dim Pk As New PostalManagement.PostalManagement.Pack(P.NextId) 06. 07. P.Item(P.FirstPlaceAvailable) = Pk 08. '... 09. End Sub 10. 11. End Module Nella scheda ".NET" che vedete nella seconda immagine di sopr a, ci sono molte libr er ie facenti par te del Fr amew or k che user emo nelle pr ossime sezioni della guida.
  • 183. A41. I Generics - Parte I Panoramic a sui Generic s I Gener ics sono un concetto molto impor tante per quanto r iguar da la pr ogr ammazione ad oggetti, specialmente in .NET e, se fino ad or a non ne conoscevate nemmeno l'esistenza, d'or a in poi non potr ete far ne a meno. Cominciamo col far e un par agone per esemplificar e il concetto di gener ics. Ammettiamo di dichiar ar e una var iabile I di tipo Int32: in questa var iabile potr emo immagazzinar e qualsiasi infor mazione che consista di un numer o inter o r appr esentabile su 32 bit. Possiamo dir e, quindi, che il tipo Int32 costituisce un'astr azione di tutti i numer i inter i esistenti da -2'147'483'648 a +2'147'483'647. Analogamente un tipo g ener ic può assumer e come valor e un altr o tipo e, quindi, astr ae tutti i possibili tipi usabili in quella classe/metodo/pr opr ietà ecceter a. È come dir e: definiamo la funzione Somma(A, B), dove A e B sono di un tipo T che non conosciamo. Quando utilizziamo la funzione Somma, oltr e a specificar e i par ametr i r ichiesti, dobbiamo anche "dir e" di quale tipo essi siano (ossia immetter e in T non un valor e ma un tipo): in questo modo, definendo un solo metodo, potr emo eseguir e somme tr a inter i, decimali, str inghe, date, file, classi, ecceter a... In VB.NET, l'oper azione di specificar e un tipo per un entità gener ic si attua con questa sintassi: 1. [NomeEntità](Of [NomeTipo]) Dato i gener ics di possono applicar e ad ogni entità del .NET (metodi, classi, pr opr ietà, str uttur e, inter facce, delegate, ecceter a...), ho scr itto solo "NomeEntità" per indicar e il nome del tar get a cui si applicano. Il pr ossimo esempio mostr a come i gener ics, usati sulle liste, possano aumentar e di molto le per for mance di un pr ogr amma. La collezione Ar r ayList, molte volte impiegata negli esempi dei pr ecedeti capitoli, per mette di immagazzinar e qualsiasi tipo di dato, memor izzando, quindi, var iabili di tipo Object. Come già detto all'inizio del cor so, l'uso di Object compor ta molti r ischi sia a livello di pr estazioni, dovute alle continue oper azioni di box ing e unbox ing (e le gar bage collection che ne conseguono, data la cr eazione di molti oggetti tempor anei), sia a livello di cor r ettezza del codice. Un esempio di questo ultimo caso si ver ifica quando si tenta di scor r er e un Ar r ayList mediante un ciclo For Each e si incontr a un r ecor d che non è del tipo specificato, ad esempio: 01. Dim A As New ArrayList 02. A.Add(2) 03. A.Add(3) 04. A.Add("C") 05. 'A run-time, sarà lanciata un'eccezione inerente il cast 06. 'poichè la stringa "C" non è del tipo specificato 07. 'nel blocco For Each 08. For Each V As Int32 In A 09. Console.WriteLine(V) 10. Next Infatti, se l'applicazione dovesse er r oneamente inser ir e una str inga al posto di un numer o inter o, non ver r ebbe gener ato nessun er r or e, ma si ver ificher ebbe un'eccezione successivamente. Altr a pr oblematica legata all'uso di collezioni a tipizzazione debole (ossia che r egistr ano gener ici oggetti Object, come l'Ar r ayList, l'HashTable o la Sor tedList) è dovuta al fatto che sia necessar ia una conver sione esplicita di tipo nell'uso dei suoi elementi, almeno nella maggior anza dei casi. La soluzione adottata da un pr ogr ammator e che non conoscesse i gener ics per r isolver e tali inconvenienti sar ebbe quella di cr ear e una nuova lista, ex novo, er editandola da un tipo base come CollectionBase e r idefinendone tutti i metodi (Add, Remove, Index Of ecc...). L'uso dei Gener ics, invece, r ende molto più veloce e meno insidiosa la scr ittur a di un codice r obusto e solido nell'ambito non solo delle collezioni, ma di molti altr i ar gomenti. Ecco un esempio di come implementar e una soluzione basata sui Gener ics: 01. 'La lista accetta solo oggetti di tipo Int32: per questo motivo 02. 'si genera un'eccezione quando si tenta di inserirvi elementi di 03. 'tipo diverso e la velocità di elaborazione aumenta! 04. Dim A As New List(Of Int32) 05.
  • 184. A.Add(1) 06. A.Add(4) 07. A.Add(8) 08. 'A.Add("C") '<- Impossibile 09. For Each V As Int32 In A 10. Console.WriteLine(V) 11. Next E questa è una dimostr azione dell'incr emento delle pr estazioni: 01. Module Module1 02. Sub Main() 03. Dim TipDebole As New ArrayList 04. Dim TipForte As New List(Of Int32) 05. Dim S As New Stopwatch 06. 07. 'Cronometra le operazioni su ArrayList 08. S.Start() 09. For I As Int32 = 1 To 1000000 10. TipDebole.Add(I) 11. Next 12. S.Stop() 13. Console.WriteLine(S.ElapsedMilliseconds & _ 14. " millisecondi per ArrayList!") 15. 16. 'Cronometra le operazioni su List 17. S.Reset() 18. S.Start() 19. For I As Int32 = 1 To 1000000 20. TipForte.Add(I) 21. Next 22. S.Stop() 23. Console.WriteLine(S.ElapsedMilliseconds & _ 24. " millisecondi per List(Of T)!") 25. 26. Console.ReadKey() 27. End Sub 28. End Module Sul mio computer por tatile l'Ar r ayList impiega 197ms, mentr e List 33ms: i Gener ics incr ementano la velocità di 6 volte! Oltr e a List, esistono anche altr e collezioni gener ic, ossia Dictionar y e Sor tedDictionar y: tutti questi sono la ver sione a tipizzazione for te delle nor mali collezioni già viste. Ma or a vediamo come scr iver e nuove classi e metodi gener ic. Generic s Standard Una volta impar ato a dichiar ar e e scr iver e entità gener ics, sar à anche altr ettanto semplice usar e quelli esistenti, per ciò iniziamo col dar e le pr ime infor mazioni su come scr iver e, ad esempio, una classe gener ics. Una classe gener ics si r ifer isce ad un qualsiasi tipo T che non possiamo conoscer e al momento dela scr ittur a del codice, ma che il pr ogr ammator e specificher à all'atto di dichiar azione di un oggetto r appr esentato da questa classe. Il fatto che essa sia di tipo gener ico indica che anche i suoi membr i, molto pr obabilmente, avr anno lo stesso tipo: più nello specifico, potr ebber o esser ci campi di tipo T e metodi che lavor ano su oggetti di tipo T. Se nessuna di queste due condizioni è ver ificata, allor a non ha senso scr iver e una classe gener ics. Ma iniziamo col veder e un semplice esempio: 001. Module Module1 002. 'Collezione generica che contiene un qualsiasi tipo T di 003. 'oggetto. T si dice "tipo generic aperto" 004. Class Collection(Of T) 005. 'Per ora limitiamoci a dichiarare un array interno 006. 'alla classe. 007. 'Vedremo in seguito che è possibile ereditare da 008. 'una collezione generics già esistente. 009. 'Notate che la variabile è di tipo T: una volta che 010. 'abbiamo dichiarato la classe come generics su un tipo T, 011.
  • 185. 'è come se avessimo "dichiarato" l'esistenza di T 012. 'come tipo fittizio. 013. Private _Values() As T 014. 015. 'Restituisce l'Index-esimo elemento di Values (anch'esso 016. 'è di tipo T) 017. Public Property Values(ByVal Index As Int32) As T 018. Get 019. If (Index >= 0) And (Index < _Values.Length) Then 020. Return _Values(Index) 021. Else 022. Throw New IndexOutOfRangeException() 023. End If 024. End Get 025. Set(ByVal value As T) 026. If (Index >= 0) And (Index < _Values.Length) Then 027. _Values(Index) = value 028. Else 029. Throw New IndexOutOfRangeException() 030. End If 031. End Set 032. End Property 033. 034. 'Proprietà che restituiscono il primo e l'ultimo 035. 'elemento della collezione 036. Public ReadOnly Property First() As T 037. Get 038. Return _Values(0) 039. End Get 040. End Property 041. 042. Public ReadOnly Property Last() As T 043. Get 044. Return _Values(_Values.Length - 1) 045. End Get 046. End Property 047. 048. 'Stampa tutti i valori presenti nella collezione a schermo. 049. 'Su un tipo generic è sempre possibile usare 050. 'l'operatore Is (ed il suo corrispettivo IsNot) e 051. 'confrontarlo con Nothing. Se si tratta di un tipo value 052. 'l'uguaglianza con Nothing sarà sempre falsa. 053. Public Sub PrintAll() 054. For Each V As T In _Values 055. If V IsNot Nothing Then 056. Console.WriteLine(V.ToString()) 057. End If 058. Next 059. End Sub 060. 061. 'Inizializza la collezione con Count elementi, tutti del 062. 'valore DefaultValue 063. Sub New(ByVal Count As Int32, ByVal DefaultValue As T) 064. If Count < 1 Then 065. Throw New ArgumentOutOfRangeException() 066. End If 067. 068. ReDim _Values(Count - 1) 069. 070. For I As Int32 = 0 To _Values.Length - 1 071. _Values(I) = DefaultValue 072. Next 073. End Sub 074. 075. End Class 076. 077. Sub Main() 078. 'Dichiara quattro variabili contenenti quattro nuovi 079. 'oggetti Collection. Ognuno di questi, però, 080. 'è specifico per un solo tipo che decidiamo 081. 'noi durante la dichiarazione. String, Int32, Date 082. 'e Person, ossia i tipi che stiamo inserendo nel tipo 083.
  • 186. 'generico T, si dicono "tipi generic collegati", 084. 'poiché collegano il tipo fittizio T con un 085. 'reale tipo esistente 086. Dim Strings As New Collection(Of String)(10, "null") 087. Dim Integers As New Collection(Of Int32)(5, 12) 088. Dim Dates As New Collection(Of Date)(7, Date.Now) 089. Dim Persons As New Collection(Of Person)(10, Nothing) 090. 091. Strings.Values(0) = "primo" 092. Integers.Values(3) = 45 093. Dates.Values(6) = New Date(2009, 1, 1) 094. Persons.Values(3) = New Person("Mario", "Rossi", Dates.Last) 095. 096. Strings.PrintAll() 097. Integers.PrintAll() 098. Dates.PrintAll() 099. Persons.PrintAll() 100. 101. Console.ReadKey() 102. End Sub 103. 104. End Module Ognuna della quattr o var iabili del sor gente contiene un oggetto di tipo Collection, ma tali oggetti non sono dello stesso tipo, poiché ognuno espone un differ ente tipo gener ics collegato. Quindi, nonostante si tr atti sempr e della stessa classe Collection, Collection(Of Int32) e Collection(Of Str ing) sono a tutti gli effetti due tipi diver si: è come se esistesser o due classi in cui T è sostituito in una da Int32 e nell'altr a da Str ing. Per dimostr ar e la lor o diver sità, basta scr iver e: 1. Console.WriteLine(Strings.GetType() Is Integers.GetType()) 2. 'Output : False Metodi Generic s e tipi generic s c ollegati implic iti Se si decide di scr iver e un solo metodo gener ics, e di focalizzar e su di esso l'attenzione, solo accanto al suo nome appar ir à la dichiar azione di un tipo gener ics aper to, con la consueta clausola "(Of T)". Anche se fin'or a ho usato come nome solamente T, nulla vieta di specificar e un altr o identificator e valido (ad esempio Pippo): tuttavia, è convenzione che il nome dei tipi gener ics aper ti sia Tn (con n numer o inter o, ad esempio T1, T2, T3, eccetr a...) o, in caso contr ar io, che inizi almeno con la letter a T (ad esempio TSize, TClass, ecceter a...). 1. Sub [NomeProcedura](Of T)([Parametri]) 2. '... 3. End Sub 4. 5. Function [NomeFunzione](Of T)([Parametri]) As [TipoRestituito] 6. '... 7. End Function Ecco un semplice esempio: 01. Module Module1 02. 03. 'Scambia i valori di due variabili, passate 04. 'per indirizzo 05. Public Sub Swap(Of T)(ByRef Arg1 As T, ByRef Arg2 As T) 06. Dim Temp As T = Arg1 07. Arg1 = Arg2 08. Arg2 = Temp 09. End Sub 10. 11. Sub Main() 12. Dim X, Y As Double 13. Dim Z As Single 14. Dim A, B As String 15. 16. X = 90.0 17.
  • 187. Y = 67.58 18. Z = 23.01 19. A = "Ciao" 20. B = "Mondo" 21. 22. 'Nelle prossime chiamate, Swap non presenta un 23. 'tipo generics collegato: il tipo viene dedotto dai 24. 'tipi degli argomenti 25. 26. 'X e Y sono Double, quindi richiama il metodo con 27. 'T = Double 28. Swap(X, Y) 29. 'A e B sono String, quindi richiama il metodo con 30. 'T = String 31. Swap(A, B) 32. 33. 'Qui viene generato un errore: nonostante Z sia 34. 'convertibile in Double implicitamente senza perdita 35. 'di dati, il suo tipo non corrisponde a quello di X, 36. 'dato che c'è un solo T, che può assumere 37. 'un solo valore-tipo. Per questo è necessario 38. 'utilizzare una scappatoia 39. 'Swap(Z, X) 40. 41. 'Soluzione 1: si esplicita il tipo generic collegato 42. Swap(Of Double)(Z, X) 43. 'Soluzione 2: si converte Z in double esplicitamente 44. Swap(CDbl(Z), X) 45. 46. Console.ReadKey() 47. End Sub 48. End Module Generic s multipli Quando, anziché un solo tipo gener ics, se ne specificano due o più, si par la di genr ics multipli. La dichiar azione avviene allo stesso modo di come abbiamo visto pr ecedentemente e i tipi vengono separ ati da una vir gola: 01. Module Module2 02. 'Una relazione qualsiasi fra due oggetti di tipo indeterminato 03. Public Class Relation(Of T1, T2) 04. Private Obj1 As T1 05. Private Obj2 As T2 06. 07. Public ReadOnly Property FirstObject() As T1 08. Get 09. Return Obj1 10. End Get 11. End Property 12. 13. Public ReadOnly Property SecondObject() As T2 14. Get 15. Return Obj2 16. End Get 17. End Property 18. 19. Sub New(ByVal Obj1 As T1, ByVal Obj2 As T2) 20. Me.Obj1 = Obj1 21. Me.Obj2 = Obj2 22. End Sub 23. End Class 24. 25. Sub Main() 26. 'Crea una relazione fra uno studente e un insegnante, 27. 'utilizzando le classi create nei capitoli precedenti 28. Dim R As Relation(Of Student, Teacher) 29. Dim S As New Student("Pinco", "Pallino", Date.Parse("25/06/1990"), _ 30. "Liceo Scientifico N. Copernico", 4) 31. Dim T As New Teacher("Mario", "Rossi", Date.Parse("01/07/1950"), _ 32.
  • 188. "Matematica") 33. 34. 'Crea una nuova relazione tra lo studente e l'insegnante 35. R = New Relation(Of Student, Teacher)(S, T) 36. Console.WriteLine(R.FirstObject.CompleteName) 37. Console.WriteLine(R.SecondObject.CompleteName) 38. 39. Console.ReadKey() 40. End Sub 41. End Module Notate che è anche possibile cr ear e una r elazione tr a due r elazioni (e la cosa diventa complicata): 01. Dim S As New Student("Pinco", "Pallino", Date.Parse("25/06/1990"), "Liceo Scientifico N. Copernico", 4) 02. Dim T As New Teacher("Mario", "Rossi", Date.Parse("01/07/1950"), "Matematica") 03. Dim StudentTeacherRelation As Relation(Of Student, Teacher) 04. Dim StudentClassRelation As Relation(Of Student, String) 05. Dim Relations As Relation(Of Relation(Of Student, Teacher), Relation(Of Student, String)) 06. 07. StudentTeacherRelation = New Relation(Of Student, Teacher)(S, T) 08. StudentClassRelation = New Relation(Of Student, String)(S, "5A") 09. Relations = New Relation(Of Relation(Of Student, Teacher), Relation(Of Student, String)) (StudentTeacherRelation, StudentClassRelation) 10. 11. 'Relations.FirstObject.FirstObject 12. ' > Student "Pinco Pallino" 13. 'Relations.FirstObject.SecondObject 14. ' > Teacher "Mario Rossi" 15. 'Relations.SecondObject.FirstObject 16. ' > Student "Pinco Pallino" 17. 'Relations.SecondObject.SecondObject 18. ' > String "5A" Alc une regole per l'uso dei Generic s Si può sempr e assegnar e Nothing a una var iabile di tipo gener ics. Nel caso il tipo gener ics collegato sia r efer ence, alla var iabile ver r à assegnato nor malmente Nothing; in caso contr ar io, essa assumer à il valor e di default per il tipo; Non si può er editar e da un tipo gener ic aper to: 1. Class Example(Of T) 2. Inherits T 3. ' SBAGLIATO 4. End Class Tuttavia si può er editar e da una classe gener ics specificando come tipo gener ics collegato lo stesso tipo aper to: 1. Class Example(Of T) 2. Inherits List(Of T) 3. ' CORRETTO 4. End Class Allo stesso modo, non si può implementar e T come se fosse un'inter faccia: 1. Class Example(Of T) 2. Implements T 3. ' SBAGLIATO 4. End Class Ma si può implementar e un'inter faccia gener ics di tipo T: 1. Class Example(Of T) 2. Implements IEnumerable(Of T) 3. ' CORRETTO 4.
  • 189. End Class Entità con lo stesso nome ma con gener ics aper ti differ enti sono consider ate in over load. Per tanto, è lecito scr iver e: 1. Sub Example(Of T)(ByVal A As T) 2. '... 3. End Sub 4. 5. Sub Example(Of T1, T2)(ByVal A As T1) 6. '... 7. End Sub
  • 190. A42. I Generics - Parte II Interfac c e Generic s Pr oviamo or a a scr iver e qualche inter faccia gener ics per veder ne il compor tamento. Ripr endiamo l'inter faccia ICompar er , che indica qualcosa con il compito di compar ar e oggetti: esiste anche la sua cor r ispettiva gener ics, ossia ICompar er (Of T). Non fa nessun differ enza il compor tamento di quest'ultima: l'unica cosa che cambia è il tipo degli oggetti da compar ar e. 01. Module Module1 02. 'Questa classe implementa un comaparatore di oggetti Student 03. 'in base al loro anno di corso 04. Class StudentByGradeComparer 05. Implements IComparer(Of Student) 06. 07. 'Come potete osservare, in questo metodo non viene eseguito 08. 'nessun tipo di cast, poiché l'interfaccia IComparer(Of T) 09. 'prevede un metodo Compare a tipizzazione forte. Dato che 10. 'abbiamo specificato come tipo generic collegato Student, 11. 'anche il tipo a cui IComparer si riferisce sarà 12. 'Student. Possiamo accedere alle proprietà di x e y 13. 'senza nessun late binding (per ulteriori informazioni, 14. 'vedere i capitoli sulla reflection) 15. Public Function Compare(ByVal x As Student, ByVal y As Student) As Integer Implements IComparer(Of Student).Compare 16. Return x.Grade.CompareTo(y.Grade) 17. End Function 18. End Class 19. 20. Sub Main() 21. 'Crea un nuovo array di oggeti Student 22. Dim S(2) As Student 23. 24. 'Inizializza ogni oggetto 25. S(0) = New Student("Mario", "Rossi", New Date(1993, 2, 3), "Liceo Classico Ugo Foscolo", 2) 26. S(1) = New Student("Luigi", "Bianchi", New Date(1991, 6, 27), "Liceo Scientifico Fermi", 4) 27. S(2) = New Student("Carlo", "Verdi", New Date(1992, 5, 12), "ITIS Cardano", 1) 28. 29. 'Ordina l'array con il comparer specificato 30. Array.Sort(S, New StudentByGradeComparer()) 31. 32. 'Stampa il profilo di ogni studente: vedrete che essi sono 33. 'in effetti ordinati in base all'anno di corso 34. For Each St As Student In S 35. Console.WriteLine(St.Profile) 36. Next 37. Console.ReadKey() 38. End Sub 39. 40. End Module I V inc oli I tipi gener ics sono molto utili, ma spesso sono un po' tr oppo... "gener ici" XD Faccio un esempio. Ammettiamo di aver e un metodo gener ics (Of T) che accetta due par ametr i A e B. Pr oviamo a scr iver e: 1. If A = B Then '... L'IDE ci comunica subito un er r or e: "Oper ator '=' is not definited for type 'T' and 'T'." In effetti, poiché T può esser e un
  • 191. qualsiasi tipo, non possiamo neanche saper e se questo tipo implementi l'oper ator e uguale =. In questo caso, vogliamo impor r e come condizione, ossia come v inco lo , che, per usar e il metodo in questione, il tipo gener ic collegato debba obbligator iamente espor r e un modo per saper e se due oggetti di quel tipo sono uguali. Come si r ende in codice? Se fate mente locale sulle inter facce, r icor der ete che una classe r appr esenta un concetto con deter minate car atter istiche se implementa deter minate inter facce. Dovr emo, quindi, tr ovar e un'inter faccia che r appr esenta l'"eguagliabilità": l'inter faccia in questione è IEquatable(Of T). Per poter saper e se due oggetti T sono uguali, quindi, T dovr à esser e un qualsiasi tipo che implementa IEquatable(Of T). Ecco che dobbiamo impor r e un vincolo al tipo. Esistono cinque categor ie di vincoli: Vincolo di inter faccia; Vincolo di er editar ietà; Vincolo di classe; Vincolo di str uttur a; Vincolo New . Iniziamo con l'analizzar e il pr imo di cui abbiamo par lato. V inc olo di Interfac c ia Il vincolo di inter faccia è indubbiamente uno dei più utili e usati accanto a quello di er editar ietà. Esso impone che il tipo gener ic collegato implementi l'inter faccia specificata. Dato che dopo l'imposizione del vincolo sappiamo per ipotesi che il tipo T espor r à sicur amente tutti i membr i di quell'inter faccia, possiamo r ichiamar e tali membr i da tutte le var iabili di tipo T. La sintassi è molto semplice: 1. (Of T As [Interfaccia]) Ecco un esempio: 001. Module Module1 002. 003. 'Questa classe rappresenta una collezione di 004. 'elementi che possono essere comparati. Per questo 005. 'motivo, il tipo T espone un vincolo di interfaccia 006. 'che obbliga tutti i tipi generics collegati ad 007. 'implementare tale interfaccia. 008. 'Notate bene che in questo caso particolare ho usato 009. 'un generics doppio, poiché il vincolo non 010. 'si riferisce a IComparable, ma a IComparable(Of T). 011. 'D'altra parte, è abbastanza ovvio che se 012. 'una collezione contiene un solo tipo di dato, 013. 'basterà che la comparazione sia possibile 014. 'solo attraverso oggetti di quel tipo 015. Class ComparableCollection(Of T As IComparable(Of T)) 016. 'Ereditiamo direttamente da List(Of T), acquisendone 017. 'automaticamente tutti i membri base e le caratteristiche. 018. 'In questo modo, godremo di due grandi vantaggi: 019. ' - non dovremo definire tutti i metodi per aggiungere, 020. ' rimuovere o cercare elementi, in quanto vengono tutti 021. ' ereditati dalla classe base List; 022. ' - non dovremo neanche implementare l'interfaccia 023. ' IEnumerable(Of T), poiché la classe base la 024. ' implementa di per sé. 025. Inherits List(Of T) 026. 027. 'Dato che gli oggetti contenuti in oggetti di 028. 'questo tipo sono per certo comparabili, possiamo 029. 'trovarne il massimo ed il minimo. 030. 031. 'Trova il massimo elemento 032. Public ReadOnly Property Max() As T 033. Get 034.
  • 192. If Me.Count > 0 Then 035. Dim Result As T = Me(0) 036. 037. For Each Element As T In Me 038. 'Ricordate che A.CompareTo(B) restituisce 039. '1 se A > B 040. If Element.CompareTo(Result) = 1 Then 041. Result = Element 042. End If 043. Next 044. 045. Return Result 046. Else 047. Return Nothing 048. End If 049. End Get 050. End Property 051. 052. 'Trova il minimo elemento 053. Public ReadOnly Property Min() As T 054. Get 055. If Me.Count > 0 Then 056. Dim Result As T = Me(0) 057. 058. For Each Element As T In Me 059. If Element.CompareTo(Result) = -1 Then 060. Result = Element 061. End If 062. Next 063. 064. Return Result 065. Else 066. Return Nothing 067. End If 068. End Get 069. End Property 070. 071. 'Trova tutti gli elementi uguali ad A e ne restituisce 072. 'gli indici 073. Public Function FindEquals(ByVal A As T) As Int32() 074. Dim Result As New List(Of Int32) 075. 076. For I As Int32 = 0 To Me.Count - 1 077. If Me(I).CompareTo(A) = 0 Then 078. Result.Add(I) 079. End If 080. Next 081. 082. 'Converte la lista di interi in un array di interi 083. 'con gli stessi elementi 084. Return Result.ToArray() 085. End Function 086. 087. End Class 088. 089. Sub Main() 090. 'Tre collezioni, una di interi, una di stringhe e 091. 'una di date 092. Dim A As New ComparableCollection(Of Int32) 093. Dim B As New ComparableCollection(Of String) 094. Dim C As New ComparableCollection(Of Date) 095. 096. A.AddRange(New Int32() {4, 19, 6, 90, 57, 46, 4, 56, 4}) 097. B.AddRange(New String() {"acca", "casa", "zen", "rullo", "casa"}) 098. C.AddRange(New Date() {New Date(2008, 1, 1), New Date(1999, 12, 31), New Date(2100, 4, 12)}) 099. 100. Console.WriteLine(A.Min()) 101. ' > 4 102. Console.WriteLine(A.Max()) 103. ' > 90 104. Console.WriteLine(B.Min()) 105.
  • 193. ' > acca 106. Console.WriteLine(B.Max()) 107. ' > zen 108. Console.WriteLine(C.Min().ToShortDateString) 109. ' > 31/12/1999 110. Console.WriteLine(C.Max().ToShortDateString) 111. ' > 12/4/2100 112. 113. 'Trova la posizione degli elementi uguali a 4 114. Dim AEqs() As Int32 = A.FindEquals(4) 115. ' > 0 6 8 116. Dim BEqs() As Int32 = B.FindEquals("casa") 117. ' > 1 4 118. 119. Console.ReadKey() 120. End Sub 121. 122. End Module V inc olo di ereditarietà Ha la stessa sintassi del vincolo di inter faccia, con la sola differ enza che al posto dell'inter faccia si specifica la classe dalla quale il tipo gener ics collegato deve er editar e. I vantaggi sono pr aticamente uguali a quelli offer ti dal vincolo di inter faccia: possiamo tr attar e T come se fosse un oggetto di tipo [Classe] (una classe qualsiasi) ed utilizzar ne i membr i, poiché tutti i tipi possibili per T sicur amente der ivano da [Classe]. Un esempio anche per questo vincolo mi sembr a abbastanza r idondante, ma c'è una caso par ticolar e che mi piacer ebbe sottolinear e. Mi r ifer isco al caso in cui al posto della classe base viene specificato un altr o tipo gener ic (aper to), e di questo, data la non immediatezza di compr ensione, posso dar e un veloce esempio: 1. Class IsARelation(Of T, U As T) 2. Public Base As T 3. Public Derived As U 4. End Class Questa classe r appr esenta una r elazione is-a ("è un"), quella famosa r elazione che avevo intr odotto come esempio una quar antina di capitoli fa dur ante i pr imi par agr afi di spiegazione. Questa r elazione è r appr esentata par ticolar mente bene, dicevo, se si pr ende una classe base e la sua classe der ivata. I tipi gener ics aper ti non fanno altr o che astr ar r e questo concetto: T è un tipo qualsiasi e U un qualsiasi altr o tipo der ivato da T o uguale T (non c'è un modo per impor r e che sia solo der ivato e non lo stesso tipo). Ad esempio, potr ebbe esser e valido un oggetto del gener e: 1. Dim P As Person 2. Dim S As Student 3. '... 4. Dim A As New IsARelation(Of Person, Student)(P, S) V inc oli di c lasse e struttura Il vincolo di classe impone che il tipo gener ics collegato sia un tipo r efer ence, mentr e il vincolo di str uttur a impone che sia un tipo value. Le sintassi sono le seguenti: 1. (Of T As Class) 2. (Of T As Structure) Questi due vincoli non sono molto usati, a dir e il ver o, e la lor o utilità non è così mar cata e lampante come appar e per i pr imi due vincoli analizzati. Cer to, possiamo evitar e alcuni compor tamenti str ani dovuti ai tipi r efer ence, o sfr uttar e alcune car atter istiche dei tipi value, ma nulla di più. Ecco un esempio dei possibili vantaggi:
  • 194. Vincolo di classe: Possiamo assegnar e Nothing con la sicur ezza di distr ugger e l'oggetto e non di cambiar ne semplicemente il valor e in 0 (o in quello di default per un tipo non numer ico); Possiamo usar e con sicur ezza gli oper ator i Is, IsNot, TypeOf e Dir ectCast che funzionano solo con i tipi r efer ence; Vincolo di str uttur a: Possiamo usar e l'oper ator e = per compar ar e due valor i sulla base di quello che contengono e non di quello che "sono"; Possiamo evitar e gli inconvenienti dell'assegnamento dovuti ai tipi r efer ence. User ò il vincolo di classe in un esempio molto significativo, ma solo quando intr odur r ò la Reflection, quindi fatevi un aster isco su questo capitolo. V inc olo New Questo vincolo impone al tipo gener ic collegato di espor r e almeno un costr uttor e senza par ametr i. Par ticolar mente utile quando si devono inizializzar e dei valor i gener ics: 01. Module Module1 02. 03. 'Con molta fantasia, il vincolo New si dichiara postponendo 04. '"As New" al tipo generic aperto. 05. Function CreateArray(Of T As New)(ByVal Count As Int32) As T() 06. Dim Result(Count - 1) As T 07. 08. For I As Int32 = 0 To Count - 1 09. 'Possiamo usare il costruttore perchè il 10. 'vincolo ce lo assicura 11. Result(I) = New T() 12. Next 13. 14. Return Result 15. End Function 16. 17. Sub Main() 18. 'Crea 10 flussi di dati in memoria. Non abbiamo 19. 'mai usato questa classe perchè rientra in 20. 'un argomento che tratterò più avanti, ma 21. 'è una classe particolarmente utile e versatile 22. 'che trova applicazioni in molte situazioni. 23. 'Avere un bel metodo generics che ne crea 10 in una 24. 'volta è una gran comodità. 25. 'Ovviamente possiamo fare la stessa cosa con tutti 26. 'i tipi che espongono almeno un New senza parametri 27. Dim Streams As IO.MemoryStream() = CreateArray(Of IO.MemoryStream)(10) 28. 29. '... 30. End Sub 31. 32. End Module V inc oli multipli Un tipo gener ic aper to può esser e sottoposto a più di un vincolo, ossia ad un vincolo multiplo, che altr o non è se non la combinazione di due o più vincoli semplici di quelli appena visti. La sintassi di un vincolo multiplo è legger mente diver sa e pr evede che tutti i vincoli siano r aggr uppati in una copia di par entesi gr affe e separ ati da vir gole: 1. (Of T As {Vincolo1, Vincolo2, ...})
  • 195. Ecco un esempio: 01. Module Module1 02. 03. 'Classe che filtra dati di qualsiasi natura 04. Class DataFilter(Of T) 05. Delegate Function FilterData(ByVal Data As T) As Boolean 06. 07. 'La signature chilometrica è fatta apposta per 08. 'farvi impazzire XD Vediamo le parti una per una: 09. ' - TSerach: deve essere un tipo uguale a T o derivato 10. ' da T, in quanto stiamo elaborando elementi di tipo T; 11. ' inoltre deve anche essere clonabile, poiché 12. ' salveremo solo una copia dei valor trovati. 13. ' Questo implica che TSearch sia un tipo reference, e che 14. ' quindi lo sia anche T: questa complicazione è solo 15. ' per mostrare dei vincoli multipli e potete anche 16. ' rimuoverla se vi pare; 17. ' - TList: deve essere un tipo reference, esporre un 18. ' costruttore senza parametri ed implementare 19. ' l'interfaccia IList(Of TSearch), ossia deve 20. ' essere una lista; 21. ' - ResultList: lista in cui riporre i risultati (passata 22. ' per indirizzo); 23. ' - Filter: delegate che punta alla funzione usata per 24. ' selezionare i valori; 25. ' - Data: paramarray contenente i valori da filtrare. 26. Sub Filter(Of TSearch As {ICloneable, T}, TList As {IList(Of TSearch), New, Class}) _ 27. (ByRef ResultList As TList, ByVal Filter As FilterData, ByVal ParamArray Data() As TSearch) 28. 29. 'Se la lista è Nothing, la inizializza. 30. 'Notare che non avremmo potuto compararla a Nothing 31. 'senza il vincolo Class, né inizializzarla 32. 'senza il vincolo New 33. If ResultList Is Nothing Then 34. ResultList = New TList() 35. End If 36. 37. 'Itera sugli elementi di data 38. For Each Element As TSearch In Data 39. 'E aggiunge una copia di quelli che 40. 'soddisfano la condizione 41. If Filter.Invoke(Element) Then 42. 'Aggiunge una copia dell'elemento alla lista. 43. 'Anche in questo non avremmo potuto richiamare 44. 'Add senza il vincolo interfaccia su IList, né 45. 'clonare Element senza il vincolo interfaccia ICloneable 46. ResultList.Add(Element.Clone()) 47. End If 48. Next 49. End Sub 50. End Class 51. 52. 'Controlla se la stringa A è palindroma 53. Function IsPalindrome(ByVal A As String) As Boolean 54. Dim Result As Boolean = True 55. 56. For I As Int32 = 0 To (A.Length / 2) - 1 57. If A.Chars(I) <> A.Chars(A.Length - 1 - I) Then 58. Result = False 59. Exit For 60. End If 61. Next 62. 63. Return Result 64. End Function 65. 66. Sub Main() 67. Dim DF As New DataFilter(Of String) 68. 'Lista di stringhe: notare che la variabile non 69. 'contiene nessun oggetto perchè non abbiamo usato New. 70.
  • 196. 'Serve per mostrare che verrà inizializzata 71. 'da DF.Filter. 72. Dim L As List(Of String) 73. 74. 'Analizza le stringhe passate, trova quelle palindrome 75. 'e le pone in L 76. DF.Filter(L, AddressOf IsPalindrome, _ 77. "casa", "pane", "anna", "banana", "tenet", "radar") 78. 79. For Each R As String In L 80. Console.WriteLine(R) 81. Next 82. 83. Console.ReadKey() 84. End Sub 85. 86. End Module
  • 197. A43. I tipi Nullable I tipi Nullable costituiscono una utile applicazione dei gener ics alla gestione dei database. Infatti, quando si lavor a con dei database, capita molto spesso di tr ovar e alcune celle vuote, ossia il cui valor e non è stato impostato. In questo caso, l'oggetto che media tr a il database e il pr ogr amma - oggetto che analizzer emo solo nella sezione C - pone in tali celle uno speciale valor e che significa "non contiene nulla". Questo valor e è par i a DBNull.Value, una costante statica pr eimpostata di tipo DBNull, appunto. Essendo un tipo r efer ence, l'assegnar e il valor e di una cella a una var iabile value può compor tar e er r or i nel caso tale cella contenga il famiger ato DBNull, poiché non si è in gr ado di effettuar e una conver sione. Compor tamenti del gener e costr ingono (anzi, costr ingevano) i pr ogr ammator i a scr iver e una quantità eccessiva di costr utti di contr ollo del tipo: 01. If Cell.Value IsNot DBNull.Value Then 02. Variable = Cell.Value 03. Else 04. Variable = 0 05. 'Per impostare il valore di default, bisognava ripetere 06. 'questi If tante volte quanti erano i tipi in gioco, poiché 07. 'non c'era modo di assegnare un valore Null a tutti 08. 'in un solo colpo 09. End If Tuttavia, con l'avvento dei gener ics, nella ver sione 2005 del linguaggio, questi pr oblemi sono stati ar ginati, almeno in par te e almeno per chi conosce i tipi nullable. Questi speciali tipi sono str uttur e gener ics che possono anche accettar e valor i r efer ence come Nothing: ovviamente, dato che i pr oblemi insor gono solo quando si tr atta di tipi value, i tipi gener ics collegati che è lecito specificar e quando si usa nullable devono esser e tipi value (quindi c'è un vincolo di str uttur a). Ci sono due sintassi molto diver se per dichiar ar e tipi nullable, una esplicita e una implicita: 1. 'Dichiarazione esplicita: 2. Dim [Nome] As Nullable(Of [Tipo]) 3. 4. 'Dichiarazione implicita: 5. Dim [Nome] As [Tipo]? La seconda si attua postponendo un punto inter r ogativo al nome del tipo: una sintassi molto br eve e concisa che tuttavia può anche sfuggir e facilmente all'occhio. Una volta dichiar ata, una var iabile nullable può esser e usata come una comunissima var iabile del tipo gener ic collegato specificato. Essa, tuttavia, espone alcuni membr i in più r ispetto ai nor mali tipi value, nella fattispecie: HasValue : pr opr ietà r eadonly che r estituisce Tr ue se l'oggetto contiene un valor e; Value : pr opr ietà r eadonly che r estituisce il valor e dell'oggetto, nel caso esista; GetValueOr Default() : funzione che r estituisce Value se l'oggetto contiene un valor e, altr imenti il valor e di default per quel tipo (ad esempio 0 per i tipi numer ici). Ha un over load che accetta un par ametr o - GetValur Or Default(X): in questo caso, se l'oggetto non contiene nulla, viene r estituito X al posto del valor e di default. Ecco un esempio: 01. Module Module1 02. 03. Sub Main() 04. 'Tre variabili di tipo value dichiarate come 05. 'nullable nei due modi diversi consentiti 06. Dim Number As Integer? 07. Dim Data As Nullable(Of Date) 08.
  • 198. Dim Cost As Double? 09. Dim Sent As Nullable(Of Boolean) 10. 11. 'Ammettiamo di star controllando un database: 12. 'questo array di oggetti rappresenta il contenuto 13. 'di una riga 14. Dim RowValues() As Object = {DBNull.Value, New Date(2009, 7, 1), 67.99, DBNull.Value} 15. 16. 'Con un solo ciclo trasforma tutti i DBNull.Value 17. 'in Nothing, poiché i nullable supportano solo 18. 'Nothing come valore nullo 19. For I As Int16 = 0 To RowValues.Length - 1 20. If RowValues(I) Is DBNull.Value Then 21. RowValues(I) = Nothing 22. End If 23. Next 24. 25. 'Assegna alle variabili i valori contenuti nell'array: 26. 'non ci sono mai problemi in questo codice, poiché, 27. 'trattandosi di tipi nullable, questi oggetti possono 28. 'accettare anche valori Nothing. In questo esempio, 29. 'Number e Sent riceveranno un Nothing come valore: la 30. 'loro proprietà HasValue varrà False. 31. Number = RowValues(0) 32. Data = RowValues(1) 33. Cost = RowValues(2) 34. Sent = RowValues(3) 35. 36. 'Scrive a schermo il valore di ogni variabile, se ne 37. 'contiene uno, oppure il valore di default se non 38. 'contiene alcun valore. 39. Console.WriteLine("{0} {1} {2} {3}", _ 40. Number.GetValueOrDefault, _ 41. Data.GetValueOrDefault, _ 42. Cost.GetValueOrDefault, _ 43. Sent.GetValueOrDefault) 44. 45. 'Provando a stampare una variabile nullable priva 46. 'di valore senza usare la funzione GetValueOrDefault, 47. 'semplicemente non stamperete niente: 48. ' Console.WriteLine(Number) 49. 'Non stampa niente e va a capo. 50. 51. Console.ReadKey() 52. End Sub 53. 54. End Module Logic a booleana a tre valori Un valor e nullable Boolean può assumer e vir tualmente tr e valor i: ver o (Tr ue), falso (False) e null (senza valor e). Usando una var iabile booleana nullable come oper ando per gli oper ator i logici, si otter r anno r isultati diver si a seconda che essa abbia o non abbia un valor e. Le nuove combinazioni che possono esser e eseguite si vanno ad aggiunger e a quelle già esistenti per cr ear e un nuovo tipo di logica elementar e, detta, appunto, "logica booleana a tr e valor i". Essa segue questo schema nei casi in cui un oper ando sia null: Valor e 1 Oper ator e Valor e 2 Risultato Tr ue And Null Null False And Null False Tr ue Or Null Tr ue False Or Null Null
  • 199. Tr ue Xor Null Null False Xor Null Null
  • 200. A44. La Reflection - Parte I Con il ter mine gener ale di r eflection si intendono tutte le classi del Fr amew or k che per mettono di acceder e o manipolar e assembly e moduli. A ssem bly L'assembly è l'unità logica più piccola su cui si basa il Fr amew or k .NET. Un assembly altr o non è che un pr ogr amma o una libr er ia di classi (compilati in .NET). Il Fr amew or k stesso è composto da una tr entina di assembly pr incipali che costituiscono le libr er ie di classi più impor tanti per la pr ogr ammazione .NET (ad esempio System.dll, System.Dr aw ing.dll, System.Cor e.dll, ecceter a...). Il ter mine Reflection ha un significato molto pr egnante: la sua tr aduzione in italiano è alquanto lampante e significa "r iflessione". Dato che viene usata per ispezionar e, analizzar e e contr ollar e il contenuto di assembly, r isulta evidente che mediante r eflection noi scr iviamo del codice che analizza altr o codice, anche se compilato: è una specie di our obor os, il ser pente che si mor de la coda; una r iflessione della pr ogr ammazione su se stessa, appunto. Lasciando da par te questo inter cor so filosofico, c'è da dir e che la r eflection è di gr an lunga una delle tecniche più utilizzate dall'IDE e dal Fr amew or k stesso, anche se spesso questi meccanismi si svolgono "dietr o le quinte" e vengono mascher ati per non far li appar ir e evidenti. Alcuni esempi sono la ser ializzazione, di cui mi occuper ò in seguito, ed il late binding. Late Binding L'azione del legar e (in inglese, appunto, "bind") un identificator e a un valor e viene detta binding: si esegue un binding, ad esempio, quando si assegna un nome a una var iabile. Questo consente un'astr azione fondamentale affinché il pr ogr ammator e possa compr ender e ciò che sta scr itto nel codice: nessuno r iuscir ebbe a capir e alcunché se al posto dei nomi di var iabile ci fosser o degli indir izzi di memor ia a otto cifr e. Ebbene, esistono due tipi di binding: quello statico o "ear ly", e quello dinam ico o "late". Il pr imo viene effetuato pr ima che il pr ogr amma sia eseguito, ed è quello che per mette al compilator e di tr adur r e in linguaggio inter medio le istr uzioni scr itte in for ma testuale dal pr ogr ammator e. Quando assegnamo un nome ad una var iabile, o r ichiamiamo un metodo da un oggetto stiamo attuando un ear ly binding: sappiamo che quell'identificator e è logicamente legato a quel pr eciso valor e di quel pr eciso tipo e che, allo stesso modo, quel nome r ichiamer à pr opr io quel metodo da quell'oggetto e, non, magar i, un metodo a caso disper so nella memor ia. Il secondo, al contr ar io, viene por tato a ter mine mentr e il pr ogr amma è in esecuzione: ad esempio, r ichiamar e dei metodi d'istanza di una classe Per son da un oggetto Object è un esempio di late binding, poiché solo a r un-time, il nome del membr o ver r à letto, ver ificato, e, in caso di successo, r ichiamato. Tuttavia, non esiste alcun legame tr a una var iabile Object e una di tipo Per son, se non che, a r untime, la pr ima potr à contener e un valor e di tipo Per son, ma questo il compilator e non può saper lo in anticipo (mentr e noi sì). Esiste un unico namespace dedicato inter amente alla r eflection e si chiama, appunto, System.Reflection. Una delle classi più impor tanti in questo ambito, invece, è System.Type. Quest'ultima è una classe molto speciale, poiché ne esistono molte istanze, ognuna unica, ma non è possibile cr ear ne di nuove. Ogni istanza di Type r appr esenta un tipo: ad esempio, c'è un oggetto Type per Str ing, uno per Per son, uno per Integer , e via dicendo. Risulta logico che non possiamo cr ear e un oggetto Type, per chè non sar ebbe associato ad alcun tipo e non avr ebbe motivo di esister e: possiamo, al contr ar io, ottener e un oggetto Type già esistente.
  • 201. I Contesti Pr ima di iniziar e a veder e come analizzar e un assembly, dobbiamo fer mar ci un attimo a capir e come funziona il sistema oper ativo a livello un po' più basso del nor male. Questo ci sar à utile per sceglier e una modalità di accesso all'assembly coer ente con le nostr e necessità. Quasi ogni sistema oper ativo è composto di più str ati sovr apposti, ognuno dei quali ha il compito di gestir e una deter minata r isor sa dell'elabor ator e e di for nir e per essa un'astr azione, ossia una visione semplificata ed estesa. Il pr imo str ato è il gestor e di pr ocessi (o ker nel), che ha lo scopo di coor dinar e ed isolar e i pr ogr ammi in esecuzione r acchiudendoli in ar ee di memor ia separ ate, i pr ocessi appunto. Un pr ocesso r appr esenta un "pr ogr amma in esecuzione" e non contiene solo il semplice codice eseguibile, ma, oltr e a questo, mantiene tutti i dati iner enti al funzionamento del pr ogr amma, ivi compr esi var iabili, collegamenti a r isor se ester ne, stato della CPU, ecceter a... Oltr e ad assegnar e un dato per iodo di tempo macchina ad ogni pr ocesso, il ker nel separ a le ar ee di memor ia r iser vate a ciascuno, r endendo impossibile per un pr ocesso modificar e i dati di un altr o pr ocesso, causando, in questo modo, un possibile cr ash di entr ambi i pr ogr ammi o del sistema stesso. Questa politica di coor dinamento, quindi, r ende sicur a e isolata l'esecuzione di un pr ogr amma. Il CLR del .NET, tuttavia, aggiunge un'ulter ior e suddivisione, basata sui dom ini applicativ i o A ppDo m ain o co ntesti di esecuzio ne. All'inter no di un singolo pr ocesso possono esister e più domini applicativi, i quali sono tr a lor o isolati come se fosser o due pr ocessi differ enti: in questo modo, un assembly appar tenente ad un cer to AppDomain non può modificar e un altr o assembly in un altr o AppDomain. Tuttavia, come è lecito scambiar e dati fr a pr ocessi, è anche lecito scambiar e dati tr a contesti di esecuzione: l'unica differ enza sta nel fatto che questi ultimi sono allocati nello stesso pr ocesso e, quindi, possono comunicar e molto più velocemente. Così facendo, un singolo pr ogr ama può cr ear e due domini applicativi che cor r ono in par allelo come se fosser o pr ocessi differ enti, ma attr aver so i quali è molto più semplice la comunicazione e lo scambio di dati. Un semplice esempio lo potr ete tr ovar e osser vando il Task Manager di Window s quando ci sono due finestr e di Fir eFox aper te allo stesso tempo: noter e che vi è un solo pr ocesso fir efox .ex e associato.
  • 202. Caric are un assembly Un assembly è r appr esentato dalla classe System.Reflection.Assembly. Tutte le oper azioni effettuabili su di esso sono esposte mediante metodi della classe assembly. Pr imi fr a tutti, spiccano i metodi per il car icamento, che si distinguono dagli altr i per la lor o copiosa quantità. Esistono, infatti, ben sette metodi statici per car icar e od ottener e un r ifer imento ad un assembly, e tutti offr ono una modalità di car icamento diver sa dagli altr i. Eccone una lista: Assembly.GetEx cecutingAssembly() Restituisce un r ifer imento all'assembly che è in esecuzione e dal quale questa chiamata a funzione viene lanciata. In poche par ole, l'oggetto che ottenete invocando questo metodo si r ifer isce al pr ogr amma o alla libr er ia che state scr ivendo; Assembly.GetAssembly(ByVal T As System.Type) oppur e T.Assembly() Restituiscono un r ifer imento all'assembly in cui è definito il tipo T specificato; Assembly.Load("Nome") Car ica un assembly a par tir e dal nome completo o par ziale. Ad esempio, si può car icar e System.Xml.dll dinamicamente con Assembly.Load("System.Xml"). Restituisce un r ifer imento all'assembly car icato. "Nome" può
  • 203. anche esser e il no m e co m pleto dell'assembly, che compr ende nome, ver sione, cultur a e token della chiave pubblica. La chiave pubblica è un lunghissimo codice for mato da cifr e esadecimali che identificano univocamente il file; il suo token ne è una ver sione "abbr eviata", utile per non scr iver e la chiave inter a. Vedr emo tr a poco una descr izione dettagliata del nome di un assembly. Se un assembly viene car icato con Load, esso diviene par te del contesto di esecuzione cor r ente, e inoltr e il Fr amew or k è capace di tr ovar e e car icar e le sue dipendenze da altr i file, ossia tutti gli assembly che ser vono a questo per funzionar e (in gener e tutti quelli specificati nelle dir ettive Impor ts). In ger go, quest'ultima azione si dice "r isolver e le dipendenze"; Assembly.LoadFr om("File") Car ica un assembly a par tir e dal suo per cor so su disco, che può esser e r elativo o assoluto, e ne r estituisce un r ifer imento. Il file car icato in questo modo diventa par te del contesto di esecuzione di LoadFr om. Inoltr e, il Fr amew or k è in gr ado di r isolver ne le dipendenze solo nel caso in cui queste siano pr esenti nella car tella pr incipale dell'applicazione; Assembly.LoadFile("File") Agisce in modo analogo a LoadFr om, ma l'assembly viene car icato in un contesto di esecuzione differ ente, e il Fr amew or k non è in gr ado di r isolver ne le dipendenze, a meno che queste non siano state già car icate con i metodi sopr a r ipor tati; Assembly.ReflectionOnlyLoad("Nome") Restituisce un r ifer imento all'assembly con dato Nome. Questo non viene car icato in memor ia, poichè il metodo ser ve solamente a ispezionar ne gli elementi; Assembly.ReflectionOnlyLoadFr om("File") Restituisce un r ifer imento all'assembly specificato nel per cor so File. Questo non viene car icato in memor ia, poichè il metodo ser ve solamente a ispezionar ne gli elementi. Gli ultimi due metodi hanno anche un par ticolar e effetto collater ale. Anche se gli assembly non vengono car icati in memor ia, ossia non diventano par te attiva dal dominio applicativo, pur tuttavia vengono posti in un altr o contesto speciale, detto co ntesto di ispezio ne. Quest'ultimo è unico per ogni pr ocesso e condiviso da tutti gli AppDomain pr esenti nel pr ocesso. Nome dell'assembly e analisi superfic iale Una volta ottenuto un r ifer imento ad un oggetto di tipo Assembly, possiamo usar ne i membr i per ottener e le più var ie infor mazioni. Ecco una br eve lista delle pr opr ietà e dei metodi più significativi: Fullname : r estituisce il nome completo dell'assembly, specificando nome, cultur a, ver sione e token della chiave pubblica; CodeBase : nel caso l'assembly sia scar icato da inter net, ne r estituisce la locazione in for mato oppor tuno; Location : r estituisce il per cor so su disco dell'assembly; GlobalAssemblyChace : pr opr ietà che value Tr ue nel caso l'assembly sia stato car icato dalla GAC; G lo bal A ssem bly Cache (G A C) La car tella fisica in cui vengono depositati tutti gli assembly pubblici. Per assembly pubblico, infatti, s'intende ogni assembly accessibile da ogni applicazione su una deter minata macchina. Gli assembly pubblici sono, solitamente, tutti quelli di base del Fr amew or k .NET, ma è possibile aggiunger ne altr i con deter minati comandi. La GAC di Window s è di solito posizionata in C:WINDOWSassembly e contiene tutte le libr er ie base del Fr amew or k. Ecco per chè basta specificar e il nome dell'assembly pubblico per car icar lo (la car tella è nota a pr ior i).
  • 204. ReflectionOnly : r estituisce Tr ue se l'assembly è stato car icato per soli scopi di analisi (r eflection); GetName() : r estituisce un oggetto AssemblyName associato all'assembly cor r ente; GetTypes() : r estituisce un ar r ay di Type che definiscono ogni tipo dichiar ato all'inter no dell'assembly. Pr ima di ter minar e il capitolo, esaminiamo le par ticolar ità del nome dell'assembly. In gener e il no m e co m pleto di un assembly ha questo for mato: [Nome Principale], Version=a.b.c.d, Culture=[Cultura], PublicKeyToken=[Token] Il nome pr incipale è deter minato dal pr ogr ammator e e di solito indica il namespace pr incipale contenuto nell'assembly. La ver sione è un numer o di ver sione a quattr o par ti, divise solitamente, in or dine, come segue: Major (numer o di ver sione pr incipale) , Minor (numer o di ver sione minor e, secondar io), Revision (numer o della r evisione a cui si è giunti per questa ver sione), Build (numer o di compilazioni eseguite per questa r evisione). Il numer o di ver sione indica di solito la ver sione del Fr amew or k per cui l'assembly è stato scr itto: se state usando VB2005, tutte le ver sioni sar anno uguali o infer ior i a 2.0.0.0; con VB2008 sar anno uguali o infer ior i a 3.5.0.0. Cultur e r appr esenta la cultur a in cui è stato scr itto l'assembly: di solito è semplicmente "neutr al", neutr ale, ma nel caso in cui sia differ ente, influenza alcuni aspetti secondar i come la r appr esentazione dei numer i (sepr ator i decimali e delle migliaia), dell'or ar io, i simboli di valuta, ecceter a... Il token della chiave pubblica è un insieme di otto bytes che identifica univocamente la chiave pubblica (è una sua ver sione "abbr eviata"), la quale identifica univocamente l'assembly. Viene usato il token e non tutta la chiave per questioni di lunghezza. Ecco un esempio che ottiene questi dati: 01. Module Module1 02. 03. Sub Main() 04. 'Carica un assembly per soli scopi di analisi. 05. 'mscorlib è l'assembly più importante di 06. 'tutto il Framework, da cui deriva pressochè ogni 07. 'cosa. Data la sua importanza, non ha dipendenze, 08. 'perciò non ci saranno problemi nel risolverle. 09. 'Se volete caricare un altro assembly, dovrete usare 10. 'uno dei metodi in grado di risolvere le dipendenze. 11. Dim Asm As Assembly = Assembly.ReflectionOnlyLoad("mscorlib") 12. Dim Name As AssemblyName = Asm.GetName 13. 14. Console.WriteLine(Asm.FullName) 15. Console.WriteLine("Nome: " & Name.Name) 16. Console.WriteLine("Versione: " & Name.Version.ToString) 17. Console.WriteLine("Cultura: " & Name.CultureInfo.Name) 18. 19. 'Il formato X indica di scrivere un numero usando la 20. 'codifica esadecimale. X2 impone di occupare sempre almeno 21. 'due posti: se c'è una sola cifra, viene inserito 22. 'uno zero. 23. Console.Write("Public Key: ") 24. For Each B As Byte In Name.GetPublicKey() 25. Console.Write("{0:X2}", B) 26. Next 27. Console.WriteLine() 28. 29. Console.Write("Public Key token: ") 30. For Each B As Byte In Name.GetPublicKeyToken 31. Console.Write("{0:X2}", B) 32. Next 33. Console.WriteLine() 34. 35. Console.WriteLine("Processore: " & _ 36. Name.ProcessorArchitecture.ToString) 37. 38. Console.ReadKey() 39. 40. End Sub 41. 42. End Module
  • 205. Con quello che abbiamo visto fin'or a si potr ebbe scr iver e una pr ocedur a che enumer i tutti gli assembly pr esenti nel contesto cor r ente: 01. Sub EnumerateAssemblies() 02. Dim Asm As Assembly 03. Dim Name As AssemblyName 04. 05. 'AppDomain è una variabile globale, oggetto singleton, da cui 06. 'si possono trarre informazioni sull'AppDomain corrente o 07. 'crearne degli altri. 08. For Each Asm In AppDomain.CurrentDomain.GetAssemblies 09. Name = Asm.GetName 10. Console.WriteLine("Nome: " & Name.Name) 11. Console.WriteLine("Versione: " & Name.Version.ToString) 12. Console.Write("Public Key Token: ") 13. For Each B As Byte In Name.GetPublicKeyToken 14. Console.Write(Hex(B)) 15. Next 16. Console.WriteLine() 17. Console.WriteLine() 18. Next 19. End Sub
  • 206. A45. La Reflection - Parte II La c lasse Sy stem.Ty pe La classe Type è una classe davver o par ticolar e, poiché r appr esenta un tipo. Con tipo indichiamo tutte le possibili tipologie di dato esistenti: tipi base, enumer ator i, str uttur e, classi e delegate. Per ogni tipo contemplato, esiste un cor r ispettivo oggetto Type che lo r appr esenta: avevo detto all'inizio della guida, infatti, che ogni cosa in .NET è un oggetto, ed i tipi non fanno eccezione. Vi sor pr ender ebbe saper e tutto ciò che può esser e r appr esentato da una classe e fr a poco vi sveler ò un segr eto... Ma per or a concentr iamoci su Type. Questi oggetti r appr esentanti un tipo - che possiamo chiamar e per br evità OT (non è un ter mine tecnico) - vengono cr eati dur ante la fase di inizializzazione del pr ogr amma e ne esiste una e una sola copia per ogni singolo tipo all'inter no di un singolo AppDomain. Ciò significa che due contesti applicativi differ enti avr anno due OT diver si per r appr esentar e lo stesso tipo, ma non analizzer emo questa peculiar e casistica. Ci limiter emo, invece, a studiar e gli OT all'inter no di un solo dominio applicativo, coincidente con il nostr o pr ogr amma. Come per gli assembly, esistono molteplici modi per ottener e un OT: Tr amite l'oper ator e GetType(Tipo); Tr amite la funzione d'istanza GetType(); Tr amite la funzione condivisa Type.GetType("nometipo"). Ecco un semplice esempio di come funzionano questi metodi: 01. Module Module1 02. 03. Sub Main() 04. 'Ottiene un OT per il tipo double tramite 05. 'l'operatore GetType 06. Dim DoubleType As Type = GetType(Double) 07. Console.WriteLine(DoubleType.FullName) 08. 09. 'Ottiene un OT per il tipo string tramite 10. 'la funzione statica Type.GetType. Essa richiede 11. 'come parametro il nome (possibilmente completo) 12. 'del tipo. Nel caso il nome non corrisponda a 13. 'nessun tipo, verrà restituito Nothing 14. Dim StringType As Type = Type.GetType("System.String") 15. Console.WriteLine(StringType.FullName) 16. 17. 'Ottiene un OT per il tipo ArrayList tramite 18. 'la funzione d'istanza GetType. Da notare che, 19. 'mentre le precedenti usavano come punto 20. 'di partenza direttamente un tipo (o il suo nome), 21. 'questa richiede un oggetto di quel tipo. 22. Dim A As New ArrayList 23. Dim ArrayListType As Type = A.GetType() 24. Console.WriteLine(ArrayListType.FullName) 25. 26. Console.ReadKey() 27. End Sub 28. 29. End Module Or a che ho esemplificato come ottener e un OT, vor r ei mostr ar e l'unicità di OT ottenuti in modi differ enti: anche se usassimo tutti i tr e metodi sopr a menzionati per ottener e un OT per il tipo Str ing, otter r emo un r ifer imento allo stesso oggetto, poiché il tipo Str ing è unico: 01. Module Module1 02. 03. Sub Main() 04.
  • 207. Dim Type1 As Type = GetType(String) 05. Dim Type2 As Type = Type.GetType("System.String") 06. Dim Type3 As Type = "Ciao".GetType() 07. 08. Console.WriteLine(Type1 Is Type2) 09. '> True 10. Console.WriteLine(Type2 Is Type3) 11. '> True 12. 13. 'Gli OT contenuti in Type1, Type2 e Type3 14. 'SONO lo stesso oggetto 15. 16. Console.ReadKey() 17. End Sub 18. 19. End Module Questo non vale per il tipo System.Type stesso, poiché il metodo d'istanza GetType r estituisce un oggetto RuntimeType. Questi dettagli, tuttavia, non vi inter esser anno se non tr a un bel po' di tempo, quindi possiamo anche evitar e di soffer mar ci e pr oceder e con la spiegazione. Ogni oggetto Type espone una quantità inimmaginabile di membr i e penso che potr ebbe esser e la classe più ampia di tutto il Fr amew or k. Di questa massa enor me di infor mazioni, ve ne è un sottoinsieme che per mette di saper e in che modo il tipo è stato dichiar ato e quali sono le sue car atter istiche pr incipali. Possiamo r icavar e, ad esempio, gli specificator i di accesso, gli eventuali modificator i, possiamo saper e se si tr atta di una classe, un enumer ator e, una str uttur a o altr o, e, nel pr imo caso, se è astr atta o sigillata; possiamo saper e le sua classe base, le inter facce che implementa, se si tr atta di un ar r ay o no, ecceter a... Di seguito elenco i membr i di questo sottoinsieme: Assembly : r estituisce l'assembly a cui il tipo appar tiene (ossia in cui è stato dichiar ato); AssemblyQualifiedName : r estituisce il nome dell'assembly a cui il tipo appar tiene; BaseType : se il tipo cor r ente er edita da una classe base, questa pr opr ietà r estituisce un oggetto Type in r ifer imento a tale classe; Declar ingMethod : se il tipo cor r ente è par ametr o di un metodo, questa pr opr ietà r estituisce un oggetto MethodBase che r appr esenta tale metodo; Declar ingType : se il tipo cor r ente è membr o di una classe, questa pr opr ietà r estituisce un oggetto Type che r appr esenta tale classe; questa pr opr ietà viene valor izzata, quindi, solo se il tipo è stato dichiar ato all'inter no di un altr o tipo (ad esempio classi nidificate); FullName : il nome completo del tipo cor r ente; IsAbstr act : deter mina se il tipo è una classe astr atta; IsAr r ay : deter mina se è un ar r ay; IsClass : deter mina se è una classe; IsEnum : deter mina se è un enumer ator e; IsInter face : deter mina se è un'inter faccia; IsNested : deter mina se il tipo è nidificato: questo significa che r appr esenta un membr o di classe o di str uttur a; di conseguenza tutte le pr opr ietà il cui nome inizia per "IsNested" ser vono a deter minar e l'ambito di visibilità del membr o, e quindi il suo specificator e di accesso; IsNestedAssembly : deter mina se il membr o è Fr iend; IsNestedFamily : deter mina se il membr o è Pr otected; IsNestedFamORAssem : deter mina se il membr o è Pr otected Fr iend; IsNestedPr ivate : deter mina se il membr o è Pr ivate; IsNestedPublic : deter mina se il membr o è Public; IsNotPublic : deter mina se il tipo non è Public (solo per tipi non nidificati). Vi r icor do, infatti, che all'inter no di un namespace, gli unici specificator i possibili sono Public e Fr iend (gli altr i si adottano solo all'inter no di una classe); IsPointer : deter mina se è un puntator e;
  • 208. IsPr imitive : deter mina se è uno dei tipi pr imitivi; IsPublic : deter mina se il tipo è Public (solo per tipi non nidificati); IsSealed : deter mina se è una classe sigillata; IsValueType : deter mina se è un tipo value; Name : il nome del tipo cor r ente; Namespace : il namespace in cui è contenuto il tipo cor r ente. Con questa abbondante manciata di pr opr ietà possiamo iniziar e a scr iver e un metodo di analisi un po' più appr ofondito. Nella fattispecie, la pr ossima pr ocedur a Enumer ateTypes accetta come par ametr o il r ifer imento ad un assembly e scr ive a scher mo tutti i tipi ivi definiti: 01. Module Module1 02. 03. Sub EnumerateTypes(ByVal Asm As Assembly) 04. Dim Category As String 05. 06. 'GetTypes restituisce un array di Type che 07. 'indicano tutti i tipi definiti all'interno 08. 'dell'assembly Asm 09. For Each T As Type In Asm.GetTypes 10. If T.IsClass Then 11. Category = "Class" 12. ElseIf T.IsInterface Then 13. Category = "Interface" 14. ElseIf T.IsEnum Then 15. Category = "Enumerator" 16. ElseIf T.IsValueType Then 17. Category = "Structure" 18. ElseIf T.IsPrimitive Then 19. Category = "Base Type" 20. End If 21. Console.WriteLine("{0} ({1})", T.Name, Category) 22. Next 23. End Sub 24. 25. Sub Main() 26. 'Ottiene un riferimento all'assembly in esecuzione, 27. 'quindi al programma. Non otterrete molti tipi 28. 'usando questo codice, a meno che il resto del 29. 'modulo non sia pieno di codice vario come nel 30. 'mio caso XD 31. Dim Asm As Assembly = Assembly.GetExecutingAssembly() 32. 33. EnumerateTypes(Asm) 34. 35. Console.ReadKey() 36. End Sub 37. 38. End Module Il nostro pic c olo segreto Pr ima di pr oceder e con l'enumer azione dei membr i, vor r ei mostr ar e che in r ealtà tutti i tipi sono classi, soltanto con r egole "speciali" di er editar ietà e di sintassi. Questo codice r intr accia tutte le classi basi di un tipo, costr uendone l'alber o di er editar ietà fino alla r adice (che sar à ovviamente System.Object): 01. Module Module1 02. 03. 'Analizza l'albero di ereditarietà di un tipo 04. Sub AnalyzeInheritance(ByVal T As Type) 05. 'La proprietà BaseType restituisce la classe 06. 'base da cui T è derivata 07. If T.BaseType IsNot Nothing Then 08. Console.WriteLine("> " & T.BaseType.FullName) 09.
  • 209. AnalyzeInheritance(T.BaseType) 10. End If 11. End Sub 12. 13. Enum Status 14. Enabled 15. Disabled 16. Standby 17. End Enum 18. 19. Structure Example 20. Dim A As Int32 21. End Structure 22. 23. Delegate Sub Sample() 24. 25. Sub Main() 26. Console.WriteLine("Integer:") 27. AnalyzeInheritance(GetType(Integer)) 28. Console.WriteLine() 29. 30. Console.WriteLine("Enum Status:") 31. AnalyzeInheritance(GetType(Status)) 32. Console.WriteLine() 33. 34. Console.WriteLine("Structure Example:") 35. AnalyzeInheritance(GetType(Example)) 36. Console.WriteLine() 37. 38. Console.WriteLine("Delegate Sample:") 39. AnalyzeInheritance(GetType(Sample)) 40. Console.WriteLine() 41. 42. Console.ReadKey() 43. End Sub 44. 45. End Module L'output mostr a che il tipo Integer e la str uttur a Ex ample der ivano entr ambi da System.ValueType, che a sua volta der iva da Object. La definizione r igor osa di "tipo value", quindi, sar ebbe "qualsiasi tipo der ivato da System.ValueType". Infatti, al par i dei pr imi due, anche l'enumer ator e der iva indir ettamente da tale classe, anche se mostr a un passaggio in più, attr aver so il tipo System.Enum. Allo stesso modo, il delegate Sample der iva dalla classe DelegateMulticast, la quale der ivata da Delegate, la quale der iva da Object. La differ enza sostanziale tr a tipi value e r efer ence, quindi, r isiede nel fatto che i pr imi hanno almeno un passaggio di er editar ietà attr aver so la classe System.ValueType, mentr e i secondi der ivano dir ettamente da Object. System.Enum e System.Delegate sono classi astr atte che espongono utili metodi statici che potete ispezionar e da soli (sono pochi e di facile compr ensione). Ma or a che sapete che tutti i tipi sono classi, potete anche esplor ar e i membr i esposti dai tipi base. Enumerare i membri Fino ad or a abbiamo visto solo come analizzar e i tipi, ma ogni tipo possiede anche dei membr i (var iabili, metodi, pr opr ietà, eventi, ecceter a...). La Reflection per mette anche di ottener e infor mazioni sui membr i di un tipo, e la classe in cui queste infor mazioni vengono poste è Member Info, del namespace System.Reflection. Dato che ci sono diver se categor ie di membr i, esistono altr ettante classi der ivate da Member Info che ci r accontano una stor ia tutta diver sa a seconda di cosa stiamo guar dando: MethodInfo contiene infor mazioni su un metodo, Pr oper tyInfo su una pr opr ietà, Par amter Info su un par ametr o, FieldInfo su un campo e via dicendo. Fr a le molteplici funzioni esposte da Type, ce ne sono alcune che ser vono pr opr io a r eper ir e questi dati; eccone un elenco: GetConstr uctor s() : r estituisce un ar r ay di Constr uctor Info, ognuno dei quali r appr esenta uno dei costr uttor i definiti per quel tipo;
  • 210. GetEvents() : r estituisce un ar r ay di EventInfo, ognuno dei quali r appr esenta uno degli eventi dichiar ati in quel tipo; GetFields() : r estituisce un ar r ay di FieldInfo, ognuno dei quali r appr esenta uno dei campi dichiar ati in quel tipo; GetInter faces() : r estituisce un ar r ay di Type, ognuno dei quali r appr esenta una delle inter facce implementate da quel tipo; GetMember s() : r estituisce un ar r ay di Member Info, ognuno dei quali r appr esenta uno dei membr i dichiar ati in quel tipo; GetMethods() : r estituisce un ar r ay di MethodInfo, ognuno dei quali r appr esenta uno dei metodi dichiar ati in quel tipo; GetNestedTypes() : r estituisce un ar r ay di Type, ognuno dei quali r appr esenta uno dei tipi dichiar ati in quel tipo; GetPr oper ties() : r estituisce un ar r ay di Pr oper tyInfo, ognuno dei quali r appr esenta una delle pr opr ietà dichiar ate in quel tipo; La funzione GetMember s, da sola, ci for nisce una lista gener ale di tutti i membr i di quel tipo: 01. Module Module1 02. Sub Main() 03. Dim T As Type = GetType(String) 04. 05. 'Elenca tutti i membri di String 06. For Each M As MemberInfo In T.GetMembers 07. 'La proprietà MemberType restituisce un enumeratore che 08. 'specifica di che tipo sia il membro, se una proprietà, 09. 'un metodo, un costruttore, eccetera... 10. Console.WriteLine(M.MemberType.ToString & " " & M.Name) 11. Next 12. 13. Console.ReadKey() 14. End Sub 15. End Module Eseguendo il codice appena pr oposto, potr ete notar e che a scher mo appaiono tutti i membr i di Str ing, ma molti sono r ipetuti: questo si ver ifica per chè i metodi che possiedono delle var ianti in over load vengono r ipor tati tante volte quante sono le var ianti; natur alemnte, ogni oggetto MethodInfo sar à diver so dagli altr i per le infor mazioni sulla quantità e sul tipo di par ametr i passati a tale metodo. Accanto a questa str anezza, noter ete, poi, che per ogni pr opr ietà ci sono due metodi definiti come get_NomePr opr ietà e set_NomePr opr ietà: questi metodi vengono cr eati automaticamente quando il codice di una pr opr ietà viene compilato, e vengono eseguiti al momento di impostar e od ottener e il valor e di tale pr opr ietà. Altr a str anezza è che tutti i costr uttor i si chiamano ".ctor " e non New . Stiamo cominciando ad entr ar e nel mondo dell'Inter mediate Language, il linguaggio inter medio simil-macchina in cui vengono conver titi i sor genti una volta compilati. Di fatto, noi stiamo eseguendo il pr ocesso inver so della compilazione, ossia la deco m pilazio ne. Alcune infor mazioni vengono manipolate nel passaggio da codice a IL, e quando si tor na indietr o, le si vede in altr o modo, ma tutta l'infor mazione necessar ia è ancor a contenuta lì dentr o. Non esiste, tuttavia, una classe già scr itta che r itr aduca in codice tutto il linguaggio inter medio: ciò che il Fr amew or k ci for nisce ci consente solo di conoscer e "a pezzi" tutta l'infor mazione ivi contenuta, ma sottolineiamo "tutta". Sar ebbe, quindi, possibile - ed infatti è già stato fatto - r itr adur r e tutti questi dati in codice sor gente. Per or a, ci limiter emo a "r icostr uir e" la signatur e di un metodo. Pr ima di pr oceder e, vi for nisco un br eve elenco dei membr i significativi di ogni der ivato di Member Info: M em ber Info Declar ingType : la classe che dichiar a questo membr o; Member Type : categor ia del membr o; Name : il nome del membr o;
  • 211. ReflectedType : il tipo usato per ottener e un r ifer imento a questo membr o tr amite r eflection; M etho dInfo GetBaseDefinition() : se il metodo è modificato tr amite polimor fismo, r estituisce la ver sione della classe base (se non è stato sottoposto a polimor fismo, r estituisce Nothing); GetCur r entMethod() : r estituisce un MethodInfo in r ifer imento al metodo in cui questa funzione viene chiamata; GetMethodBody() : r estituisce un oggetto MethodBody (che vedr emo in seguito) contenente infor mazioni sulle var iabili locali, le eccezioni e il codice IL; GetPar ameter s() : r estituisce un elenco di Par ameter Info r appr esentanti i par ametr i del metodo; IsAbstr act : deter mina se il metodo è MustOver r ide; IsConstr uctor : deter mina se è un costr uttor e; IsFinal : deter mina se è NotOver r idable; IsStatic : deter mina se è Shar ed; IsVir tual : deter mina se è Over r idable; Retur nPar ameter : qualor a il metodo fosse una funzione, r estituisce infor mazioni sul valor e r estituito; Retur nType : in una funzione, r estituisce l'oggetto Type associato al tipo r estituito. Se il metodo non è una funzione, r estituisce Nothing o uno speciale OT in r ifer imento al tipo System.Void. FieldInfo GetRaw CostantValue() : se il campo è una costante, ne r estituisce il valor e; IsLiter al : deter mina se è una costante; IsInitOnly : deter mina se è una var iabile r eadonly; Pr o per tyInfo CanRead : deter mina se si può legger e la pr opr ietà; CanWr ite : deter mina se si può impostar e la pr opr ietà; GetGetMethod() : r estituisce un MethodInfo cor r ispondente al blocco Get; GetSetMethod() : r estituisce un MethodInfo cor r ispondente al blocco Set; GetPr oper tyType() : r estituisce un oggetto Type in r ifer imento al tipo della pr opr ietà. Ev entInfo (per ulter ior i infor mazioni, veder e i capitoli della sezione B sugli eventi) GetAddMethod() : r estituisce un r ifer imento al metodo usato per aggiunger e gli handler d'evento; GetRaiseMethod() : r estituisce un r ifer imento al metodo che viene r ichiamato quando si scatena l'evento; GetRemoveMethod() : r estituisce un r ifer imento al metodo usato per r imuover e gli handler d'evento; IsMulticast : indica se l'evento è gestito tr amite un delegate multicast. 001. Module Module1 002. 'Analizza il metodo rappresentato dall'oggetto MI 003. Sub AnalyzeMethod(ByVal MI As MethodInfo) 004. 'Il nome 005. Dim Name As String 006. 'Il nome completo, con scpecificatori di accesso, 007. 'modificatori, signature e tipo restituito. Per 008. 'ulteriori informazioni sul tipo StringBuilder, 009. 'vedere il capitolo "Magie con le stringhe" 010.
  • 212. Dim CompleteName As New System.Text.StringBuilder 011. 'Lo specificatore di accesso 012. Dim Scope As String 013. 'Gli eventuali modificatori 014. Dim Modifier As String 015. 'La categoria: Sub o Function 016. Dim Category As String 017. 'La signature del metodo, che andremo a costruire 018. Dim Signature As New System.Text.StringBuilder 019. 020. 'Di solito, tutti i metodi hanno un tipo restituito, 021. 'poiché, in analogia con la sintassi del C#, una 022. 'procedura è una funzione che restituisce Void, 023. 'ossia niente. Per questo bisogna controllare anche il 024. 'nome del tipo di ReturnParameter 025. If MI.ReturnParameter IsNot Nothing AndAlso _ 026. MI.ReturnType.FullName <> "System.Void" Then 027. Category = "Function" 028. Else 029. Category = "Sub" 030. End If 031. 032. If MI.IsConstructor Then 033. Name = "New" 034. Else 035. Name = MI.Name 036. End If 037. 038. If MI.IsAssembly Then 039. Scope = "Friend" 040. ElseIf MI.IsFamily Then 041. Scope = "Protected" 042. ElseIf MI.IsFamilyOrAssembly Then 043. Scope = "Protected Friend" 044. ElseIf MI.IsPrivate Then 045. Scope = "Private" 046. Else 047. Scope = "Public" 048. End If 049. 050. If MI.IsFinal Then 051. 'Vorrei far notare una sottigliezza. Se il metodo è 052. 'Final, ossia NotOverridable, significa che non può 053. 'essere modificato nelle classi derivate. Ma tutti i 054. 'membri non dichiarati esplicitamente Overridable 055. 'non sono modificabili nelle classi derivate. Quindi, 056. 'definire un metodo senza modificatori polimorfici 057. '(come quelli che seguono qua in basso), equivale a 058. 'definirlo NotOverridable. Perciò non si 059. 'aggiunge nessun modificatore in questo caso 060. ElseIf MI.IsAbstract Then 061. Modifier = "MustOverride" 062. ElseIf MI.IsVirtual Then 063. Modifier = "Overridable" 064. ElseIf MI.GetBaseDefinition IsNot Nothing AndAlso _ 065. MI IsNot MI.GetBaseDefinition Then 066. Modifier = "Overrides" 067. End If 068. 069. If MI.IsStatic Then 070. If Modifier <> "" Then 071. Modifier = "Shared " & Modifier 072. Else 073. Modifier = "Shared" 074. End If 075. End If 076. 077. 'Inizia la signature con una parentesi tonda aperta. 078. 'Append aggiunge una stringa a Signature 079. Signature.Append("(") 080. For Each P As ParameterInfo In MI.GetParameters 081. 'Se P è un parametro successivo al primo, lo separa dal 082.
  • 213. 'precedente con una virgola 083. If P.Position > 0 Then 084. Signature.Append(", ") 085. End If 086. 087. 'Se P è passato per valore, ci vuole ByVal, altrimenti 088. 'ByRef. IsByRef è un membro di Type, ma viene 089. 'usato solo quando il tipo in questione indica il tipo 090. 'di un parametro 091. If P.ParameterType.IsByRef Then 092. Signature.Append("ByRef ") 093. Else 094. Signature.Append("ByVal ") 095. End If 096. 097. 'Se P è opzionale, ci vuole la keyword Optional 098. If P.IsOptional Then 099. Signature.Append("Optional ") 100. End If 101. 102. 103. Signature.Append(P.Name) 104. If P.ParameterType.IsArray Then 105. Signature.Append("()") 106. End If 107. 'Dato che la sintassi del nome è in stile C#, al 108. 'posto delle parentesi tonde in un array ci sono delle 109. 'quadre: rimediamo 110. Signature.Append(" As " & P.ParameterType.Name.Replace("[]","")) 111. 112. 'Si ricordi che i parametri optional hanno un valore 113. 'di default 114. If P.IsOptional Then 115. Signature.Append(" = " & P.DefaultValue.ToString) 116. End If 117. Next 118. Signature.Append(")") 119. 120. If MI.ReturnParameter IsNot Nothing AndAlso _ 121. MI.ReturnType.FullName <> "System.Void" Then 122. Signature.Append(" As " & MI.ReturnType.Name) 123. End If 124. 125. 'Ed ecco il nome completo 126. CompleteName.AppendFormat("{0} {1} {2} {3}{4}", Scope, Modifier, _ 127. Category, Name, Signature.ToString) 128. Console.WriteLine(CompleteName.ToString) 129. Console.WriteLine() 130. 131. 'Ora ci occupiamo del corpo 132. Dim MB As MethodBody = MI.GetMethodBody 133. 134. If MB Is Nothing Then 135. Exit Sub 136. End If 137. 138. 'Massima memoria occupata sullo stack 139. Console.WriteLine("Massima memoria stack : {0} bytes", _ 140. MB.MaxStackSize) 141. Console.WriteLine() 142. 143. 'Variabili locali (LocalVariableInfo è una variante di 144. 'FieldInfo) 145. Console.WriteLine("Variabili locali:") 146. For Each L As LocalVariableInfo In MB.LocalVariables 147. 'Dato che non si può ottenere il nome, ci si deve 148. 'accontentare di un indice 149. Console.WriteLine(" Var({0}) As {1}", L.LocalIndex, _ 150. L.LocalType.Name) 151. Next 152. Console.WriteLine() 153. 154.
  • 214. 'Gestione delle eccezioni 155. Console.WriteLine("Gestori di eccezioni:") 156. For Each Ex As ExceptionHandlingClause In MB.ExceptionHandlingClauses 157. 'Tipo di clausola: distingue tra filtro (When), 158. 'clausola (Catch) o un blocco Finally 159. Console.WriteLine(" Tipo : {0}", Ex.Flags.ToString) 160. 'Se si tratta di un blocco Catch, ne specifica la 161. 'natura 162. If Ex.Flags = ExceptionHandlingClauseOptions.Clause Then 163. Console.WriteLine(" Catch Ex As " & Ex.CatchType.Name) 164. End If 165. 'Offset, ossia posizione in bytes nel Try, del gestore 166. Console.WriteLine(" Offset : {0}", Ex.HandlerOffset) 167. 'Lunghezza, in bytes, del codice eseguibile del gestore 168. Console.WriteLine(" Lunghezza : {0}", Ex.HandlerLength) 169. Console.WriteLine() 170. Next 171. End Sub 172. 173. Sub Test(ByVal Num As Int32, ByVal S As String) 174. Dim T As Date 175. Dim V As String 176. 177. Try 178. Console.WriteLine("Prova 1, 2, 3") 179. Catch Ex As ArithmeticException 180. Console.WriteLine("Errore 1") 181. Catch Ex As ArgumentException 182. Console.WriteLine("Errore 2") 183. Finally 184. Console.WriteLine("Ciao") 185. End Try 186. End Sub 187. 188. Sub Main() 189. Dim T As Type = GetType(Module1) 190. Dim Methods() As MethodInfo = T.GetMethods 191. Dim Index As Int16 192. 193. Console.WriteLine("Inserire un numero tra i seguenti per analizzare il metodo corrispondente:") 194. Console.WriteLine() 195. For I As Int16 = 0 To Methods.Length - 1 196. Console.WriteLine("{0} - {1}", I, Methods(I).Name) 197. Next 198. Console.WriteLine() 199. Index = Console.ReadLine 200. 201. If Index >= 0 And Index &rt; Methods.Length Then 202. AnalyzeMethod(Methods(Index)) 203. End If 204. 205. Console.ReadKey() 206. End Sub 207. 208. End Module Analizzando il metodo Test, si otter r à questo output: Public Shared Sub Test(ByVal Num As Int32, ByVal S As String) Massima memoria stack : 2 bytes Variabili locali: Var(0) As DateTime Var(1) As String Var(2) As ArithmeticException Var(3) As ArgumentException
  • 215. Gestori di eccezioni: Tipo : Clause Catch Ex As ArithmeticException Offset : 15 Lunghezza : 26 Tipo : Clause Catch Ex As ArgumentException Offset : 41 Lunghezza : 26 Tipo : Finally Offset : 67 Lunghezza : 13
  • 216. A46. La Reflection - Parte III Reflec tion dei generic s I gener ics si compor tano in modo differ ente in molti ambiti, e la Reflection r icade pr opr io fr a questi. Infatti, un Type che r appr esenta un tipo gener ic non ha lo stesso nome di quando è stato dichiar ato nel codice, ma possiede una for ma contr atta e diver sa. Ad esempio, ammettendo che l'assembly che stiamo analizzando contenga questa classe: 1. Class Example(Of T, K) 2. '... 3. End Class quando tr over emo l'oggetto Type che la r appr esenta dur ante l'enumer azione dei tipi, scopr ir emo che il suo nome è molto str ano. Sar à molto simile a questa str inga: Example`2 In questa par ticolar e for mattazione, il due indica che la classe ex ample lavor a su due tipi gener ics: i lor o nome "vir tuali" non vengono r ipor tati nel nome, cosicché anche confr ontando i nomi di due OT indicanti tipi gener ics, magar i pr ovenienti da AppDomain diver si, si capisce che in r ealtà sono pr opr io lo stesso tipo, poiché la ver a differ enza sta solo nel nome e nella quantità di par ametr i gener ics (l'identificator e di questi ultimi, infatti, essendo solo un segnaposto, è ininfluente). Nonostante l'assenza di dettagli, ci sono delle pr opr ietà che ci per mettono di r ecuper ar e il nome dei tipi gener ics aper ti, ossia "T" e "K" in questo caso. In gener ale, per lavor ar e su classi o tipi genr ics, è impor tante far e affidamento su questi membr i di Type: IsGener icTypeDefinition : deter mina se questo Type r appr esenta una definizione di un tipo gener ics. Fate attenzione ai dettagli, poiché esiste un'altr a pr opr ietà molto simile con la quale ci si può confonder e. Affinché questa pr oper ietà r estituisca Tr ue è necessar io (e sufficiente) che il tipo che stiamo esaminando contenga una definizione di uno o più tipi gener ics APERTI (e n on collegati). Ad esempio: 01. Module Module1 02. 03. 'Dichiaro questa classe e la prossima variabile come 04. 'pubblici perchè se fossero Friend bisognerebbe 05. 'usare un overload troppo lungo di GetField e 06. 'GetNestedTypes specificando ci cercare i membri non 07. 'pubblici. Di default, le funzioni di ricerca operano 08. 'solo su membri pubblici 09. 10. Public Class Example(Of T) 11. 12. End Class 13. 14. Public E As Example(Of Int32) 15. 16. Sub Main() 17. 'Ottiene il tipo di questo modulo 18. Dim ModuleType As Type = GetType(Module1) 19. 20. 'Enumera tutti i tipi presenti nel modulo fino a 21. 'trovare la classe Example. Ho usato un for perchè 22. 'non si può usare GetType (in qualsiasi 23. 'sua versione) su una classe generics senza specificare 24. 'un tipo generics collegato, cosa che noi non 25. 'vogliamo affatto. Per ottenere il riferimento a 26. 'Example(Of T) bisogna per forza usare una funzione 27. 'che restituisca tutti i tipi esistenti e poi 28. 'cercarlo tra questi. 29. For Each T As Type In ModuleType.GetNestedTypes() 30.
  • 217. If T.Name.StartsWith("Example") Then 31. Console.WriteLine("{0} - IsGenericTypeDefinition: {1}", _ 32. T.Name, T.IsGenericTypeDefinition) 33. End If 34. Next 35. 36. 'Ottiene un riferimento al campo E dichiarayo sopra 37. Dim EField As FieldInfo = ModuleType.GetField("E") 38. 'E ne ottiene il tipo 39. Dim EType As Type = EField.FieldType 40. 41. Console.WriteLine("{0} - IsGenericTypeDefinition: {1}", EType.Name, EType.IsGenericTypeDefinition) 42. 43. Console.ReadKey() 44. End Sub 45. 46. End Module A scher mo appar ir à lo stesso nome due volte, ma in un caso IsGener icTypeDefinition sar à Tr ue e nell'altr o False. Questo per chè il tipo della var iabile E è sì dichiar ato come gener ic, ma all'atto pr atico lavor a su un solo tipo: Int32; per ciò non si tr atta di una defin izion e di tipo gener ic, ma di un uso di un tipo gener ic; IsGener icType : molto simile alla pr ecedente, ma funziona al contr ar io, ossia r estituisce Tr ue se il tipo NON è una definizione di tipo gener ic, ma una sua applicazione mediante tipi collegati. Nell'esempio di pr ima, EType.IsGener icType sar ebbe stato Tr ue; GetGener icAr guments() : se almeno uno tr a IsGener icTypeDefinition e IsGener icType è ver o, allor a abbiamo a che far e con tipi gener ics. Questa funzione r estituisce gli OT dei tipi gener ics aper ti (nel pr imo caso) o collegati (nel secondo caso). Tr a br eve ne vedr emo un esempio. Ecco un esempio di come enumer ar e tutti i tipi gener ics di un assembly: 01. Module Module1 02. 03. Sub EnumerateGenerics(ByVal Asm As Assembly) 04. For Each T As Type In Asm.GetTypes 05. 'Controlla se si tratta di un tipo contenente 06. 'tipi generics aperti 07. If T.IsGenericTypeDefinition Then 08. 'Ottiene il nome semplice di quel tipo (la 09. 'versione completa è troppo lunga XD) 10. Dim Name As String = T.Name 11. 12. 'Controlla che il nome contenga l'accento tonico. 13. 'Infatti, possono esistere casi in cui la 14. 'propietà IsGeneircTypeDefinition è vera, 15. 'ma non ci troviamo di fronte a un tipo la cui 16. 'signature contenga effettivamente tipi generics. 17. 'Ne darò un esempio dopo... 18. If Not Name.Contains("`") Then 19. Continue For 20. End If 21. 22. 'Ottiene una stringa in cui elimina tutti i 23. 'caratteri a partire dall'indice del'accento 24. Name = T.Name.Remove(T.Name.IndexOf("`")) 25. 'E poi gli aggiunge un "(Of ", per far vedere che 26. 'si sta iniziando una dichiarazione generic 27. Name &= "(Of " 28. 'Quindi aggiunge tutti gli argomenti generic 29. For Each GenT As Type In T.GetGenericArguments 30. 'Se il parametro non è il primo, lo separa dal 31. 'precedente con una virgola. 32. If GenT.GenericParameterPosition > 0 Then 33. Name &= ", " 34. End If 35. 'Quindi vi aggiunge il nome 36. Name &= GenT.Name 37.
  • 218. Next 38. 'E chiude la parentesi 39. Name &= ")" 40. Console.WriteLine(Name) 41. End If 42. Next 43. End Sub 44. 45. 'Notate che la classe Type espone molte proprietà che 46. 'si possono usare solo in determinati casi. Ad esempio, in 47. 'questo codice è lecito richiamare GenericParametrPosition 48. 'poiché sappiamo a priori che quel Type indica un tipo 49. 'generic in una signature generic. Ma un in un qualsiasi OT 50. 'non ha alcun senso usare tale proprietà! 51. 52. Sub Main() 53. 'Ottiene un riferimento all'assembly corrente 54. Dim Asm As Assembly = Assembly.GetExecutingAssembly() 55. 56. EnumerateGenerics(Asm) 57. 58. Console.ReadKey() 59. End Sub 60. 61. End Module Ecco alcuni dei miei r isultati: ThreadSafeObjectProvider(Of T) Collection(Of T) ComparableCollection(Of T) Relation(Of T1, T2) IsARelation(Of T, U) DataFilter(Of T) Riguar do all'if posto nel ciclo enumer ativo, vor r ei far notar e che IsGener icTypeDefinition r estituisce tr ue se r intr accia nel tipo un r ifer imento ad un tipo gener ic aper to, indipendentemente che questo sia dichiar ato nel tipo o da un'altr a par te. Ad esempio: 1. Class Example(Of T) 2. Delegate Sub DoSomething(ByVal Data As T) 3. '... 4. End Class L'enumer azione r aggiunge anche DoSomething, poiché è anch'esso un tipo, anche se nidificato, accessibile a tutti i membr i dell'assembly (o, se pubblico, a tutti); ed anche in quel caso, la pr opr ietà IsGener icTypeDefinition è Tr ue, poiché la sua signatur e contiene un tipo gener ic aper to (T). Tuttavia, il suo nome non contiene accenti tonici, poiché il gener ics è stato dichiar ato a livello di classe. Ecco un altr o esempio, ma sui tipi gener ic collegati: 01. Module Module1 02. 03. 'Enumera solo i campi generic di un tipo 04. Sub EnumerateGenericFieldMembers(ByVal T As Type) 05. For Each F As FieldInfo In T.GetFields() 06. If F.FieldType.IsGenericType Then 07. Dim Name As String = F.FieldType.Name 08. Dim I As Int16 = 0 09. 10. If Not Name.Contains("`") Then 11. Continue For 12. End If 13. 14. Name = Name.Remove(Name.IndexOf("`")) 15. Name &= "(Of " 16. For Each GenP As Type In F.FieldType.GetGenericArguments 17.
  • 219. 'Dato che non si stanno analizzando dei 18. 'parametri generic, non si può utilizzare 19. 'la proprietà GenericParameterPosition 20. If I > 0 Then 21. Name &= ", " 22. End If 23. Name &= GenP.Name 24. I += 1 25. Next 26. Name &= ")" 27. Console.WriteLine("Dim {0} As {1}", F.Name, Name) 28. End If 29. Next 30. End Sub 31. 32. Public L As New List(Of Integer) 33. Public I As Int32? 34. 35. Sub Main() 36. EnumerateGenericFieldMembers(GetType(Module1)) 37. 38. Console.ReadKey() 39. End Sub 40. End Module L'uso della Reflec tion Fino ad or a non abbiamo fatto altr o che enumer ar e membr i e tipi. Devo dir lo, una cosa un po' noiosa... Tuttavia ci è ser vita per compr ender e come far e per acceder e a cer te infor mazioni che si celano negli assembly. Anche se non user emo quasi mai la r eflection per enumer ar e le par ti di un assembly (a meno che non decidiate di scr iver e un object br ow ser ), or a sappiamo quali infor mazioni possiamo r aggiunger e e come pr ender le. Questo è impor tante sopr attutto quando si lavor a con assembly che vengono car icati dinamicamente, ad esempio in un sistema di plug-ins, come mostr er ò fr a poco. Per dar vi un assaggio della potenza della r eflection, ho scr itto un semplice codice che per mette di acceder e a tutte le infor mazioni di un oggetto, qualsiasi esso sia, di qualunque tipo e in qualunque assembly. Per far lo, mi è bastato ottener ne le pr opr ietà: 01. Module Module1 02. 03. 'Stampa tutte le informazioni ricavabili dalle 04. 'proprietà di un dato oggetto O. Indent è solo 05. 'una variabile d'appoggio per la formattazione, in modo 06. 'da indentare bene le righe nel caso i valori delle 07. 'proprietà siano altri oggetti. 08. Public Sub PrintInfo(ByVal O As Object, ByVal Indent As String) 09. 'Ottiene il tipo di O 10. Dim T As Type = O.GetType() 11. 12. Console.WriteLine("{0}Object of type {1}", Indent, T.Name) 13. 'Enumera tutte le proprietà 14. For Each Prop As PropertyInfo In T.GetProperties() 15. 'Ottiene il tipo restituito dalla proprietà 16. Dim PropType As Type = Prop.PropertyType() 17. 18. 'Se si tratta di una proprietà parametrica, 19. 'la salta: in questo esempio non volevo dilungarmi, 20. 'ma potete completare il codice se desiderate. 21. If Prop.GetIndexParameters().Count > 0 Then 22. Continue For 23. End If 24. 25. 'Se è un di tipo base o una stringa (giacché le 26. 'stringhe non sono tipo base ma reference), ne stampa 27. 'direttamente il valore a schermo 28. If (PropType.IsPrimitive) Or (PropType Is GetType(String)) Then 29. Console.WriteLine("{0} {1} = {2}", _ 30.
  • 220. Indent, Prop.Name, Prop.GetValue(O, Nothing)) 31. 32. 'Altrimenti, se si tratta di un oggetto, lo analizza a 33. 'sua volta 34. ElseIf PropType.IsClass Then 35. Console.WriteLine("{0} {1} = ", Indent, Prop.Name) 36. PrintInfo(Prop.GetValue(O, Nothing), Indent & " ") 37. End If 38. Next 39. Console.WriteLine() 40. End Sub 41. 42. Sub Main() 43. 'Crea alcuni oggetti vari 44. Dim P As New Person("Mario", "Rossi", New Date(1982, 3, 17)) 45. Dim T As New Teacher("Luigi", "Bianchi", New Date(1879, 8, 21), "Storia") 46. Dim R As New Relation(Of Person, Teacher)(P, T) 47. Dim Q As New List(Of Int32) 48. Dim K As New Text.StringBuilder() 49. 50. 'Ne stampa le proprietà, senza sapere nulla a priori 51. 'sulla natura degli oggetti. 52. 'Notate che i nomi generics rimangono con l'accento... 53. PrintInfo(P, "") 54. PrintInfo(T, "") 55. PrintInfo(R, "") 56. PrintInfo(Q, "") 57. PrintInfo(K, "") 58. 59. Console.ReadKey() 60. End Sub 61. End Module L'output sar à questo: Object of type Person FirstName = Mario LastName = Rossi CompleteName = Mario Rossi Object of type Teacher Subject = Storia LastName = Prof. Bianchi CompleteName = Prof. Luigi Bianchi, dottore in Storia FirstName = Luigi Object of type Relation`2 FirstObject = Object of type Person FirstName = Mario LastName = Rossi CompleteName = Mario Rossi SecondObject = Object of type Teacher Subject = Storia LastName = Prof. Bianchi CompleteName = Prof. Luigi Bianchi, dottore in Storia FirstName = Luigi Object of type List`1 Capacity = 0 Count = 0
  • 221. Object of type StringBuilder Capacity = 16 MaxCapacity = 2147483647 Length = 0 Per scr iver e questo codice mi sono basato sul metodo GetValue esposto dalla classe Pr oper tyInfo. Esso per mette di ottener e il valor e che la pr opr ietà r appr esentata dall'oggetto Pr oper tyInfo da cui viene invocato possiede nell'oggetto specificato come par ametr o. In gener ale, GetValue accetta due par ametr i: il pr imo è l'oggetto da cui estr ar r e il valor e della pr opr ietà, mentr e il secondo è un ar r ay di oggetti che r appr esenta i par ametr i da passar e alla pr opr ietà. Come avete visto, ho enumer ato solo pr opr ietà non par ametr iche e per ciò non c'er a bisogno di for nir e alcun par ametr o: ecco per chè ho messo Nothing. Al par i di GetValue c'è SetValue che per mette di impostar e, invece, la pr opr ietà (ma solo se non è in sola lettur a, ossia se CanWr ite è Tr ue). Ovviamente SetValue ha un par ametr o in più, ossia il valor e da impostar e (secondo par ametr o). Ecco un esempio: 01. Module Module1 02. 03. 'Non riscrivo PrintInfo, ma considero che stia 04. 'ancora in questo modulo 05. 06. Sub Main() 07. Dim P As New Person("Mario", "Rossi", New Date(1982, 3, 17)) 08. Dim T As New Teacher("Luigi", "Bianchi", New Date(1879, 8, 21), "Storia") 09. Dim R As New Relation(Of Person, Teacher)(P, T) 10. Dim Q As New List(Of Int32) 11. Dim K As New Text.StringBuilder() 12. Dim Objects() As Object = {P, T, R, Q, K} 13. Dim Cmd As Int32 14. 15. Console.WriteLine("Oggetti nella collezione: ") 16. For I As Int32 = 0 To Objects.Length - 1 17. Console.WriteLine("{0} - Istanza di {1}", _ 18. I, Objects(I).GetType().Name) 19. Next 20. Console.WriteLine("Inserire il numero corrispondente all'oggetto da modificare: ") 21. Cmd = Console.ReadLine 22. 23. If Cmd < 0 Or Cmd > Objects.Length - 1 Then 24. Console.WriteLine("Nessun oggetto corrispondente!") 25. Exit Sub 26. End If 27. 28. Dim Selected As Object = Objects(Cmd) 29. Dim SelectedType As Type = Selected.GetType() 30. Dim Properties As New List(Of PropertyInfo) 31. 32. For Each Prop As PropertyInfo In SelectedType.GetProperties() 33. If (Prop.PropertyType.IsPrimitive Or Prop.PropertyType Is GetType(String)) And _ 34. Prop.CanWrite Then 35. Properties.Add(Prop) 36. End If 37. Next 38. 39. Console.Clear() 40. Console.WriteLine("Proprietà dell'oggetto:") 41. For I As Int32 = 0 To Properties.Count - 1 42. Console.WriteLine("{0} - {1}", _ 43. I, Properties(I).Name) 44. Next 45. Console.WriteLine("Inserire il numero corrispondente alla proprietà da modificare:") 46. Cmd = Console.ReadLine 47. 48. If Cmd < 0 Or Cmd > Objects.Length - 1 Then 49. Console.WriteLine("Nessuna proprietà corrispondente!") 50. Exit Sub 51.
  • 222. End If 52. 53. Dim SelectedProp As PropertyInfo = Properties(Cmd) 54. Dim NewValue As Object 55. 56. Console.Clear() 57. Console.WriteLine("Nuovo valore: ") 58. NewValue = Console.ReadLine 59. 60. 'Imposta il nuovo valore della proprietà. Noterete che 61. 'si ottiene un errore di cast con tutti i tipi che 62. 'non siano String. Questo accade poiché viene 63. 'eseguito un matching sul tipo degli argomenti: se essi 64. 'sono diversi, indipendentemente dal fatto che possano 65. 'essere convertiti l'uno nell'altro (al contrario di 66. 'quanto dice il testo dell'errore), viene sollevata 67. 'quell'eccezione. Per aggirare il problema, si 68. 'dovrebbe eseguire un cast esplicito controllando prima 69. 'il tipo della proprietà: 70. ' If SelectedProp.PropertyType Is GetType(Int32) Then 71. ' NewValue = CType(NewValue, Int32) 72. ' ElseIf SelectedProp. ... 73. 'È il prezzo da pagare quando si lavora con 74. 'uno strumento così generale come la Reflection. 75. '[Generalmente si conosce in anticipo il tipo] 76. SelectedProp.SetValue(Selected, NewValue, Nothing) 77. 78. Console.WriteLine("Proprietà modificata!") 79. 80. PrintInfo(Selected, "") 81. 82. Console.ReadKey() 83. End Sub 84. End Module Chi ha letto anche la ver sione pr ecedente della guida, avr à notato che manca il codice per l'assembly br ow ser , ossia quel pr ogr amma che elenca tutti i tipi (e tutti i membr i di ogni tipo) pr esenti in un assembly. Mi sembr ava tr oppo noioso e labor ioso e tr oppo poco inter essante per r ipr opor lo anche qui, ma siete liber i di dar ci un'occhiata (al r elativo capitolo della ver sione 2).
  • 223. A47. La Reflection - Parte IV Compilazione di c odic e a runtime Bene, or a che sappiamo scr iver e del nor male codice per una qualsiasi applicazione e che sappiamo come analizzar e codice ester no, che ne dite di scr iver e pr ogr ammi che pr oducano pr ogr ammi? La questione è molto diver tente: esistono delle apposite classi, in .NET, che consentono di compilar e codice che viene pr odotto dur ante l'esecuzione dal'applicazione stessa, gener ando così nuovi assembly per gli scopi più var i. Una volta mi sono ser vito in manier a intensiva di questa capacità del .NET per scr iver e un installer : non solo esso cr eava altr i pr ogr ammi (autoestr aenti), ma questi a lor o volta cr eavano altr i pr ogr ammi per estr ar r e le infor mazioni memor izzate negli autoestr aenti stessi: pr ogr ammi che scr ivono pr ogr ammi che scr ivono pr ogr ammi! Ma or a vediamo più nel dettaglio cosa usar e nello specifico per attivar e queste inter essanti funzionalità. Pr ima di tutto, è necessar io impor tar e un paio di namespace: System.CodeDom e System.CodeDom.Compiler . Essi contengono le classi che fanno al caso nostr o per il mestier e. Il pr ocesso di compilazione si svolge alltr aver so queste fasi: Pr ima si ottiene il codice da compilar e, che può esser e memor izzato in un file o pr odotto dir ettamente dal pr ogr amma sottofor ma di nor male str inga; Si impostano i par ametr i di compilazione: ad esempio, si può sceglier e il tipo di output (*.ex e o *.dll), i r ifer imenti da includer e, se mantener e i file tempor anei, se cr ear e l'assembly e salvar lo in memor ia, se tr attar e gli w ar ning come er r or i, ecceter a... Insomma, tutto quello che noi scegliamo tr amite l'inter faccia dell'ambiente di sviluppo o che ci tr oviamo già impostato gr azie all'IDE stesso; Si compila il codice r ichiamando un pr ovider di compilazione; Si leggono i r isultati della compilazione. Nel caso ci siano stati er r or i, i r isultati conter r anno tutta la lista degli er r or i, con r elative infor mazioni sulla lor o posizione nel codice; in caso contr ar io, l'assembly ver r à gener ato cor r ettamente; Se l'assembly conteneva codice che ser ve al pr ogr amma, si usa la Reflection per ottener ne e invocar ne i metodi. Queste cinque fasi cor r ispondono a cinque oggetti che dovr emo usar e nel codice: Str ing : ovviamente, il codice memor izzato sottofor ma di str inga; Compiler Par ameter s : classe del namespace CodeDom.Compiler . Contiene come pr opr ietà tutte le opzioni che ho esemplificato nella lista pr ecedente; VBCodePr ovider : pr ovider di compilazione per il linguaggio Visual Basic. Esiste un pr ovider per ogni linguaggio .NET, anche se può non tr ovar si sempr e nello stesso namespace. Esso for nir à i metodi per avviar e la compilazione; Compiler Results : contiene tutte le infor mazioni r elative all'output della compilazione. Se si sono ver ificati er r or i, ne espone una lista; se la compilazion è andata a buon file, r ifer isce dove si tr ova l'assembly compilato e, se ci sono, dove sono posti i file tempor anei; Assembly : classe che abbiamo già analizzato. Per mette di car icar e in memor ia l'assembly pr odotto come output e r ichiamar ne il codice od usar ne le classi ivi definite. Il pr ossimo esempio costituisce uno dei casi più evidenti di quando conviene r ivolger si alla r eflection, e sono sicur o che potr ebbe tor nar vi utile in futur o: 001. Module Module1 002. 003. 'Questa classe rappresenta una funzione matematica: 004. 'ne ho racchiuso il nome tra parentesi quadre poiché Function 005. 'è una keyword del linguaggio, ma in questo caso la si 006.
  • 224. 'vuole usare come identificatore. In generale, si possono usare 007. 'le parentesi quadre per trasformare ogni keyword in un normale 008. 'nome. 009. Class [Function] 010. Private _Expression As String 011. Private EvaluateFunction As MethodInfo 012. 013. 'Contiene l'espressione matematica che costruisce il valore 014. 'della funzione in base alla variabile x 015. Public Property Expression() As String 016. Get 017. Return _Expression 018. End Get 019. Set(ByVal value As String) 020. _Expression = value 021. Me.CreateEvaluator() 022. End Set 023. End Property 024. 025. 'La prossima è la procedura centrale di tutto l'esempio. 026. 'Il suo compito consiste nel compilare una libreria di 027. 'classi in cui è definita una funzione che, ricevuto 028. 'come parametro un x decimale, ne restituisce il valore 029. 'f(x). Il corpo di tale funzione varia in base 030. 'all'espressione immessa dall'utente. 031. Private Sub CreateEvaluator() 032. 'Crea il codice della libreria: una sola classe 033. 'contenente la sola funzione statica Evaluate. Gli {0} 034. 'verranno sostituti con caratteri di "a capo", mentre 035. 'in {1} verrà posta l'espressione che produce 036. 'il valore desiderato, ad esempio "x ^ 2 + 1". 037. Dim Code As String = String.Format( _ 038. "Imports Microsoft.VisualBasic{0}" & _ 039. "Imports System{0}" & _ 040. "Imports System.Math{0}" & _ 041. "Public Class Evaluator{0}" & _ 042. " Public Shared Function Evaluate(ByVal X As Double) As Double{0}" & _ 043. " Return {1}{0}" & _ 044. " End Function{0}" & _ 045. "End Class", Environment.NewLine, Me.Expression) 046. 047. 'Crea un nuovo oggetto CompilerParameters, per 048. 'contenere le informazioni relative alla compilazione 049. Dim Parameters As New CompilerParameters 050. 051. With Parameters 052. 'Indica se creare un eseguibile o una libreria di 053. 'classi: in questo caso ci interessa la seconda, 054. 'quindi impostiamo la proprietà su False 055. .GenerateExecutable = False 056. 057. 'Gli warning vengono considerati come errori 058. .TreatWarningsAsErrors = True 059. 'Non vogliamo tenere alcun file temporaneo: ci 060. 'interessa solo l'assembly compilato 061. .TempFiles.KeepFiles = False 062. 'L'assembly verrà tenuto in memoria temporanea 063. .GenerateInMemory = True 064. 065. 'I due riferimenti di cui abbiamo bisogno, che si 066. 'trovano nella GAC (quindi basta specificarne il 067. 'nome). In questo caso, si richiede anche 068. 'l'estensione (*.dll) 069. .ReferencedAssemblies.Add("Microsoft.VisualBasic.dll") 070. .ReferencedAssemblies.Add("System.dll") 071. End With 072. 073. 'Crea un nuovo provider di compilazione 074. Dim Provider As New VBCodeProvider 075. 'E compila il codice seguendo i parametri di 076. 'compilazione forniti dall'oggetto Parameters. Il 077. 'valore restituito dalla funzione 078.
  • 225. 'CompileAssemblyFromSource è di tipo 079. 'CompilerResults e viene salvato in CompResults 080. Dim CompResults As CompilerResults = _ 081. Provider.CompileAssemblyFromSource(Parameters, Code) 082. 083. 'Se ci sono errori, lancia un'eccezione 084. If CompResults.Errors.Count > 0 Then 085. Throw New FormatException("Espressione non valida!") 086. Else 087. 'Altrimenti crea un riferimento all'assembly di 088. 'output. La proprietà CompiledAssembly di 089. 'CompResults contiene un riferimento diretto a 090. 'quell'assembly, quindi ci è molto comoda. 091. Dim Asm As Reflection.Assembly = CompResults.CompiledAssembly 092. 'Dall'assembly ottiene un OT che rappresenta 093. 'l'unico tipo ivi definito, e da questo ne 094. 'estrae un MethodInfo con informazioni sul 095. 'metodo Evaluate (l'unico presente). 096. Me.EvaluateFunction = _ 097. Asm.GetType("Evaluator").GetMethod("Evaluate") 098. End If 099. End Sub 100. 101. 'Per richiamare la funzione, basta invocare il metodo 102. 'Evaluate estratto precedentemente. Al pari delle 103. 'proprietà e dei campi, che possono essere letti o 104. 'scritti dinamicamente, anche i metodi possono essere 105. 'invocati dinamicamete attraverso MethodInfo. Si usa 106. 'la funzione Invoke: il primo parametro da 107. 'passare è l'oggetto da cui richiamare il metodo, mentre 108. 'il secondo è un array di oggetti che indicano i 109. 'parametri da passare a tale metodo. 110. 'In questo caso, il primo parametro è Nothing poiché 111. 'Evaluate è una funzione statica e non ha bisgno di nessuna 112. 'istanza per essere richiamata. 113. Public Function Apply(ByVal X As Double) As Double 114. Return EvaluateFunction.Invoke(Nothing, New Object() {X}) 115. End Function 116. 117. End Class 118. 119. Sub Main() 120. Dim F As New [Function]() 121. 122. Do 123. Try 124. Console.Clear() 125. Console.WriteLine("Inserisci una funzione: ") 126. Console.Write("f(x) = ") 127. F.Expression = Console.ReadLine 128. Exit Do 129. Catch ex As Exception 130. Console.WriteLine("Espressione non valida!") 131. Console.ReadKey() 132. End Try 133. Loop 134. 135. Dim Input As String 136. Dim X As Double 137. 138. Do 139. Try 140. Console.Clear() 141. Console.WriteLine("Immettere 'stop' per terminare.") 142. Console.WriteLine("Il programma calcola il valore di f in X: ") 143. Console.Write("x = ") 144. Input = Console.ReadLine 145. If Input <> "stop" Then 146. X = CType(Input, Double) 147. Console.WriteLine("f(x) = {0}", F.Apply(X)) 148. Console.ReadKey() 149. Else 150.
  • 226. Exit Do 151. End If 152. Catch Ex As Exception 153. Console.WriteLine(Ex.Message) 154. Console.ReadKey() 155. End Try 156. Loop 157. End Sub 158. End Module In questo esempio ho utilizzato solo alcuni dei membr i esposti dalle classi sopr a menzionate. Di seguito elenco tutti quelli più r ilevanti, che potr ebber o ser vir vi in futur o: Co m piler Par am eter s Compiler Options : contiene sottofor ma di str inghe dei par ametr i aggiuntivi da passar e al compilator e. Vedr emo solo più avanti di cosa si tr atta e di come possano gener almente esser e modificati dall'IDE nell'ambito del nostr o pr ogetto; EmbeddedResour ces : una lista di str inghe, ognuna delle quali indica il per cor so su disco di un file di r isor se da includer e nell'assembly compilato. Di questi file par ler ò nella sezione B; Gener ateEx cutable : deter mina se gener ar e un eseguibile o una libr er ia di classi; Gener ateInMemor y : deter mina se non salvar e l'assembly gener ato su un suppor to per manente (disco fisso o altr e memor ie non volatili); IncludeDebugInfor mations : deter mina se includer e nell'eseguibile anche le infor mazioni r elative al debug. Di solito questo non è molto utile per ché è possibile acceder e pr ima a queste infor mazioni tr amite l'IDE facendo il debug del codice stesso che compila altr o codice XD; MainClass : imposta il nome della classe pr incipale dell'assembly. Se si sta compilando una libr er ia di classi, questa pr opr ietà è inutile. Se, invece, si sta compilando un pr ogr amma, questa pr opr ietà indica il nome della classe dove è contenuta la pr ocedur a Main, il punto di ingr esso nell'applicazione. Gener almente il compilator e r iesce ad individuar e da solo tale classe, ma nel caso ci siano più classi contenenti un metodo Main bisogna specificar lo esplicitamente. Nel caso l'applicazione da compilar e sia di tipo w indow s for m, come vedr emo nella sezione B, la MainClass può anche indicar e la classe che r appr esenta la finestr a iniziale; OutputAssembly : imposta il per cor so dell'assembly da gener ar e. Nel caso questa pr opr ietà non venga impostata pr ima della compilazione, sar à il pr ovider di compilazione a pr eoccupar si di cr ear e un nome casuale per l'assembly e di salvar lo nella stessa car tella del nostr o pr ogr amma; Refer encedAssemblis : anche questa è una collezione di str inghe, e contiene il nome degli assemblies da includer e come r ier imeneto per il codice cor r ente. Dovete sempr e includer e almeno System.dll (quello più impor tante). Gli altr i assemblies pubblici sono facoltativi e var iano in funzione del compito da svolger e: se doveste usar e file x ml, impor ter ete anche System.Xml.dll, ad esempio; TempFiles : collezione che contiene i per cor si dei file tempor anei. Espone qualche pr opr ietà e metodo in più r ispetto a una nor male collezione; Tr eatWar ningsAsEr r or s : tr atta gli w ar ning come se fosser o er r or i. Questo impedisce di por tar e a ter mine la compilazione quando ci sono degli w ar ning; War ningLevel : livello da cui il compilator e inter r ompe la compilazione. La documentazione su questa pr opr ietà non è molto chiar a e non si capisce bene cosa intenda. È pr obabile che ogni w ar ning abbia un cer to livello di aller ta e questo valor e dovr ebbe comunicar e al compilator e di visualizzar e solo gli w ar ning con livello maggior e o uguale a quello specificato... solo ipotesi, tuttavia. Co m piler Results CompiledAssembly : r estituisce un oggetto Assembly in r ifer imento all'assembly compilato; Er r or s : collezione di oggetti di tipo Compiler Er r or . Ognuno di questi oggetti espone delle pr opr ietà utili a
  • 227. identificar e il luogo ed il motivo dell'er r or e. Alcune sono: Line e Column (linea e colonna dell'er r or e), IsWar ning (se è un w ar ning o un er r or e), Er r or Number (numer o identificativo dell'er r or e), Er r or Tex t (testo dell'er r or e) e FileName (nome del file in cui si è ver ificato); NativeCompiler Retur nValue : r estituisce il valor e che a sua volta il compilator e ha r estituito al pr ogr amma una volta ter minata l'esecuzione. Vi r icor do, infatti, che compilator e, editor di codice e debugger sono tr e pr ogr ammi differ enti: l'ambiente di sviluppo integr ato for nisce un'inter faccia che sembr a unir li in un solo applicativo, ma r imangono sempr e entità distinti. Come tali, un pr ogr amma può r estituir e al suo chiamante un valor e, solitamente inter o: pr opr io come si compor ta una funzione; PathToAssembly : il per cor so su disco dell'assembly gener ato; TempFiles : i file tempor aneai r imasti. Avr ete notato che anche VBCodePr ovider espone molti metodi, ma la maggior par te di questi ser vono per la gener azione di codice. Questo meccanismo per mette di assemblar e una collezione di oggetti ognuno dei quali r appr esenta un'istr uzione di codice, e poi di gener ar e codice per un qualsiasi linguaggio .NET. Sebbene sia un funzionalità potente, non la tr atter ò in questa guida. Generazione di programmi Il pr ossimo sor gente è un esempio che, secondo me, sar ebbe stato MOLTO più fr uttuoso se usato in un'applicazione w indow s for ms. Tuttavia siamo nella sezione A e qui si fa solo teor ia, per ciò, pur tr oppo, dovr ete sor bir vi questo entusiasmante esempio come applicazione console. Ammettiamo che un'impr esa abbia un softw ar e di gestione dei suoi mater iali, e che abbastanza spesso acquisti nuove tipologie di pr odotti o semilavor ati. Gli addetti al magazzino dovr anno intr odur r e i dati dei nuovi oggetti, ma per far ciò, è necessar io un pr ogr amma adatto per gestir e quel tipo di oggetti: in questo modo, ogni volta, ser ve un pr ogr amma nuovo. L'esempio che segue è una possibile soluzione a questo pr oblema: il pr ogr amma pr incipale r ichiede di immetter e infor mazioni su un nuovo tipo di dato e cr ea un pr ogr amma apposta per la gestione di quel tipo di dato (notate che ho scr itto cinque volte pr ogr amma sulla stessa colonna XD). 001. Module Module1 002. 003. 'Classe che rappresenta il nuovo tipo di dato, ed 004. 'espone una funzione per scriverne il codice 005. Class TypeCreator 006. Private _Fields As Dictionary(Of String, String) 007. Private _Name As String 008. 009. 'Fields è un dizionario che contiene come 010. 'chiavi i nomi delle proprietà da definire 011. 'nel nuovo tipo e come valori il loro tipi 012. Public ReadOnly Property Fields() As Dictionary(Of String, String) 013. Get 014. Return _Fields 015. End Get 016. End Property 017. 018. 'Nome del nuovo tipo 019. Public Property Name() As String 020. Get 021. Return _Name 022. End Get 023. Set(ByVal value As String) 024. _Name = value 025. End Set 026. End Property 027. 028. Public Sub New() 029. _Fields = New Dictionary(Of String, String) 030. End Sub 031. 032.
  • 228. 'Genera il codice della proprietà 033. Public Function GenerateCode() As String 034. Dim Code As New Text.StringBuilder() 035. 036. Code.AppendLine("Class " & Name) 037. For Each Field As String In Me.Fields.Keys 038. Code.AppendFormat("Private _{0} As {1}{2}", _ 039. Field, Me.Fields(Field), Environment.NewLine) 040. Code.AppendFormat( _ 041. "Public Property {0} As {1}{2}" & _ 042. " Get{2}" & _ 043. " Return _{0}{2}" & _ 044. " End Get{2}" & _ 045. " Set(ByVal value As {1}){2}" & _ 046. " _{0} = value{2}" & _ 047. " End Set{2}" & _ 048. "End Property{2}", _ 049. Field, Me.Fields(Field), Environment.NewLine) 050. Next 051. Code.AppendLine("End Class") 052. 053. Return Code.ToString() 054. End Function 055. 056. End Class 057. 058. 'Classe statica contenente la funzione per scrivere 059. 'e generare il nuovo programma 060. Class ProgramCreator 061. 062. 'Accetta come input il nuovo tipo di dato 063. 'da gestire. Restituisce in output il percorso 064. 'dell'eseguibile creato 065. Public Shared Function CreateManagingProgram(ByVal T As TypeCreator) As String 066. Dim Code As New Text.StringBuilder() 067. 068. Code.AppendLine("Imports System") 069. Code.AppendLine("Imports System.Collections.Generic") 070. Code.AppendLine("Module Module1") 071. Code.AppendLine(T.GenerateCode()) 072. 073. Code.AppendLine("Sub Main()") 074. Code.AppendLine(" Dim Storage As New List(Of " & T.Name & ")") 075. Code.AppendLine(" Dim Cmd As Char") 076. Code.AppendLine(" Do") 077. Code.AppendLine(" Console.Clear()") 078. Code.AppendLine(" Console.WriteLine(""Inserimento di oggetti " & T.Name & """)") 079. Code.AppendLine(" Console.WriteLine()") 080. Code.AppendLine(" Console.Writeline(""Scegliere un'operazione: "")") 081. Code.AppendLine(" Console.WriteLine("" i - inserimento;"")") 082. Code.AppendLine(" Console.WriteLine("" e - elenca;"")") 083. Code.AppendLine(" Console.WriteLine("" u - uscita."")") 084. Code.AppendLine(" Cmd = Console.ReadKey().KeyChar") 085. Code.AppendLine(" Console.Clear()") 086. Code.AppendLine(" Select Case Cmd") 087. Code.AppendLine(" Case ""i"" ") 088. Code.AppendLine(" Dim O As New " & T.Name & "()") 089. Code.AppendLine(" Console.WriteLine(""Inserire i dati: "")") 090. 'Legge ogni membro del nuovo tipo. Usa la CType 091. 'per essere sicuri che tutto venga interpretato nel 092. 'modo corretto. 093. For Each Field As String In T.Fields.Keys 094. Code.AppendFormat("Console.Write("" {0} = ""){1}", Field, Environment.NewLine) 095. Code.AppendFormat("O.{0} = CType(Console.ReadLine(), {1}){2}", Field, T.Fields(Field), Environment.NewLine) 096. Next 097. Code.AppendLine(" Storage.Add(O)") 098. Code.AppendLine(" Console.WriteLine(""Inserimento completato!"")") 099. Code.AppendLine(" Case ""e"" ") 100. Code.AppendLine(" For I As Int32 = 0 To Storage.Count - 1") 101. Code.AppendLine(" Console.WriteLine(""{0:000} + "", I)") 102.
  • 229. 'Fa scrivere una linea per ogni proprietà 103. 'dell'oggetto, mostrandone il valore 104. For Each Field As String In T.Fields.Keys 105. Code.AppendFormat("Console.WriteLine("" {0} = "" & Storage(I). {0}.ToString()){1}", Field, Environment.NewLine) 106. Next 107. Code.AppendLine(" Next") 108. Code.AppendLine(" Console.ReadKey()") 109. Code.AppendLine(" End Select") 110. Code.AppendLine(" Loop Until Cmd = ""u""") 111. 112. Code.AppendLine("End Sub") 113. Code.AppendLine("End Module") 114. 115. Dim Parameters As New CompilerParameters 116. 117. With Parameters 118. .GenerateExecutable = True 119. .TreatWarningsAsErrors = True 120. .TempFiles.KeepFiles = False 121. .GenerateInMemory = False 122. .ReferencedAssemblies.Add("Microsoft.VisualBasic.dll") 123. .ReferencedAssemblies.Add("System.dll") 124. End With 125. 126. Dim Provider As New VBCodeProvider 127. Dim CompResults As CompilerResults = _ 128. Provider.CompileAssemblyFromSource(Parameters, Code.ToString()) 129. 130. If CompResults.Errors.Count = 0 Then 131. Return CompResults.PathToAssembly 132. Else 133. For Each E As CompilerError In CompResults.Errors 134. Stop 135. Next 136. Return Nothing 137. End If 138. End Function 139. 140. End Class 141. 142. Sub Main() 143. Dim NewType As New TypeCreator() 144. Dim I As Int16 145. Dim Field, FieldType As String 146. 147. Console.WriteLine("Creazione di un tipo") 148. Console.WriteLine() 149. 150. Console.Write("Nome del tipo = ") 151. NewType.Name = Console.ReadLine 152. 153. Console.WriteLine("Inserisci il nome del campo e il suo tipo:") 154. 155. I = 1 156. Do 157. Console.Write("Nome campo {0}: ", I) 158. Field = Console.ReadLine 159. 160. If String.IsNullOrEmpty(Field) Then 161. Exit Do 162. End If 163. 164. Console.Write("Tipo campo {0}: ", I) 165. FieldType = Console.ReadLine 166. 167. 'Dovrete immettere il nome completo e con 168. 'le maiuscole al posto giusto. Ad esempio: 169. ' System.String 170. 'e non string o system.String o STring. 171. If Type.GetType(FieldType) Is Nothing Then 172. Console.WriteLine("Il tipo {0} non esiste!", FieldType) 173.
  • 230. Console.ReadKey() 174. Else 175. NewType.Fields.Add(Field, FieldType) 176. I += 1 177. End If 178. Loop 179. 180. Dim Path As String = ProgramCreator.CreateManagingProgram(NewType) 181. 182. If Not String.IsNullOrEmpty(Path) Then 183. Console.WriteLine("Programma di gestione per il tipo {0} creato!", NewType.Name) 184. Console.WriteLine("Avviarlo ora? y/n") 185. If Console.ReadKey().KeyChar = "y" Then 186. Process.Start(Path) 187. End If 188. End If 189. End Sub 190. End Module
  • 231. A48. Gli Attributi Cosa sono e a c osa servono Gli attr ibuti sono una par ticolar e categor ia di oggetti che ha come unico scopo quello di for nir e infor mazioni su altr e entità. Tutte le infor mazioni contenute in un attr ibuto vanno sotto il nome di m etadati. Attr aver so i metadati, il pr ogr ammator e può definir e ulter ior i dettagli su un membr o o su un tipo e sul suo compor tamento in r elazione con le altr e par ti del codice. Gli attr ibuti, quindi, non sono str umenti di "pr ogr ammazione attiva", poiché non fan n o nulla, ma dicon o semplicemente qualcosa; si avvicinano, per cer ti ver si, alla pr ogr ammazione dichiar ativa. Inoltr e, essi non possono esser e usati né r intr acciati dal codice in cui sono definiti: non si tr atta di var iabili, metodi, classi, str uttur e o altr o; gli attr ibuti, semplicemente par lando, non "esistono" se non come par te invisibile di infor mazione attaccata a qualche altr o membr o. Sono come dei par assiti: hanno senso solo se attr ibuiti, appunto, a qualcosa. Per questo motivo, l'unico modo per utilizzar e le infor mazioni che essi si por tano dietr o consiste nella Reflection. La sintassi con cui si asseg na un attr ibuto a un membr o (non "si dichiar a", né "si inizializza", ma "si assegna" a qualcosa) è questa: 1. <Attributo(Parametri)> [Entità] Faccio subito un esempio. Il Visual Basic per mette di usar e ar r ay con indici a base maggior e di 0: questa è una sua peculiar ità, che non si tr ova in tutti i linguaggi .NET. Le Common Language Specifications del Fr amew or k specificano che un qualsiasi linguaggio, per esser e qualificato come .NET, deve dar e la possibilità di gestir e ar r ay a base 0. VB fa questo, ma espone anche quella par ticolar ità di pr ima che gli der iva dal VB classico: questa potenzialità è detta n on CLS-Complian t, ossia che non r ispetta le specifiche del Fr amew or k. Noi siamo liber i di usar la, ad esempio in una libr er ia, ma dobbiamo avver tir e gli altr i che, qualor a usasser o un altr o linguaggio .NET, non potr ebber o pr obabilmente usufr uir e di quella par te di codice. L'unico modo di far e ciò consiste nell'assegnar e un attr ibuto CLSCompliant a quel membr o che non r ispetta le specifiche: 01. <CLSCompliant(False)> _ 02. Function CreateArray(Of T)(ByVal IndexFrom As Int32, ByVal IndexTo As Int32) As Array 03. 'Per creare un array a estremi variabili è necessario 04. 'usare una funzione della classe Array, ed è altrettanto 05. 'necessario dichiarare l'array come di tipo Array, anche se 06. 'questo è solitamente sconsigliato. Per creare il 07. 'suddetto oggetto, bisogna passare alla funzione tre 08. 'parametri: 09. ' - il tipo degli oggetti che l'array contiene; 10. ' - un array contenente le lunghezze di ogni rango dell'array; 11. ' - un array contenente gli indici iniziali di ogni rango. 12. Return Array.CreateInstance(GetType(T), New Int32() {IndexTo - IndexFrom}, New Int32() {IndexTo}) 13. End Function 14. 15. Sub Main() 16. Dim CustomArray As Array = CreateArray(Of Int32)(3, 9) 17. 18. CustomArray(3) = 1 19. '... 20. End Sub Or a, se un pr ogr ammator e usasse la libr er ia in cui è posto questo codice, pr obabilmente il compilator e pr odur r ebbe un War ning aggiuntivo indicano esplicitamente che il metodo Cr eateAr r ay non è CLS-Compliant, e per ciò non è sicur o usar lo. Allo stesso modo, un altr o esempio potr ebbe esser e l'attr ibuto Obsolete. Specialmente quando si lavor a su gr andi pr ogetti di cui esistono più ver sioni e lo sviluppo è in costante evoluzione, capita che vengano scr itte nuove ver sioni di membr i o tipi già esistenti: quelle vecchie sar anno molto pr obabilmente mantenute per assicur ar e la compatibilità con
  • 232. softw ar e datati, ma sar anno comunque mar cate con l'attr ibuto obsolete. Ad esempio, con questa r iga di codice potr ete ottener e l'indir izzo IP del mio sito: 1. Dim IP As Net.IPHostEntry = System.Net.Dns.Resolve("www.totem.altervista.org") Tuttavia otter r ete un War ning che r ipor ta la seguente dicitur a: 'Public Shared Fun ction Res olve(hos tName As Strin g) As Sys tem.Net.IPHos tEn try' is obs olete: Res olve is obs oleted for this type, pleas e us e GetHos tEn try in s tead. http://go.micros oft.com/fwlin k/?lin kid=14202 . Questo è un esempio di un metodo, esistente dalla ver sione 1.1 del Fr amew or k, che è stato r impiazzato e quindi dichiar ato obsoleto. Un altr o attr ibuto molto inter essante è, ad esempio, Conditional, che per mette di eseguir e o tr alasciar e del codice a seconda che sia definita una cer ta costante di compilazione. Queste costanti sono impostabili in una finestr a di compilazione avanzata che vedr emo solo più avanti. Tuttavia, quando l'applicazione è in modalità debug, è di default definita la costante DEBUG. 01. <Conditional("DEBUG")> _ 02. Sub WriteStatus() 03. 'Scriva a schermo la quantità di memoria usata, 04. 'senza aspettare la prossima garbage collection 05. Console.WriteLine("Memory: {0}", GC.GetTotalMemory(False)) 06. End Sub 07. 08. 'Crea un nuovo oggetto 09. Function CreateObject(Of T As New)() As T 10. Dim Result As New T 11. 'Richiama WriteStatus: questa chiamata viene IGNORATA 12. 'in qualsiasi caso, tranne quando siamo in debug. 13. WriteStatus() 14. Return Result 15. End Function 16. 17. Sub Main() 18. Dim k As Text.StringBuilder = _ 19. CreateObject(Of Text.StringBuilder)() 20. '... 21. End Sub Usando Cr eateObject possiamo monitor ar e la quantità di memor ia allocata dal pr ogr amma, ma solo in modalità debug, ossia quando la costante di compilazione DEBUG è definita. Come avr ete notato, tutti gli esempi che ho fatto comunicavano infor mazioni dir ettamente al compilator e, ed infatti una buona par te degli attr ibuti ser ve pr opr io per definir e compor tamente che non si potr ebber o indicar e in altr o modo. Un attr ibuto può esser e usato, quindi, nelle manier e più var ie e intr odur r ò nuovi attr ibuti molto impor tanti nelle pr ossime sezioni. Questo capitolo non ha lo scopo di mostr ar e il funzionamento di ogni attr ibuti esistente, ma di insegnar e a cosa esso ser va e come agisca: ecco per chè nel pr ossimo par agr afo ci cimenter emo nella scr ittur a di un nuovo attr ibuto. Dic hiarare nuovi attributi For malmente, un attr ibuto non è altr o che una classe der ivata da System.Attr ibute. Ci sono alcune convenzioni r iguar do la scr ittur a di queste classi, per ò: Il nome della classe deve sempr e ter minar e con la par ola "Attr ibute"; Gli unici membr i consentiti sono: campi, pr opr ietà e costr uttor i; Tutte le pr opr ietà che vengono impostate nei costr uttor i devono esser e ReadOnly, e vicever sa. Il pr imo punto è solo una convenzione, ma gli altr i sono di utilità pr atica. Dato che lo scopo dell'attr ibuto è contener e infor mazione, è ovvio che possa contener e solo pr opr ietà, poiché non spetta a lui usar ne il valor e. Ecco un esempio semplice con un attr ibuto senza pr opr ietà:
  • 233. 001. 'In questo codice, cronometreremo dei metodi, per 002. 'vedere quale è il più veloce! 003. Module Module1 004. 005. 'Questo è un nuovo attributo completamente vuoto. 006. 'L'informazione che trasporta consiste nel fatto stesso 007. 'che esso sia applicato ad un membro. 008. 'Nel metodo di cronometraggio, rintracceremo e useremo 009. 'solo i metodi a cui sia stato assegnato questo attributo. 010. Public Class TimeAttribute 011. Inherits Attribute 012. 013. End Class 014. 015. 'I prossimi quattro metodi sono procedure di test. Ognuna 016. 'esegue una certa operazione 100mila o 10 milioni di volte. 017. 018. <Time()> _ 019. Sub AppendString() 020. Dim S As String = "" 021. For I As Int32 = 1 To 100000 022. S &= "a" 023. Next 024. S = Nothing 025. End Sub 026. 027. <Time()> _ 028. Sub AppendBuilder() 029. Dim S As New Text.StringBuilder() 030. For I As Int32 = 1 To 100000 031. S.Append("a") 032. Next 033. S = Nothing 034. End Sub 035. 036. <Time()> _ 037. Sub SumInt32() 038. Dim S As Int32 039. For I As Int32 = 1 To 10000000 040. S += 1 041. Next 042. End Sub 043. 044. <Time()> _ 045. Sub SumDouble() 046. Dim S As Double 047. For I As Int32 = 1 To 10000000 048. S += 1.0 049. Next 050. End Sub 051. 052. 'Questa procedura analizza il tipo T e ne estrae tutti 053. 'i metodi statici e senza parametri marcati con l'attributo 054. 'Time, quindi li esegue e li cronometra, poi riporta 055. 'i risultati a schermo per ognuno. 056. 'Vogliamo che i metodi siano statici e senza parametri 057. 'per evitare di raccogliere tutte le informazioni per la 058. 'funzione Invoke. 059. Sub ReportTiming(ByVal T As Type) 060. Dim Methods() As MethodInfo = T.GetMethods() 061. Dim TimeType As Type = GetType(TimeAttribute) 062. Dim TimeMethods As New List(Of MethodInfo) 063. 064. 'La funzione GetCustomAttributes accetta due parametri 065. 'nel secondo overload: il primo è il tipo di 066. 'attributo da cercare, mentre il secondo specifica se 067. 'cercare tale attributo in tutto l'albero di 068. 'ereditarietà del membro. Restituisce come 069. 'risultato un array di oggetti contenenti gli attributi 070. 'del tipo voluto. Vedremo fra poco come utilizzare 071. 'questo array: per ora limitiamoci a vedere se non è 072.
  • 234. 'vuoto, ossia se il metodo è stato marcato con Time 073. For Each M As MethodInfo In Methods 074. If M.GetCustomAttributes(TimeType, False).Length > 0 And _ 075. M.GetParameters().Count = 0 And _ 076. M.IsStatic Then 077. TimeMethods.Add(M) 078. End If 079. Next 080. 081. Methods = Nothing 082. 083. 'La classe Stopwatch rappresenta un cronometro. Start 084. 'per farlo partire, Stop per fermarlo e Reset per 085. 'resettarlo a 0 secondi. 086. Dim Crono As New Stopwatch 087. 088. For Each M As MethodInfo In TimeMethods 089. Crono.Reset() 090. Crono.Start() 091. M.Invoke(Nothing, New Object() {}) 092. Crono.Stop() 093. Console.WriteLine("Method: {0}", M.Name) 094. Console.WriteLine(" Time: {0}ms", Crono.ElapsedMilliseconds) 095. Next 096. 097. TimeMethods.Clear() 098. TimeMethods = Nothing 099. End Sub 100. 101. Sub Main() 102. Dim This As Type = GetType(Module1) 103. 104. 'Non vi allarmate se il programma non stampa nulla 105. 'per qualche secondo. Il primo metodo è molto 106. 'lento XD 107. ReportTiming(This) 108. 109. Console.ReadKey() 110. End Sub 111. End Module Ecco i r isultati del benchmar king (ter mine tecnico) sul mio por tatile: Method: AppendString Time: 4765ms Method: AppendBuilder Time: 2ms Method: SumInt32 Time: 27ms Method: SumDouble Time: 34ms Come potete osser var e, concatenar e le str inghe con & è enor memente meno efficiente r ispetto all'Append della classe Str ingBuilder . Ecco per chè, quando si hanno molti dati testuali da elabor ar e, consiglio sempr e di usar e il secondo metodo. Per quando r iguar da i numer i, le pr estazioni sono comunque buone, se non che i Double occupano 32 bit in più e ci vuole più tempo anche per elabor ar li. In questo esempio avete visto che gli attr ibuti possono esser e usati solo attr aver so la Reflection. Pr ima di pr oceder e, bisogna dir e che esiste uno speciale attr ibuto applicabile solo agli attr ibuti che definisce quali entità possano esser e mar cate con dato attr ibuto. Esso si chiama Attr ibuteUsage. Ad esempio, nel codice pr ecedente, Time è stato scr itto con l'intento di mar car e tutti i metodi che sar ebber o stati sottoposti a benchmar king, ossia è "nato" per esser e applicato solo a metodi, e non a classi, enumer ator i, var iabili o altr o. Tuttavia, per come l'abbiamo dichiar ato, un pr ogr ammator e può applicar lo a qualsiasi cosa. Per r estr inger e il suo campo d'azione si dovr ebbe modificar e il sor gente come segue: 1. <AttributeUsage(AttributeTargets.Method)> _ 2.
  • 235. Public Class TimeAttribute 3. Inherits Attribute 4. 5. End Class Attr ibuteTar gets è un enumer ator e codificato a bit. Ma veniamo or a agli attr ibuti con par ametr i: 001. Module Module1 002. 003. 'UserInputAttribute specifica se una certa proprietà 004. 'debba essere valorizzata dall'utente e se sia 005. 'obbligatoria o meno. Il costruttore impone un solo 006. 'argomento, IsUserScope, che deve essere per forza 007. 'specificato, altrimenti non sarebbe neanche valsa la 008. 'pena di usare l'attributo, dato che questa è la 009. 'sua unica funzione. 010. 'Come specificato dalle convenzioni, la proprietà 011. 'impostata nel costruttore è ReadOnly, mentre 012. 'le altre (l'altra) è normale. 013. <AttributeUsage(AttributeTargets.Property)> _ 014. Class UserInputAttribute 015. Inherits Attribute 016. 017. Private _IsUserScope As Boolean 018. Private _IsCompulsory As Boolean = False 019. 020. Public ReadOnly Property IsUserScope() As Boolean 021. Get 022. Return _IsUserScope 023. End Get 024. End Property 025. 026. Public Property IsCompulsory() As Boolean 027. Get 028. Return _IsCompulsory 029. End Get 030. Set(ByVal value As Boolean) 031. _IsCompulsory = value 032. End Set 033. End Property 034. 035. Sub New(ByVal IsUserScope As Boolean) 036. _IsUserScope = IsUserScope 037. End Sub 038. 039. End Class 040. 041. 'Cubo 042. Class Cube 043. Private _SideLength As Single 044. Private _Density As Single 045. Private _Cost As Single 046. 047. 'Se i parametri del costruttore vanno specificati 048. 'tra parentesi quando si assegna l'attributo, allora 049. 'come si fa a impostare le altre proprietà 050. 'facoltative? Si usa un particolare operatore di 051. 'assegnamento ":=" e si impostano esplicitamente 052. 'i valori delle proprietà ad uno ad uno, 053. 'separati da virgole, ma sempre nelle parentesi. 054. <UserInput(True, IsCompulsory:=True)> _ 055. Public Property SideLength() As Single 056. Get 057. Return _SideLength 058. End Get 059. Set(ByVal value As Single) 060. _SideLength = value 061. End Set 062. End Property 063. 064.
  • 236. <UserInput(True)> _ 065. Public Property Density() As Single 066. Get 067. Return _Density 068. End Get 069. Set(ByVal value As Single) 070. _Density = value 071. End Set 072. End Property 073. 074. 'Cost non verrà chiesto all'utente 075. <UserInput(False)> _ 076. Public Property Cost() As Single 077. Get 078. Return _Cost 079. End Get 080. Set(ByVal value As Single) 081. _Cost = value 082. End Set 083. End Property 084. 085. End Class 086. 087. 'Crea un oggetto di tipo T richiendendo all'utente di 088. 'impostare le proprietà marcate con UserInput 089. 'in cui IsUserScope è True. 090. Function GetInfo(ByVal T As Type) As Object 091. Dim O As Object = T.Assembly.CreateInstance(T.FullName) 092. 093. For Each PI As PropertyInfo In T.GetProperties() 094. If Not PI.CanWrite Then 095. Continue For 096. End If 097. 098. Dim Attributes As Object() = PI.GetCustomAttributes(GetType(UserInputAttribute), True) 099. 100. If Attributes.Count = 0 Then 101. Continue For 102. End If 103. 104. 'Ottiene il primo (e l'unico) elemento dell'array, 105. 'un oggetto di tipo UserInputAttribute che rappresenta 106. 'l'attributo assegnato e contiene tutte le informazioni 107. 'passate, sottoforma di proprietà. 108. Dim Attr As UserInputAttribute = Attributs(0) 109. 110. 'Se la proprietà non è richiesta all'utente, 111. 'allora continua il ciclo 112. If Not Attr.IsUserScope Then 113. Continue For 114. End If 115. 116. Dim Value As Object = Nothing 117. 'Se è obbligatoria, continua a richiederla 118. 'fino a che l'utente non immette un valore corretto. 119. If Attr.IsCompulsory Then 120. Do 121. Try 122. Console.Write("* {0} = ", PI.Name) 123. Value = Convert.ChangeType(Console.ReadLine, PI.PropertyType) 124. Catch Ex As Exception 125. Value = Nothing 126. Console.WriteLine(Ex.Message) 127. End Try 128. Loop Until Value IsNot Nothing 129. Else 130. 'Altrimenti la richiede una sola volta 131. Try 132. Console.Write("{0} = ", PI.Name) 133. Value = Convert.ChangeType(Console.ReadLine, PI.PropertyType) 134. Catch Ex As Exception 135.
  • 237. Value = Nothing 136. End Try 137. End If 138. If Value IsNot Nothing Then 139. PI.SetValue(O, Value, Nothing) 140. End If 141. Next 142. 143. Return O 144. End Function 145. 146. Sub Main() 147. Dim O As Object 148. 149. Console.WriteLine("Riempire i campi (* = obbligatorio):") 150. 151. O = GetInfo(GetType(Cube)) 152. 153. 'Stampa i valori con il metodo PrintInfo scritto qualche 154. 'capitolo fa 155. PrintInfo(O, "") 156. 157. Console.ReadKey() 158. End Sub 159. End Module Vi lascio immaginar e cosa faccia il metodo Conver t.ChangeType...
  • 238. A49. Modificare le opzioni di compilazione Esistono più modi di influir e sulla compilazione di un sor gente e di modificar e il compor tamento del compilator e ver so le sue par ti. Alcuni di questi "modi" consistono nel modificar e le opzioni di compilazione, che si suddividuono in quattr o voci pr incipali: Ex plicit, Compar e, Infer e Str ict. Per attivar e o disattivar e ognuna di esse, è possibile usar e delle dir ettive speciali poste in testa al codice o acceder e alla finestr a dell'ambiente di sviluppo r elativa alla compilazione. Nel secondo caso, baster à che clicchiate, dal menù pr incipale, Pr oject > [NomePr getto] Pr oper ties; dalle pr opr ietà, scegliete la seconda scheda cliccando sull'etichetta Compile sulla sinistr a: Da questo pannelo potr ete anche decider e il compor tamento da adottar e ver so cer te cir costanze di codice, ossia se tr attar le come w ar ning, er r or i, o se non segnalar le neppur e. Option Explic it Quando Ex plicit è attiva, tutte le var iabili devono esser e esplicitamente dichiar ate pr ima del lor o uso: d'altr a par te, questa è sempr e stata la pr assi che abbiamo adottato fin dall'inizio del cor so e non ci sono par ticolar i motivi per combiar la. Quando l'opzione è disattivata, ogni nome sconosciuto ver r à tr attato come una nuova var iabile e cr eato al momento. Ecco un esempio in cui disattivo Ex plicit da codice: 01. Option Explicit Off 02. 03. Module Module1 04. Sub Main() 05. 'La variabile Stringa non viene dichiarata, ma è 06. 'lecito usarla e non viene comunicato alcun errore 07. Stringa = "Ciao" 08. 09. 'Stessa cosa per la variabile I, che non è stata 10. 'dichiarata da nessuna parte 11. For I = 1 To 20 12. Console.WriteLine(Stringa) 13. Next 14. 15.
  • 239. Console.ReadKey() 16. End Sub 17. End Module Le dir ettive per l'attivazione/disattivazione di un'opzione di compilazione devono tr ovar si sempr e in cima al sor gente, anche pr ima di ogni altr a dir ettiva Impor ts, e hanno una sintassi pr essoché costante: 1. Option [Nome] On/Off Anche se è possibile disattivar e Ex plicit, è fortemen te s con s igliato far lo: può pr odur r e molti più danni che benefici. È pur ver o che si usa meno codice, ma questo diventa anche meno compr ensibile, e gli er r or i di battitur a possono condannar vi a settimane di insonnia. Ad esempio: 01. Option Explicit Off 02. 03. Module Module1 04. Sub Main() 05. Stringa = "Ciao" 06. 07. For I = 1 To 20 08. If I > 10 Then 09. Strnga = I 10. End If 11. Console.WriteLine(Stringa) 12. Next 13. 14. Console.ReadKey() 15. End Sub 16. End Module Il codice dovr ebbe, nelle vostr e intenzioni, scr iver e "Ciao" solo 10 volte, e poi stampar e 11, 12, 13, ecceter a... Tuttavia questo non succede, per chè avete dimenticato una "i" e il compilator e non vi segnala nessun er r or e a causa della dir ettiva Option; non r icever ete neppur e un w ar ning del tipo "Unused local var iable", per chè la r iga in cui è pr esente Str nga consiste in un assegnamento. Er r or i stupidi possono causar e gr andi per dite di tempo. Option Compare Questa opzione non assume i valor i On/Off, ma Binar y e Tex t. Quando Compar e è impostata su Binar y, la compar azione tr a due str inghe viene effettuata confr ontando il valor e dei singoli bytes che la compongono e, per ciò, il confr onto diventa cas e-s en s itive (maiuscole e minuscole della stessa letter a sono consider ate differ enti). Se, al contr ar io, è impostata su Tex t, viene compar ato solo il testo che le str inghe contengono, senza far e distinzioni su letter e maiuscole o minuscole (cas e-in s en s itive). Eccone un esempio: 01. Option Compare Text 02. Module Module1 03. Sub Main() 04. If "CIAO" = "ciao" Then 05. Console.WriteLine("Option Compare Text") 06. Else 07. Console.WriteLine("Option Compare Binary") 08. End If 09. Console.ReadKey() 10. End Sub 11. End Module Scr ivendo "Binar y" al posto di "Tex t", otter r emo un messaggio differ ente a r untime! Option Stric t Questa opzione si occupa di r egolar e le conver sioni implicite tr a tipi di dato differ enti. Quando è attiva, tutti i cast
  • 240. impliciti vengono segnalati e consider ati come er r or i: non si può passar e, ad esempio, da Double a Integer o da una classe base a una der ivata: 01. Option Strict On 02. Module Module1 03. Sub Main() 04. Dim I As Int32 05. Dim P As Student 06. 07. 'Conversione implicita da Double a Int32: viene 08. 'segnalata come errore 09. I = 4.0 10. 'Conversione implicita da Person a Student: viene 11. 'segnalata come errore 12. P = New Person("Mario", "Rossi", New Date(1968, 9, 12)) 13. 14. Console.ReadKey() 15. End Sub 16. End Module Per evitar e di r icever e le segnalazioni di er r or e, bisogna utilizzar e un oper ator e di cast esplicito come CType. Gener almente, Str ict viene mantenuta su Off, ma se siete par ticolar mente r igor osi e volete evitar e le conver sioni implicite, siete liber i di attivar la. Option Infer Questa opzione di compilazione è stata intr odotta con la ver sione 2008 del linguaggio e, se attivata, per mette di infer ir e il tipo di una var iabile senza tipo (ossia senza clausola As) analizzando i valor i che le vengono passati. All'inizio della guida, ho detto che una var iabile dichiar ata solo con Dim (ad esempio "Dim I") viene consider ata di tipo Object: questo è ver o dalla ver sione 2005 in giù, e nella ver sioni 2008 e successive solo se Option Infer è disattivata. Ecco un esempio: 01. Option Infer Off 02. 03. Module Module1 04. Sub Main() 05. 'Infer è disattivata: I viene considerata di 06. 'tipo Object 07. Dim I = 2 08. 09. 'Dato che I è Object, può contenere 10. 'qualsiasi cosa, e quindi questo codice non genera 11. 'alcun errore 12. I = "ciao" 13. 14. End Sub 15. End Module Pr ovando ad impostar e Infer su On, non otter r ete nessuna segnalazione dur ante la scr ittur a, ma appena il pr ogr amma sar à avviato, ver r à lanciata un'eccezione di cast, poiché il tipo di I viene dedotto dal valor e assegnatole (2) e la fa diventar e, da quel momento in poi, una var iabile Integer a tutti gli effetti, e "ciao" non è conver tibile in inter o.
  • 241. A50. Comprendere e implementare un algoritmo For se sar ebbe stato oppor tuno tr attar e questo ar gomento moooolto pr ima nella guida piuttosto che alla fine della sezione; non per niente, la compr ensione degli algor itmi è la pr ima cosa che viene r ichiesta in un qualsiasi cor so di infor matica. Tuttavia, è pur ver o che, per r isolver e un pr oblema, bisogna dispor r e degli str umenti giusti e, pr efer ibilmente, conoscer e tutti gli str umenti a pr opr ia disposizione. Ecco per chè, pr ima di giunger e a questo punto, ho voluto spiegar e tutti i concetti di base e la sintassi del linguaggio, poiché questi sono solo str umenti nelle mani del pr ogr ammator e, la per sona che deve occupar si della stesur a del codice. Iniziamo col dar e una classica definizione di algor itmo, mutuata da Wikipedia: In s ieme di is truzion i elemen tari un ivocamen te in terpretabili che, es eguite in un ordin e s tabilito, permetton o la s oluzion e di un problema in un n umero fin ito di pas s i. Vor r ei innanzitutto por r e l'attenzione sulle pr ime par ole: "insieme di istr uzioni elementar i" (io aggiunger e "insieme finito", per esser e r igor osi, ma mi sembr a abbastanza difficile for nir e un insieme infinito di istr uzioni :P). Sottolineiamo elem entar i: anche se i linguaggi .NET si tr ovano ad un livello molto distante dal pr ocessor e, che è in gr ado di eseguir e un numer o molto limitato di istr uzioni - fr a cui And, Or , Not, +, Shift, lettur a e scr ittur a - la gamma di "comandi" disponibile è altr ettanto esigua. Dopotutto, cosa possiamo scr iver e che sia una ver a e pr opr ia istr uzione? Assegnamenti, oper azioni matematiche e ver ifia di condizioni. Punto. I cicli? Non fanno altr o che r ipeter e istr uzioni. I metodi? Non fanno altr o che r ichiamar e altr o codice in cui si cono istr uzioni elementar i. Dobbiamo impar ar e, quindi, a far e il meglio possibile con ciò ci cui disponiamo. Vi sar à utile, a questo pr oposito, disegnar e dei diagr ammi di flusso per i vostr i algor itmi. Inoltr e, r equsitio essenziale, è che ogni istr uzione sia unvicamente inter pr etabile, ossia che non possa esser ci ambiguità con qualsiasi altr a istr uzione. Dopotutto, questa condizione viene soddisfatta dall'elabor ator e stesso, per come è costr uito. Altr o aspetto impor tante è l'or dine: eseguir e pr ima A e poi B o vicever sa è differ ente; molto spesso questa inver sione può causar e er r or i anche gr avi. Infine, è necessar io che l'algor itmo r estituisca un r isultato in un numer o finito di passi: e questo è abbastanza ovvio, ma se ne per de di vista l'impor tanza, ad esempio, nelle funzioni r icor sive. In gener e, il pr oblema più gr ande di un pr ogr ammator e che deve scr iver e un algor itmo è r ender si conto di come l'uomo pensa. Ad esempio: dovete scr iver e un pr ogr amma che contr olla se due str inghe sono l'una l'anagr amma dell'altr a. A occhio è facile pensar e a come far e: basta qualche tentativo per veder e se r iusciamo a for mar e la pr ima con le letter e della seconda o vicever sa, ma, domanda classica, "come si tr aduce in codice"? Ossia, dobbiamo domandar ci come abbiamo fatto a giunger e alla conclusione, scomponendo le istr uzioni che il nostr o cer vello ha eseguito. Infatti, quasi sempr e, per istinto o abitudine, siamo por tati ad eseguir e molti passi logici alla volta, senza r ender cene conto: questo il computer non è in gr ado di far lo, e per istr uir lo a dover e dobbiamo abbassar ci al suo livello. Ecco una semplice lista di punti in cui espongo come passer ei dal "linguaggio umano" al "linguaggio macchina": Definizione di anagr amma da Wikipedia: "Un anagr amma è il r isultato della per mutazione delle letter e di una o più par ole compiuta in modo tale da cr ear e altr e par ole o eventualmente fr asi di senso compiuto." ; Bisogna contr ollar e se, spostando le letter e, è possibile for mar e l'altr a par ola; Ma questo è possibile solo se ogni letter a compar e lo stesso numer o di volte in entr ambe le par ole; Per ver ificar e quest'ultimo dato è necessar io contar e ogni letter a di entr ambe le par ole, e quindi contr ollar e che tutte abbiano lo stesso numer o di occor r enze; Ser ve per pr ima cosa memor izzar e i dati: due var iabili Str ing per le str inghe. Per gli altr i dati, bisogna associar e ad una letter a (Char ) un numer o (Int32), quindi una chiave ad un valor e: il tipo di dato ideale è un
  • 242. Dictionar y(Of Char , Int32). Si possono usar e due dizionar i o uno solo a seconda di cosa vi viene meglio (per semplicità ne user ò due); Ovviamente, a pr ior i, se le str inghe hanno lunghezza diver sa, sicur amente non sono anagr ammi; Or a è necessar io analizzar e le str inghe. Per scor r er e una str inga, basta ser vir si di un ciclo For e per ottener e un dato car atter e a una data posizione, la pr opr ietà (di default) Char s; In questo ciclo, se il dizionar io contiene il car atter e, allor a questo è già stato tr ovato almeno una volta, quindi se ne pr ende il valor e e lo si incr ementa di uno; in caso contr ar io, si aggiunge una nuova chiave con il valor e 1; Una volta ottenuti i due dizionar i, per pr ima cosa, devono aver e lo stesso numer o di chiavi, altr imenti una str inga avr ebbe dei car atter i che non compaiono nell'altr a; poi bisogna veder e se ogni chiave del pr imo dizionar io esiste anche nel secondo, ed infine se il valor e ad essa associato è lo stesso. Se una sola di queste condizioni è falsa, allor a le str inghe NON sono anagr ammi. Pr ima di veder e il codice, notate che è più semplice ver ificar e quando NON succede qualcosa, poiché basta un solo (contr o)esempio per confutar e una teor ia, ma ne occor r ono infiniti per dimostr ar la: 01. Module Module1 02. 03. Sub Main() 04. Dim S1, S2 As String 05. Dim C1, C2 As Dictionary(Of Char, Int32) 06. Dim Result As Boolean = True 07. 08. Console.Write("Stringa 1: ") 09. S1 = Console.ReadLine 10. Console.Write("Stringa 2: ") 11. S2 = Console.ReadLine 12. 13. If S1.Length = S2.Length Then 14. C1 = New Dictionary(Of Char, Int32) 15. For I As Int16 = 0 To S1.Length - 1 16. If C1.ContainsKey(S1.Chars(I)) Then 17. C1(S1.Chars(I)) += 1 18. Else 19. C1.Add(S1.Chars(I), 1) 20. End If 21. Next 22. 23. C2 = New Dictionary(Of Char, Int32) 24. For I As Int16 = 0 To S2.Length - 1 25. If C2.ContainsKey(S2.Chars(I)) Then 26. C2(S2.Chars(I)) += 1 27. Else 28. C2.Add(S2.Chars(I), 1) 29. End If 30. Next 31. 32. If C1.Keys.Count = C2.Keys.Count Then 33. For Each C As Char In C1.Keys 34. If Not C2.ContainsKey(C) Then 35. Result = False 36. ElseIf C1(C) <> C2(C) Then 37. Result = False 38. End If 39. If Not Result Then 40. Exit For 41. End If 42. Next 43. Else 44. Result = False 45. End If 46. Else 47. Result = False 48. End If 49. 50. If Result Then 51. Console.WriteLine("Sono anagrammi!") 52.
  • 243. Else 53. Console.WriteLine("Non sono anagrammi!") 54. End If 55. 56. Console.ReadKey() 57. End Sub 58. End Module
  • 244. A51. Il miglior codice Il fine giustifica i mezzi... beh non sempr e. In questo caso mi sto r ifer endo allo stile in cui il codice sor gente viene scr itto: infatti, si può ottener e un r isultato che all'occhio dell'utente del pr ogr amma sembr a buono, se non ottimo, ma che visto da un pr ogr ammator e osser vando il codice non è per niente affidabile. Quando si pubblicano i pr opr i pr ogr ammi open sour ce, con sor genti annessi, si dovr ebbe far e par ticolar e attenzione anche a come si scr ive, per mettendo agli altr i pr ogr ammator i di usufr uir e del pr opr io codice in manier a veloce e intuitiva. In questo modo ne tr ar r anno vantaggio non solo gli altr i, ma anche voi stessi, che potr este tr ovar vi a r iveder e uno stesso sor gente molto tempo dopo la sua stesur a e non r icor dar vi più niente. A tal pr oposito, elencher ò or a alcune buone nor me da seguir e per miglior ar e il pr opr io stile. Commentare È buona nor ma commentar e il sor gente nelle sue var ie fasi, per spiegar ne il funzionamento o anche solo lo scopo. Mentr e il commento può esser e tr alasciato per oper azione str aor dinar iamente lampanti e semplici, dovr ebbe diventar e una r egola quando scr ivete pr ocedur e funzioni o anche solo pezzi di codice più complessi o cr eati da voi ex novo (il che li r ende sconosciuti agli occhi altr ui). Vi faccio un piccolo esempio: 1. X = Math.Sqrt((1 - (Y ^ 2 / B ^ 2)) * A ^ 2) Questo potr ebbe esser e qualsiasi cosa: non c'è alcuna indicazione di cosa le letter e r appr esentino, nè del per chè venga effettuata pr opr io quell'oper azione. Ripr oviamo in questo modo, con il sor gente commentato, e vediamo se capite a cosa la for mula si r ifer isca: 01. 'Data l'equazione di un'ellisse: 02. 'x^2 y^2. 03. '--- + --- = 1 04. 'a^2 b^2 05. 'Ricava x: 06. 'x^2 / a^2 = 1 - (y^2 / b^2) 07. 'x^2 = (1 - (y^2 / b^2)) * a^2 08. 'x = sqrt((1 - (y^2 / b^2)) * a^2) 09. 'Prende la soluzione positiva: 10. X = Math.Sqrt((1 - (Y ^ 2 / B ^ 2)) * A ^ 2) Così è molto meglio: possiamo capir e sia lo scopo della for mula sia il pr ocedimento logico per mezzo del quale ci si è ar r ivati. Dare un nome Quando si cr eano nuovi contr olli all'inter no della w indow s for m, i lor o nomi vengono gener ati automaticamente tr amite un indice, pr eceduto dal nome della classe a cui il contr ollo appar tiene, come, ad esempio, Button1 o TabContr ol2. Se per applicazioni veloci, che devono svolger e pochissime, semplici oper azioni e per le quali basta una finestr a anche piccola, non c'è pr oblema a lasciar e i contr olli innominati in questo modo, quasi sempr e è utile, anzi, molto utile, r inominar li in modo che il lor o scopo sia compr ensibile anche da codice e che il lor o nome sia molto più facile da r icor dar e. Tr oppe volte vedo nei sor genti dei Tex tBox 3, Button34, ToolStr ipItem7 che non si sa cosa siano. A mio par er e, invece, è necessar io adottar e uno stile ben pr eciso anche per i nomi. Dal canto mio, utilizzo sempr e come nome una str inga composta per i pr imi tr e car atter i dalla sigla del tipo di contr ollo (ad esempio cmd o btn per i button, lst per le liste, cmb per le combobox , tx t per le tex tbox e così via) e per i r imanenti da par ole che ne descr ivano la funzione. Esemplificando, un pulsante che debba cr ear e un nuovo file si chiamer à btnNew File, una lista che debba contener e degli indir izzi di posta sar à lstEmail: quest'ultima notazione è detta "notazione ungher ese". A tal
  • 245. pr oposito, vi voglio sugger ir e quest'ar tico lo e vi invito caldam ente a legger lo. V ariabili E se il nome dei contr olli deve esser e accur ato, lo deve esser e anche quello delle var iabili. Pr ogr ammar e non è far e algebr a, non si deve cr eder e di poter usar e solo a, c, x , m, ki ecceter a. Le var iabili dovr ebber o aver e dei nomi significativi. Bisogna utilizzar e le stesse nor me sopr a descr itte, anche se a mio par er e il pr efisso per i tipi (obj è object, sng single, int integer ...) si può anche tr alasciar e. Risparmiare memoria Rispar miar e memor ia r ender à anche le oper azioni più semplici per il computer . Spesso è bene valutar e quale sia il tipo più adatto da utilizzar e, se Integer , Byte, Double, Object o altr i. Per ciò bisogna anche pr evedr e quali sar anno i casi che si potr ano ver ificar e. Se steste costr uendo un pr ogr amma che r iguar di la fisica, dovr este usar e numer i in vir gola mobile, ma quali? Più è alta la pr ecisioe da utilizzar e, più vi ser vir à spazio: se dovete r isolver e pr oblemi da liceo user ete il tipo Decimal (28 decimali, estensione da -7,9e+28 a 7,9e+28), o al massimo Single (38 decimali, estensione da -3,4e+38 a +3,4e+38), ma se state facendo calcoli specialistici ad esempio per un acceler ator e di par ticelle (megalomani!) avr este bisogno di tutta la potenza di calcolo necessar ia e user este quindi Double (324 decimali, estensione da -1,7e308 a +1,7e+308). Ricor datevi anche dell'esistenza dei tipi Unsigned, che vi per mettono di ottener e un'estensione di numer i doppia sopr a lo zer o, così UInt16 avr à tanti numer i positivi quanti Int32. Ricor date, inoltr e, di distr ugger e sempr e gli oggetti che utilizzate dopo il lor o ciclo di vita e di r ichiamar e il r ispettivo distr uttor e se ne hanno uno (Dispose). Il rasoio di Oc c am La soluzione più semplice è quella esatta. E per questo mi r ifer isco allo scr iver e anche in ter mini di lunghezza di codice. È inutile scr iver e funzioni lunghissime quando è possibile eguagliar le con pochissime r ighe di codice, più compatto, incisivo ed efficace. 01. Public Function Fattoriale(ByVal X as byte) As UInt64 02. If X = 1 Then 03. Return 1 04. Else 05. Dim T As UInt64 = 1 06. For I As Byte = 1 To X 07. T *= I 08. Next 09. Return T 10. End If 11. End Function 12. 13. 'Diventa: 14. 15. Public Function Fattoriale(ByVal X as byte) As UInt64 16. If X = 1 Then 17. Return 1 18. Else 19. Return X * Fattoriale(X - 1) 20. End If 21. End Function 01. Sub Copia(ByVal Da As String, ByVal A As String) 02. Dim R As <font class="keyword">New</font> IO.SreamReader(Da) 03. Dim W As <font class="keyword">New</font> IO.StreamWriter(A) 04.
  • 246. 05. W.Write(R.ReadToEnd) 06. 07. R.Close() 08. W.Close() 09. End Sub 10. 11. 'Diventa 12. 13. IO.File.Copy(Da, A) 14. 'Spesso anche il non conoscere tutte le possibilità 15. 'si trasforma in uno spreco di tempo e spazio Inc apsulamento L'incapsulamento è uno dei tr e fondamentali del par adigma di pr ogr ammazione ad Oggetti (gli altr i due sono polimor fismo ed er editar ietà, che abbiamo già tr attato). Come r icor der ete, all'inizio del cor so, ho scr itto che il vb.net pr esenta tr e aspetti peculiar i e ve li ho spiegati. Tuttavia essi non costituiscono il ver o par adigma di pr ogr ammazione ad oggetti e questo mi è stato fatto notar e da Netar r ow , che r ingr azio :P. Tr atter ò quindi, in questo momento tale ar gomento. Nonostante il nome possa sugger ir e un concetto difficile, non è complicato. Scr iver e un pr ogr amma usando l'incapsulamento significa str uttur ar lo in sezioni in modo tale che il cambiamento di una di esse non si r iper cuota sul funzionamento delle altr e. Facendo lo stesso esempio che por ta Wikipedia, potr este usar e tr e var iabili x , y e z per deter minar e un punto e poi cambiar e idea e usar e un ar r ay di tr e elementi. Se avete str uttur ato il pr ogr amma nella manier a suddetta, dovr este modificar e legger mente solo i metodi della sezione (modulo, classe o altr o) che è impegnata nella lor o modifica e non tutto il pr ogr amma. Convenzioni di denominazione Nei capitoli pr ecedenti ho spesse volte r ipor tato quali siano le "convenzioni" per la cr eazione di nomi appositi, come quelli per le pr opr ietà o per le inter facce. Esistono anche altr i canoni, stabiliti dalla Micr osoft, che dovr ebber o r ender e il codice miglior e in ter mini di velocità di lettur a e chiar ezza. Pr ima di elencar li, espongo una br eve ser ie di definizioni dei tipi di no m enclatur a usati: Pascal Case : nella notazione Pascal, ogni par te che for ma un nome deve iniziar e con una letter a maiuscola, ad esempio una var iabile che conetenga il per cor so di un file sar à FileName, o una pr ocedur a che analizza un oggetto sar à ScanObject. Si consiglia sempr e, in nomi composti da sostantivi e ver bi, di anticipar e i ver bi e posticipar e i sostantivi: il metodo per eseguir e la stampa di un documento sar à Pr intDocument e non DocumentPr int. Cam el Case : nella notazione camel, la pr ima par te del nome inizia con la letter a minuscola, e tutte le successive con una maiuscola. Ad esempio, il titolo di un libr o sar à bookTitle, o l'indir izzo di una per sona addr ess (un solo nome). No tazio ne Ung her e se : nella notazione ungher ese, il nome del membr o viene composto come in quella Pascal, ma è pr eceduto da un pr efisso alfanumer ico con l'iniziale minuscola che indica il tipo di membr o. Ad esempio una casella di testo (Tex tBox ) che contenga il nome di una per sona sar à txtName, o una lista di oggetti lstObject. A ll Case : nella notazione All, tutte le letter e sono maiuscole. Detto questo, le seguenti dir ettive specificano quando usar e quale tipo di notazione: Nome di un metodo : Pascal Campo di una classe : Pascal Nome di una classe : Pascal Membr i pubblici : Pascal
  • 247. Membr i pr otected : Pascal Campi di enumer ator i : Pascal Membr i pr ivati : Camel Var iabili locali : Camel Par ametr i : Camel Nomi di contr olli : Ungher ese Nomi costituiti da acr onimi: All se di 2 car atter i, altr imenti Pascal
  • 248. B1. IDE: Uno sguardo approfondito Fino ad or a ci siamo ser viti dell'ambiente di sviluppo integr ato - in br eve, IDE - come di un mer o suppor to per lo sviluppo di semplici applicazioni console: ne abbiamo fatto uso in quanto dotato di editor di testo "intelligente", un comodo debugger e un compilator e integr ato nelle funzionalità. E non potr ete negar e che anche solo per questo non ne avr emmo potuto far e a meno. Tuttavia, iniziando a sviluppar e applicazioni dotate di GUI (Gr aphical User Inter face) a finestr e, l'IDE diventa uno str umento ancor a più impor tante. Le sue numer ose featur es ci per mettono di "disegnar e" le finestr e del pr ogr amma, associar vi codice, navigar e tr a i sor genti, modificar e pr opr ietà con un click, or ganizzar e le var ie par ti dell'applicativo, ecceter a ecceter a... Pr ima di intr odur vi all'ambito Window s For ms, far ò una r apida panor amica dell'IDE, più appr ofondita di quella pr oposta all'inizio. Primo impatto Fate click su File > New Pr oject, scegliete "Window s For m Application" e, dopo aver scelto un qualsiasi nome per il pr ogetto, confer mate la scelta. Vi appar ir à una scher mata più o meno simile a quella che segue: Non vi allar mate se manca qualcosa, poiché i settaggi standar d dell'IDE sar anno molto pr obabilmente diver si da quelli che uso io. L'inter faccia dell'ambiente di sviluppo, comunque, è completamente customizzabile, dato che è anch'essa str uttur ata a finestr e: potete aggiunger e, r imuover e, fonder e o divider e finestr e semplicemente tr ascinandole con il mouse. Ecco un esempio di come manipolar e le par ti dell'IDE in questo v ideo . Menù princ ipale Il menù pr incipale è costituito dalla pr ima bar r a di voci appena sotto il bor do super ior e della finestr a. Esso per mette
  • 249. di acceder e ad ogni oper azione possibile all'inter no dell'IDE. Per or a ci ser vir emo di questi: File: il menù File per mette di cr ear e nuovi pr ogetti, salvar li, chiuder e quelli cor r enti e/o apr ir e file r ecenti; Edit: contiene le var ie oper azioni effettuabili all'inter no dell'editor di testo: taglia, copia, incolla, undo, r edo, cer ca, sostituisci, seleziona tutto ecceter a... View : i sottomenù consentono di nasconder e o visualizzar e finestr e: Code per mette di visualizzar e il codice sor gente associato a una finestr a; Designer por ta in pr imo piano l'ar ea r iser vata alla cr eazione delle finestr e; Database ex plor er per mette di navigar e tr a le tabelle di un database aper to nell'IDE; Solution Ex plor er consente di veder e le singole par ti del pr ogetto (vedi par agr afo successivo); Er r or List visualizza la finestr a degli er r or i e Pr oper ties Window quella delle pr opr ietà. Il sottomenù di Toolbar s per mette di aggiunger e o r imuover e nuove categor ie di pulsanti alla bar r a degli str umenti. Le altr e voci per or a non ci inter essano; Pr oject : espone alcune opzioni per il pr ogetto ed in par ticolar e consente di acceder e alle pr opr ietà di pr ogetto; Build : for nisce diver se opzioni per la compilazione del pr ogetto e/o della soluzione. Infine, cone Tools > Options, potr ete modificar e qualsiasi opzione r iguar dante l'ambiente di sviluppo, dal color e del testo nell'editor , agli spazi usati per l'indentazione, all'autosalvataggio, ecceter a... (non vale la pena di analizzar e tutte le voci disponibili, per chè sono ver amente tr oppe!).
  • 250. Solution Explorer La finestr a denominata "Solution Ex plor er " per mette di navigar e all'inter no della soluzione cor r ente e veder ne le var ie par ti. Una soluzione è l'insieme di due o più pr ogetti, o, se si tr atta di un pr ogetto singolo, coincide con esso. Come vedete ci sono cinque pulsanti sulla bar r a super ior e: il pr imo per mette di apr ir e una finestr a delle pr opr ietà per l'elemento selezionato; il secondo visualizza tutti i files fisicamente esistenti nella car tella della soluzione, il ter zo aggior na il solution ex plor er (nel caso di files aggiunti dall'ester no dell'IDE), mentr e quar to e quinto per mettono di passar e dal codice al visual designer e vicever sa. Pr emendo il secondo pulsante, potr emo ossevar e che c'è molto più di ciò che appar e a pr ima vista:
  • 251. La pr ima car tella contiene dei files che vanno a costr uir e uno dei namespace più utili in un'applicazione w indow s, M y, di cui ci occuper emo nella sezione C. La seconda car tella mostr a l'elenco di tutti i r ifer imenti inclusi nel pr ogetto: il numer o e il tipo di assembly impor tati var ia a seconda della ver sione dell'IDE e nella 2008 quelli elencati sono gli elementi di default. Per i nostr i pr ogetti, solamente tr e sar anno di vitale impor tanza: System, System.Dr aw ing e System.Window s.For ms. Potete r imuover e gli altr i senza pr eoccupazione (questo ci far à r ispar miar e anche un megabyte di RAM). La car tella bin contiene a sua volta una o due car telle (Debug e Release) in cui tr over ete il pr ogr amma compilato o in modalità debug o in modalità r elease. obj, invece, è dedicato ai file che contengono il codice oggetto, una ser ie di bytes molto simili al codice compilato, ma ancor a in attesa di esser e assemblati in un unico eseguibile. Dopo tutti questi elementi, che per or a ci inter essano poco, tr oviamo la For m1, di default la pr ima finestr a dell'applicazione. Possiamo notar e che esiste anche un altr o file, oltr e a For m1.vb (in cui è contenuto il codice che scr iviamo noi): For m1.Designer .vb. Quest'ultimo sor gente è pr odotto automaticamente dal Designer e contiene istr uzioni che ser vono a inizializzar e e costr uir e l'inter faccia gr afica; in esso sono anche dichiar ati tutti i contr olli che abbiamo tr ascinato sulla for m. Ogni for m, quindi, è costituita da due file sor genti diver si, i quali contengono, tuttavia, infor mazioni sulla stessa classe (For m1 in questo caso). Se r icor date, avevo detto che esiste una par ticolar e categor ia di classi che possono esser e scr itte su file diver si: le classi par ziali. In gener e, infatti, le for ms sono classi par ziali, in cui il codice "gr afico" viene tenuto nascosto e pr odotto dall'IDE e il codice di utilità viene scr itto dal pr ogr ammator e. Finestra delle proprietà Contiene un elenco di tutte le pr opr ietà dell'elemento selezionato nel designer e per mette di modificar le dir ettamente dall'ambiente di sviluppo. Il box sottostante contiene anche una br eve descr izione della pr opr ietà selezionata.
  • 252. Pr emendo il pulsante con l'icona del fulmine in cima alla finestr a, si apr ir à la finestr a degli Eventi, che contiene, appunto, una lista di tutti gli eventi che il contr ollo possiede (vedi pr ossimo capitolo).
  • 253. B2. Gli Eventi Cosa sono Or a che stiamo per entr ar e nel mondo della pr ogr ammazione visuale, è necessar io allontanar si da quello ster eotipo di applicazione che ho usato fin dall'inizio della guida. In questo nuovo contesto, "non esiste" una Sub Main (o, per meglio dir e, esiste ma possiede una semplice funzione di inizializzazione): il codice da eseguir e, quindi, non viene posto in un singolo blocco ed eseguito dall'inizio alla fine seguendo un flusso ben definito. Piuttosto, esiste un oggetto standar d, la For m - nome tecnico della finestr a - che viene cr eato all'avvio dell'applicazione e che l'utente può veder e e manipolar e a suo piacimento. L'appr occio cambia: il pr ogr ammator e non vincola il flusso di esecuzione, ma dice semplicemente al pr ogr amma "come compor tar si" in r eazione all'input dell'utente. Ad esempio, viene pr emuto un cer to pulsante: bene, al click esegui questo codice; viene inser ito un testo in una casella di testo: quando l'utente digita un car atter e, esegui quest'altr o codice, e così via... Il codice viene scr itto, quindi, per even ti. Volendo dar e una definizione teor ico- concettuale di evento, potr emmo dir e che è un qualsiasi atto che modifica lo stato attuale di un oggetto. Ho di pr oposito detto "oggetto", poiché le For ms non sono le uniche entità a posseder e eventi. Passando ad un ambito più for male e r igor oso, infatti, un evento non è altr o che una speciale var iabile di tipo delegate (multicast). Essendo di tipo delegate, tale var iabile può contener e r ifer imenti a uno o più metodi, i quali vengono comunemente chiamati g esto r i d'ev ento (o ev ent's handler ). La pr ogr ammazione visuale, in sostanza, r ichiede di scr iver e tanti gestor i d'evento quanti sono gli eventi che vogliamo gestir e e, quindi, tanti quanti possono esser e le azioni che il nostr o pr ogr amma consente all'utente di eseguir e. Mediante queste definizioni, delineamo il compor tamento di tutta l'applicazione. Sintassi e invoc azione degli eventi Le facilitazioni che l'IDE mette a disposizione per la scr ittur a dei gestor i d'evento por tano spesso i pr ogr ammator i novelli a non saper e cosa siano e come funzionino r ealmente gli eventi, anche a causa di una consider evole pr esenza di tutor ial del tipo HOW-TO che spiegano in due o tr e passaggi come costr uir e "il tuo pr imo pr ogr amma". Inutile dir e che queste scor ciatie fanno più male che bene. Per questo motivo ho deciso di intr odur r e l'ar gomento quanto pr ima, per metter vi subito al cor r ente di come stanno le cose. Iniziamo con l'intr odur r e la sintassi con cui si dichiar a un evento: 1. Event [Nome] As [Tipo] Dove [Nome] è il nome dell'evento e [Tipo] il suo tipo. Data la natur a di ciò che staimo definendo, il tipo sar à sempr e un tipo delegate. Possiamo sceglier e di utilizzar e un delegate già definito nelle libr er ie standar d del Fr amew or k - come il classico EventHandler - oppur e decider e di scr iver ne uno noi al momento. Scegliendo il secondo caso, tuttavia, si devono r ispettar e delle convenzioni: Il nome del delegate deve ter minar e con la par ola "Handler "; Il delegate deve espor r e solo due par ametr i; Il pr imo par ametr o è solitamente chiamato "sender " ed è comunemente di tipo Object. Questa convenzione è abbastanza r estr ittiva e non è necessar io seguir la sempr e; Il secondo par ametr o è solitamente chiamato "e" ed il suo tipo è una classe che er edita da System.EventAr gs. Allo stesso modo, possiamo definir e un nuovo tipo der ivato da tale classe per il nuovo delegate, ma il nome di questo tipo deve ter minar e con "EventAr gs". Come avr ete notato sono un po' fissato sulle convenzioni. Ser vono a r ender e il codice più chiar o e "standar d" (quando non ci sono r egole da seguir e, ognuno fa come meglio cr ede: vedi, ad esempio, i compilator i C). Ad ogni modo, sender r appr esenta l'oggetto che ha gener ato l'evento, mentr e e contiene tutte le infor mazioni r elative alle cir costanze in cui
  • 254. questo evento si è ver ificato. Se e è di tipo EventAr gs, non contiene alcun membr o: il fatto che l'evento sia stato gener ato è di per sé significativo. Ad esempio, per un ipotetico evento Click non avr emmo bisogno di conoscer e nessun'altr a infor mazione: ci basta saper e che è stato fatto click col mouse. Invece, per l'evento KeyDow n (pr essione di un tasto sulla tastier a) sar ebbe inter essante saper e quale tasto è stato pr emuto, il codice associato ad esso ed eventualmente il car atter e. Ma or a passiamo a un piccolo esempio sul pr imo caso, mantenendoci ancor a per qualche r iga in una Console Application: 001. Module Module1 002. 003. 'Questa classe rappresenta una collezione generica di 004. 'elementi che può essere ordinata con l'algoritmo 005. 'Bubble Sort già analizzato 006. Public Class BubbleCollection(Of T As IComparable) 007. 'Eredita tutti i membri pubblici e protected della classe 008. 'a tipizzazione forte List(Of T), il che consente di 009. 'disporre di tutti i metodi delle liste scrivendo 010. 'solo una linea di codice 011. Inherits List(Of T) 012. 013. 'Questo campo indica il numero di millisecondi impiegati 014. 'ad ordinare tutta la collezione 015. Private _TimeElapsed As Single = 0 016. 017. Public ReadOnly Property TimeElapsed() As Single 018. Get 019. Return _TimeElapsed 020. End Get 021. End Property 022. 023. 'Ecco gli eventi: 024. 'Il primo viene lanciato prima che inizi la procedura di 025. 'ordinamento, e per tale motivo è di tipo 026. 'CancelEventHandler. Questo delegate espone come 027. 'secondo parametro della signature una variabile "e" 028. 'al cui intero è disponibile una proprietà 029. 'Cancel che indica se cancellare oppure no l'operazione. 030. 'Se si volesse cancellare l'operazione sarebbe possibile 031. 'farlo nell'evento BeforeSorting. 032. 'In genere, si usa il tipo CancelEventHandler o un suo 033. 'derivato ogni volta che bisogna gestire un evento 034. 'che inizia un'operazione annullabile. 035. Event BeforeSorting As System.ComponentModel.CancelEventHandler 036. 'Il secondo viene lanciato dopo aver terminato la procedura 037. 'di ordinamento e serve solo a notificare un'azione 038. 'avvenuta. Il tipo è un semplicissimo EventHandler 039. Event AfterSorting As EventHandler 040. 041. 'Scambia l'elemento alla posizione Index con il suo 042. 'successivo 043. Private Sub SwapInList(ByVal Index As Int32) 044. Dim Temp As T = Me(Index + 1) 045. Me.RemoveAt(Index + 1) 046. Me.Insert(Index, Temp) 047. End Sub 048. 049. 'In List(Of T) è già presente un metodo Sort, 050. 'perciò bisogna oscurarlo con Shadows (in quanto non 051. 'è sovrascrivibile con il polimorfismo) 052. Public Shadows Sub Sort() 053. Dim Occurrences As Int32 054. Dim J As Int32 055. Dim Time As New Stopwatch 056. 'Attenzione! non bisogna confondere EventHandlers con 057. 'EventArgs: il primo è un tipo delegate e costituisce 058. 'il tipo dell'evento; il secondo è un normale tipo 059. 'reference e rappresenta tutti gli argomenti opzionali 060. 'inerenti alle operazioni svolte 061. Dim e As New System.ComponentModel.CancelEventArgs 062. 063.
  • 255. 'Viene generato l'evento. RaiseEvent si occupa di 064. 'richiamare tutti i gestori d'evento memorizzati 065. 'nell'evento BeforeSorting (che, ricordo, è un 066. 'delegate multicast). A tutti i gestori d'evento 067. 'vengono passati i parametri Me ed e. Al termine 068. 'di questa operazione, se un gestore d'evento ha 069. 'modificato una qualsiasi proprietà di e (e volendo, 070. 'anche di quest'oggetto), possiamo sfruttare tale 071. 'conoscenza per agire in modi diversi. 072. RaiseEvent BeforeSorting(Me, e) 073. 'In questo caso, se e.Cancel = True si 074. 'cancella l'operazione 075. If e.Cancel Then 076. Exit Sub 077. End If 078. 079. Time.Start() 080. J = 0 081. Do 082. Occurrences = 0 083. For I As Int32 = 0 To Me.Count - 1 - J 084. If I = Me.Count - 1 Then 085. Continue For 086. End If 087. If Me(I).CompareTo(Me(I + 1)) = 1 Then 088. SwapInList(I) 089. Occurrences += 1 090. End If 091. Next 092. J += 1 093. Loop Until Occurrences = 0 094. Time.Stop() 095. _TimeElapsed = Time.ElapsedMilliseconds 096. 097. 'Qui genera semplicemente l'evento 098. RaiseEvent AfterSorting(Me, EventArgs.Empty) 099. End Sub 100. 101. End Class 102. 103. '... 104. 105. End Module Questo codice mostr a anche l'uso dell'istr uzione RaiseEvent, usata per gener ar e un evento. Essa non fa altr o che scor r er e tutta l'invocation list di tale evento ed invocar e tutti i gestor i d'evento ivi contenuti. Le invocazioni si svolgono, di nor ma, una dopo l'altr a (sono sincr one). Or a che abbiamo ter minato la classe, tuttavia, bisogner ebbe anche poter la usar e, ma mancano ancor a due impor tanti infor mazioni per esser e in gr ado di gestir la cor r ettamente. Pr ima di tutto, la var iabile che user emo per contener e l'unica istanza di BubbleCollection deve esser e dichiar ata in modo diver so dal solito. Se nor malmente potr emmo scr iver e: 1. Dim Bubble As New BubbleCollection(Of Int32) in questo caso, non basta. Per poter usar e gli eventi di un oggetto, è necessar io comunicar lo esplicitamente al compilator e usando la keyw or d WithEvents, da antepor r e (o sostituir e) a Dim: 1. WithEvents Bubble As New BubbleCollection(Of Int32) Infine, dobbiamo associar e dei gestor i d'evento ai due eventi che Bubble espone (NB: non è obbligator io associar e handler a tutti gli eventi di un oggetto, ma basta far lo per quelli che ci inter essano). Per associar e un metodo a un evento e far lo diventar e gestor e di quell'evento, si usa la clausola Handles, molto simile come sintassi alla clausola Implements analizzata nei capitoli sulle inter facce. 1. Sub [Nome Gestore](ByVal sender As Object, ByVal e As [Tipo]) Handles [Oggetto].[Evento] 2. '... 3.
  • 256. End Sub I gestor i d'evento sono sempr e pr ocedur e e mai funzioni: questo è ovvio, poiché eseguono solo istr uzioni e nessuno r ichiede alcun valor e in r itor no da lor o. Ecco l'esempio completo: 001. Module Module1 002. 003. Public Class BubbleCollection(Of T As IComparable) 004. Inherits List(Of T) 005. 006. Private _TimeElapsed As Single = 0 007. 008. Public ReadOnly Property TimeElapsed() As Single 009. Get 010. Return _TimeElapsed 011. End Get 012. End Property 013. 014. Event BeforeSorting As System.ComponentModel.CancelEventHandler 015. Event AfterSorting As EventHandler 016. 017. Private Sub SwapInList(ByVal Index As Int32) 018. Dim Temp As T = Me(Index + 1) 019. Me.RemoveAt(Index + 1) 020. Me.Insert(Index, Temp) 021. End Sub 022. 023. Public Shadows Sub Sort() 024. Dim Occurrences As Int32 025. Dim J As Int32 026. Dim Time As New Stopwatch 027. Dim e As New System.ComponentModel.CancelEventArgs 028. 029. RaiseEvent BeforeSorting(Me, e) 030. If e.Cancel Then 031. Exit Sub 032. End If 033. 034. Time.Start() 035. J = 0 036. Do 037. Occurrences = 0 038. For I As Int32 = 0 To Me.Count - 1 - J 039. If I = Me.Count - 1 Then 040. Continue For 041. End If 042. If Me(I).CompareTo(Me(I + 1)) = 1 Then 043. SwapInList(I) 044. Occurrences += 1 045. End If 046. Next 047. J += 1 048. Loop Until Occurrences = 0 049. Time.Stop() 050. _TimeElapsed = Time.ElapsedMilliseconds 051. 052. 'Qui genera semplicemente l'evento 053. RaiseEvent AfterSorting(Me, EventArgs.Empty) 054. End Sub 055. 056. End Class 057. 058. 'Bubble è WithEvents poiché ne utilizzeremo 059. 'gli eventi 060. WithEvents Bubble As New BubbleCollection(Of Int32) 061. Sub Main() 062. Dim I As Int32 063. 064. Console.WriteLine("Inserire degli interi (0 per terminare):") 065. I = Console.ReadLine 066. Do While I <> 0 067.
  • 257. Bubble.Add(I) 068. I = Console.ReadLine 069. Loop 070. 071. 'Il corpo di Main termina con l'esecuzione di Sort, ma 072. 'il programma non finisce qui, poiché Sort 073. 'scatena due eventi, BeforeSorting e AfterSorting. 074. 'Questi comportano l'esecuzione prima del metodo 075. 'Bubble_BeforeSorting e poi di Bubble_AfterSorting. 076. 'Vedrete bene il risultato eseguendo il programma 077. Bubble.Sort() 078. End Sub 079. 080. Private Sub Bubble_BeforeSorting(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles Bubble.BeforeSorting 081. If Bubble.Count = 0 Then 082. e.Cancel = True 083. Console.WriteLine("Lista vuota!") 084. Console.ReadKey() 085. End If 086. End Sub 087. 088. Private Sub Bubble_AfterSorting(ByVal sender As Object, ByVal e As EventArgs) Handles Bubble.AfterSorting 089. Console.WriteLine("Lista ordinata:") 090. 'Scrive a schermo tutti gli elementi di Bubble 091. 'mediante un delegate generico. 092. Bubble.ForEach(AddressOf Console.WriteLine) 093. Console.WriteLine("Tempo impiegato: {0} ms", Bubble.TimeElapsed) 094. Console.ReadKey() 095. End Sub 096. 097. 'Handles significa "gestisce". In questo come in molti altri 098. 'casi, il codice è molto simile al linguaggio. 099. 'Ad esempio, traducendo in italiano si avrebbe: 100. ' Bubble_AfterSorting gestisce Bubble.AfterSorting 101. 'Il VB è molto chiaro nelle keywords 102. End Module Anche per i nomi dei gestor i d'evento c'è questa convenzione: "[Oggetto che gener a l'evento]_[Evento gestito]". Ciò che abbiamo appena visto consente di eseguir e una sor ta di ear ly binding, ossia legar e l'evento a un gestor e dur ante la scr ittur a del codice. C'è, par imenti, una tecnica par allela più simile al late binding, che consente di associar e un gestor e ad un evento dinamicamente. La sintassi è: 1. 'Add Handler = Aggiungi Gestore; molto intuitiva come keyword 2. AddHandler [Oggetto].[Evento], AddressOf [Gestore] 3. 'E per rimuovere il gestore dall'invocation list: 4. RemoveHandler [Oggetto].[Evento], AddressOf [Gestore] Il codice sopr a potr ebbe esser e stato modificato come segue: 01. Module Module1 02. 03. '... 04. 05. WithEvents Bubble As New BubbleCollection(Of Int32) 06. Sub Main() 07. Dim I As Int32 08. 09. AddHandler Bubble.BeforeSorting, AddressOf Bubble_BeforeSorting 10. AddHandler Bubble.AfterSorting, AddressOf Bubble_AfterSorting 11. 12. Console.WriteLine("Inserire degli interi (0 per terminare):") 13. I = Console.ReadLine 14. Do While I <> 0 15. Bubble.Add(I) 16. I = Console.ReadLine 17. Loop 18. 19. 'Il corpo di Main termina con l'esecuzione di Sort, ma 20.
  • 258. 'il programma non finisce qui, poiché Sort 21. 'scatena due eventi, BeforeSorting e AfterSorting. 22. 'Questi comportano l'esecuzione prima del metodo 23. 'Bubble_BeforeSorting e poi di Bubble_AfterSorting. 24. 'Vedrete bene il risultato eseguendo il programma 25. Bubble.Sort() 26. End Sub 27. 28. Private Sub Bubble_BeforeSorting(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) 29. If Bubble.Count = 0 Then 30. e.Cancel = True 31. Console.WriteLine("Lista vuota!") 32. Console.ReadKey() 33. End If 34. End Sub 35. 36. Private Sub Bubble_AfterSorting(ByVal sender As Object, ByVal e As EventArgs) 37. Console.WriteLine("Lista ordinata:") 38. Bubble.ForEach(AddressOf Console.WriteLine) 39. Console.WriteLine("Tempo impiegato: {0} ms", Bubble.TimeElapsed) 40. Console.ReadKey() 41. End Sub 42. End Module Ovviamente se usate questo metodo, non potr ete usar e allo stesso tempo anche Handles, o aggiunger este due volte lo stesso gestor e!
  • 259. B3. I Controlli La base delle applic azioni W indow s Form Se gli eventi sono il pr incipale meccanismo con cui scr iver e un'applicazione visuale, i contr olli sono i pr incipali oggetti da usar e. For malmente, un contr ollo non è altr o che una classe der ivata da System.Window s.For ms.Contr ol. In pr atica, esso r appr esenta un qualsiasi componente dell'inter faccia gr afica di un pr ogr amma: pulsanti, menù, caselle di testo, liste var ie, e anche le finestr e, sono tutti contr olli. Per questa r agione, se volete cr ear e una GUI (Gr aphical User Inter face) per il vostr o applicativo, dovr ete necessar iamente conoscer e quali contr olli le libr er ie standar d vi mettono a disposizione (e questo avviene in tutti i linguaggi che suppor tino libr er ie visuali). Conoscer e un contr ollo significa pr incipalmente saper e quali pr opr ietà, metodi ed eventi esso possiede e come usar li. Una volta aper to il pr ogetto Window s For m, tr over ete che l'IDE ha cr eato per noi la pr ima For m, ossia la pr ima finestr a dell'applicazione. Essa sar à la pr ima ad esser e aper ta quando il pr ogr amma ver r à fatto cor r er e e, per i pr ossimi capitoli, sar à anche l'unica che user emo. L'esecuzione ter mina automaticamente quando tale finestr a viene chiusa. Come avr ete visto, inoltr e, tr a le mer avigliose funzionalità del nostr o ambiente di sviluppo c'è anche un'ar ea gr afica - detta Designer - che ci per mette di veder e un'antepr ima della For m e di modificar la o aggiunger ci nuovi elementi. Per modificar e l'aspetto o il compor tamento della For m, è sufficiente modificar e le r elative pr opr ietà nella finestr a delle pr opr ietà Mentr e per aggiunger e elementi alla super ficie liber a della finestr a, è sufficiente tr ascinar e i contr olli desider ati dalla toolbox nel designer . La toolbox è di solito nascosta e la si può mostr ar e soffer mandosi un secondo sulla linguetta "Toolbox " che spunta fuor i dal lato sinistr o della scher mata dell'IDE: La c lasse Control La classe Contr ol è la classe base di tutti i contr olli (ma non è astr atta). Essa espone un buon numer o di metodi e pr opr ietà che vengono er editati da tutti i suoi der ivati. Tr a questi membr i di default, sono da r icor dar e: Allow Dr op : specifica se il contr ollo suppor ta il Dr ag and Dr op (per ulter ior i infor mazioni su questa tecnica, veder e capitolo r elativo); Anchor : pr opr ietà enumer ata codificata a bit (vedi capitolo sugli enumer ator i) che per mette di impostar e a quali lati del for m i cor r ispondenti lati del contr ollo r estano "ancor ati" dur ante il pr ocesso di r idimensionamento. Dir e che un un contr ollo è ancor ato a destr a, per esempio, significa che il suo lato destr o manter r à sempr e la stessa distanza dal lato destr o del suo contenitor e (il contenitor e per eccellenza è la For m stessa). Seguendo questa logica, ancor ando un contr ollo a tutti i lati, si otter r à come r isultato che quel contr ollo si ingr andir à quanto il suo contenitor e; BackColor : color e di sfondo; Backgr oundImage : immagine di sfondo; Contex tMenuStr ip : il menù contestuale associato al contr ollo; Contr ols : l'elenco dei contr olli contenuti all'inter no del contr ollo cor r ente. Un contr ollo può, infatti, far e da "contenitor e" per altr i contr olli. La finestr a, la For m, è un classico esempio di contenitor e, ma nel cor so delle lezioni vedr emo altr i contr olli specializzati e molto ver satili pensati apposta per questo compito; DoDr agDr op() : inizia un'oper azione di Dr ag and Dr op da questo contr ollo;
  • 260. Enabled : deter mina se il contr ollo è abilitato. Quando disabilitato, esso è di color e gr igio scur o e non è possibile alcuna inter azone tr a l'utente e il contr ollo stesso; Focus() : attiva il contr ollo; Focused : deter mina se il contr ollo è attivo; Font : car atter e con cui il testo viene scr itto sul contr ollo (se è pr esente del testo); For eColor : color e del testo; Height : altezza, in pix el, del contr ollo; Location : posizione del contr ollo r ispetto al suo contenitor e (r estituisce un valor e di tipo Point); MousePosition : posizione del mouse r ispetto al contr ollo (anche questa r estituisce un Point); Name : il nome del contr ollo (molto spesso coincide col nome della var iabile che r appr esenta quel contr ollo nel for m); Size : dimensione del contr ollo (r estituisce un valor e di tipo Size); TabIndex : for se non tutti sanno che con il pulsante Tab (tabulazione) è possibile scor r er e or dinatamente i contr olli. Ad esempio, in una finestr a con due caselle di testo, è possibile spostar si dalla pr ima alla seconda pr emendo Tab. Questo accade anche nei moduli Web. La pr opr ietà TabIndex deter mina l'indice associato al contr ollo in questo meccanismo. Così, se una casella di testo ha TabIndex = 0 e un menù a discesa TabIndex = 1, una volta selezionata la casella di testo sar à possibile spostar si sul menù a discesa pr emendo Tab. L'iter azione può continuar e indefinitamente per un qualsiasi numer o di contr olli e, una volta r aggiunta la fine, r einizia daccapo; Tag : qualsiasi oggetto associato al contr ollo. Tag è di tipo Object ed è molto utile per immagazzinar e infor mazioni di var io gener e che non è possibile por r e in nessun'altr a pr opr ietà; Tex t : testo visualizzato sul contr ollo (se il contr ollo pr evede del testo); Visible : deter mina se il contr ollo è visibile; Width : lar ghezza, in pix el, del contr ollo. La c lasse Form For m è la classe che r appr esenta una finestr a. Ogni finestr a che noi usiamo nelle applicazioni è r appr esentata da una classe der ivata da For m. Oltr e ai membr i di Contr ol, essa ne espone molti altr i. Ecco una lista molto sintetica di alcuni membr i che potr ebber o inter essar vi ad or a: Allow Tr anspar ency : deter mina se il for m può esser e r eso tr aspar ente (vedi pr opr ietà Opacity); AutoScr oll : deter mina se sulla finestr a venga automaticamente mostr ata una bar r a di scor r imento quando i contr olli che essa contiene spor gono oltr e il suo bor do visibile; Close() : chiude la for m. Se si tr atta della pr ima for m, l'applicazione ter mina (è possibile modificar e questo compor tamento, come vedr emo in seguito); For mBor der Style : imposta il tipo di bor do della finestr a (nessuno, singolo, doppio: singolo equivale a non poter r idimensionar e la finestr a); HelpButton : deter mina se il pulsante help (?) è visualizzato nella bar r a del titolo, accanto agli altr i; Hide() : nasconde la for m, ossia la r ende invisibile, ma non la chiude; Icon : indica l'icona mostr ata nell'angolo super ior e sinistr o della finestr a, vicino al titolo. Questà pr opr ietà è di tipo System.Dr aw ing.Icon; Max imizeBox : deter mina se l'icona che per mette di ingr andir e la finestr a a scher mo inter o è visualizzata; Max imumSize : massima dimensione consentita; MinimizeBox : deter mina se il pulsante che per mette di r idur r e la finestr a a icona è visualizzato; MinimumSize : minima dimensione consentita; Opacity : imposta l'opacità della finestr a: 0 per r ender la invisibile, 1 per r ender la totalmente opaca (nor male);
  • 261. Show () : visualizza la for m nel caso sia nascosta o comunque non attualmente visibile sullo scher mo; Show Dialog() : come Show (), ma la finestr a viene mostr ata in modalità Dialog. In questo modo, l'utente può inter agir e solo con essa e con nessun'altr a for m del pr ogr amma fino a che questa non sia stata chiusa, confer mando una scelta o annullando l'oper azione. Restituisce come r isultato un valor e enumer ato che indica che azione l'utente abbia compiuto; Show Icon : deter mina se visualizzar e l'icona nella bar r a del titolo; Show InTaskBar : deter mina se visualizzar e la finestr a nella bar r a delle applicazioni; TopMost : deter mina se la finestr a è sempr e in pr imo piano; Window State : indica lo stato della finestr a (nor male, massimizzata, r idotta a icona). Questi sono solo alcuni dei molteplici membr i che la classe espone. Ho elencato sopr attutto quelli che vi per metter anno di modificar e l'aspetto ed il compor tamento della for m, in quanto, allo stato attuale delle cose, non siete in gr ado di gestir e e compr ender e il r esto delle funzionalità. Nel cor so di questa sezione, comunque, intr odur r ò via via nuovi dettagli r iguar do questa classe e spiegher ò come usar li. Ma or a passiamo alla scr ittur a del pr imo pr ogr amma... Il c ontrollo Button Per il pr ossimo esempio, dovr emo usar e un nuovo contr ollo, che possiamo indicar e senza r emor e come il pr incipale e più usato meccanismo di inter azione: il pulsante. Esso viene r appr esentato dal contr ollo Button. Dopo aver aper to un nuovo pr ogetto Window s For m vuoto, tr ascinate un nuovo pulsante dalla toolbox sulla super ficie della finestr a e posizionatelo dove più vi aggr ada. Il nome di questo contr ollo sar à btnHello, ad esempio. Or a che abbiamo disposto l'unico elemento della GUI, bisogna cr ear e un gestor e d'evento che si occupi di eseguir e del codice quando l'utente clicca sul pulsante. Per far e ciò, possiamo scr iver e il codice a mano o semplicemente far e doppio click sul pulsante nel Designer e l'IDE scr iver à automaticamente il codice associato. Questo succede per chè ogni contr ollo ha un "evento di default", ossia quell'evento che viene usato più spesso: il doppio click su un elemento dell'inter faccia gr afica ci per mette di delegar e all'ambiente di sviluppo la stesur a del pr ototipo per la Sub che dovr emo cr ear e per tale evento. Nel caso di Button, l'evento più usato è Click. Il codice automaticamente gener ato sar à: 1. Private Sub btnHello_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnHello.Click 2. 3. End Sub Or a, all'inter no del cor po della pr ocedur a possiamo por r e ciò che vogliamo. In questo esempio, visualizzer emo a scher mo il messaggio "Hello, Wor ld!", ma in modo diver so dalle applicazioni console. In questo ambiente, si è soliti usar e una par ticolar e classe che ser ve per visualizzar e finestr e di avver timento. Tale classe è MessageBox e ha un solo metodo statico, Show : 1. Public Class Form1 2. 3. Private Sub btnHello_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnHello.Click 4. MessageBox.Show("Hello, World!", "Esempio", MessageBoxButtons.OK, MessageBoxIcon.Information) 5. End Sub 6. 7. End Class Show accetta come minimo un par ametr o, ossia il messaggio da visualizzar e. Tutti gli altr i par ametr i sono "opzionali" (non nel ver o senso del ter mine, ma esisteono 18 ver sioni diver se dello stesso metodo Show modificate tr amite over loading). In questo caso, il secondo indica il titolo della finestr a di avviso, il ter zo i pulsanti visualizzati (un solo pulsante "OK") ed il quar to l'icona mostr ata in fianco al messaggio (una "I" bianca su sfondo blu, che significa "Infor mazione").
  • 263. B4. Label e TextBox In questo capitolo mi occuper ò di altr i due comunissimi contr olli: label (etichetta) e tex tbox (casella di testo). L'esempio della lezione consiste nello scr iver e un pr ogr amma che, dato il r aggio, calcola l'ar ea del cer chio. Label Il contr ollo Label ser ve per visualizzar e un qualsiasi messaggio o testo sulla super ficie della w indow s for m. Per questo pr ogetto, occor r e aggiunger e una label all'inter no del for m designer e impostar e il testo su "Intr odur r e il r aggio di un cer chio:". Poichè questo tipo di contr ollo è utilizzatissimo, è inutile assegnar e un nome significativo a ogni sua istanza, a meno che non la si debba modificar e dur ante l'esecuzione del pr ogr amma. Solo due pr opr ietà mer itano di esser e menzionate: AutoSize : se attiva, r idimensiona la label per ader ir e alla lunghezza del testo. Il r idimensionamento avviene solo in lunghezza, a meno che il testo non contenga esplicitamente un car atter e "a capo". Se disattivata, invece, il testo della label ver r à automaticamente spostato per r ientr ar e nei limiti imposti dalla sua dimensione; Tex tAlign : per mette di allinear e il testo in 9 modi diver si, combinando i tr e valor i di allineamente ver ticale (Top, Center , Bottom) con i tr e valor i di allineamento or izzontale (Left, Center , Right). L'allineamento non è effettivo se AutoSize = Tr ue. Dopo aver modificato le pr opr ietà della for m come nella lezione scor sa, l'inter faccia si pr esenter à pr essapoco così: TextBox Costituisce il contr ollo di input per eccellenza, il più usato in tutte quelle situazioni che r ichiedono all'utente di immetter e dati. Le pr opr ietà r ilevanti sono: Max Length : massima lunghezza del testo, in car atter i; AutoCompleteMode : modalità di autocompletamento. Fr a i pr egi della Tex tBox vi è la possibilità di "sugger ir e" all'utente cosa digitar e nel caso le pr ime letter e pr emute cor r ispondano all'inizio di una delle par ole che il pr ogr amma ha già elabor ato. Per far e un esempio pr atico, si compor ta allo stesso modo del sistema di composizione T9 dei cellular i, in cui il r esto della par ola viene "sugger ita" pr ima del suo completamento. L'enumer ator e può assumer e quattr o valor i: None (assente), Suggest (viene sugger ita la par ola facendo appar ir e sotto la tex tbox un menù a discesa con tutte le possibili var ianti), Append (viene sugger ita la par ola accodando alle letter e digitate il pezzo mancante evidenziato il blu), AppendSuggest (un'unione di entr ambe le pr ecedenti opzioni); AutoCompleteSour ce : fonte dalla quale pr elevar e le par ole dell'autocompletamento. I valor i pr edefiniti indicano
  • 264. r isor se di sistema, quali la cr onologia (Histor yList, nel caso, ad esempio, la tex tbox funga da contenitor e di indir izzi inter net), le car telle (FileSystemDir ector ies, ad esempio per facilitar e l'immissione di un per cor so da tastier a), i file (FileSystem), i files o i pr ogr ammi aper ti di r ecente (RecentlyUsedList), oppur e tutti questi insieme (AllSystemResour ces). Se impostato su CustomSour ce, sar à la pr opr ietà AutoCompleteCustomSour ce a deter minar e la fonte da cui attinger e infor mazioni; Char acter Casing : indica il casing delle letter e. Ci sono tr e valor i possibili: None (tutte le letter e vengono lasciate così come sono), Upper (tutte le letter e sono conver tite in maiuscole) o Low er (tutte in minuscole); Lines : r estituisce un ar r ay di str inghe r appr esentanti tutte le r ighe di testo della tex tbox , nel caso di una tex tbox Multiline; Multiline : se impostata su Tr ue, la tex tbox sar à r idimensionabile e l'utente potr à inser ir e un testo che compr ende più r ighe. Quando la pr opr ietà è False, il car atter e "a capo" viene r espinto; Passw or dChar : un valor e di tipo Char che deter mina il car atter e da visualizzar e al posto delle letter e qualor a la tex tbox debba contener e una passw or d. In questo modo si evita che occhi indiscr eti possano intr aveder e le str inghe digitate. Impostando questa pr opr ietà, si mascher a automaticamente il testo; ReadOnly : deter mina se l'utente può modificar e il testo della tex tbox ; Scr ollBar s : pr opr ietà enumer ata che specifica se le bar r e di scor r imento devono esser e pr esenti. L'enumer ator e accetta quattr o valor i: None (nessuna scr ollbar ), Ver tical (solo ver ticale), Hor izontal (solo or izzontale), Both (entr ambe); Or a aggiungiamo una tex tbox di nome tx tRadius, appena sotto la label. Finire il programma di c alc olo Ultima cosa essenziale per concluder e il pr ogr amma è un pulsante che avvii il calcolo, altr imenti non si potr ebbe saper e quando l'utente ha finito l'immissione e vuole conoscer e il r isultato. Dopo aver aggiunto il button btnAr ea, la finestr a sar à simile a questa: Doppio click sul pulsante per apr ir e l'editor di codice sull'evento Click di btnAr ea: 01. Public Class Form1 02. 03. Private Sub btnArea_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnArea.Click 04. Dim Radius As Single = txtRadius.Text 05. Dim Area As Single 06. Area = Radius ^ 2 * Math.PI 07. MessageBox.Show("L'area del cerchio è " & Area & ".", Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Information) 08. End Sub 09. 10. End Class Pr ima di far cor r er e il pr ogr amma, bisogna r icor dar si che i numer i decimali immessi in input devono aver e la vir gola, e non il punto. Da notar e che abbiamo assegnato una str inga a un valor e single: come già detto, in VB.NET, le conver sioni implicite vengono eseguite automaticamente quando sono possibili e Option Str ict è disattivata.
  • 265. Tuttavia, se l'utente immettesse una par ola, il pr ogr amma andr ebbe in cr ash: vediamo quindi di r affinar e il codice così da inter cettar e l'eccezione gener ata. 01. Public Class Form1 02. 03. Private Sub btnArea_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnArea.Click 04. Try 05. Dim Radius As Single = txtRadius.Text 06. Dim Area As Single 07. Area = Radius ^ 2 * Math.PI 08. MessageBox.Show("L'area del cerchio è " & Area & ".", Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Information) 09. Catch ICE As InvalidCastException 10. MessageBox.Show("Inserire un valore numerico valido!", Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Error) 11. End Try 12. End Sub 13. 14. End Class Ma non basta ancor a. I numer i negativi o nulli vengono comunque accetati, ma per definizione una lunghezza non può aver e misur a non positiva, per ciò: 01. Public Class Form1 02. 03. Private Sub btnArea_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnArea.Click 04. Try 05. Dim Radius As Single = txtRadius.Text 06. 07. If Radius <= 0 Then 08. Throw New ArgumentException() 09. End If 10. 11. Dim Area As Single 12. Area = Radius ^ 2 * Math.PI 13. MessageBox.Show("L'area del cerchio è " & Area & ".", Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Information) 14. Catch ICE As InvalidCastException 15. MessageBox.Show("Inserire un valore numerico valido!", Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Error) 16. Catch AE As ArgumentException 17. MessageBox.Show("Il raggio non può essere negativo o nullo!", Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Exclamation) 18. End Try 19. End Sub 20. 21. End Class
  • 266. B5. Input e Output su file Gli Stream Le oper azioni di input e output, in .NET come in molti altr i linguaggi, hanno come tar get uno str eam, ossia un flusso di dati. In .NET, tale flusso viene r appr esentato da una classe astr atta, System.IO.Str eam, che espone alcuni metodi per acceder e e manipolar e i dati ivi contenuti. Dato che si tr atta di una classe astr atta, non possiamo utilizzar la dir ettamente, poiché, appunto, r appr esenta un concetto astr atto non istanziabile. Come già spiegato nel capitolo r elativo, classi del gener e r appr esentano un ar chetipo per diver se altr e classi der ivate. Infatti, un flusso di dati può esser e tante cose, e pr ovenir e da molti posti diver si: può tr attar si di un file, come vedr emo fr a poco; allor a la classe der ivata oppor tuna sar à FileStr eam; può tr attar si di dati gr ezzi pr esenti in memor ia, ed avr emo, ad esempio, Memor yStr eam; potr ebbe tr attar si, invece, di un flusso di dati pr oveniente dal ser ver a cui siamo collegati, e ci sar à allor a, un Netw or kStr eam; e così via, per molti diver se casistiche... Globalmente par lando, quindi, si può associar e uno str eam al flusso di dati pr oveniente da un qualsiasi dispositivo vir tuale o fisico o da qualunque entità astr atta all'inter no della macchina: ad esempio è possibile aver e uno str eam associato a una stampante, a uno scanner , allo scher mo, ad un file, alla memor ia tempor anea, a qualsiasi altr a cosa. Per ognuno di questi casi, esister à un'oppor tuna classe der ivata di Str eam studiata per adempier e a quello specifico compito. In questo capitolo, vedr emo cinque classi del gener e, ognuna altamente specializzata: FileStr eam, Str eamReader , Str eamWr iter , Binar yReader e Binar yWr iter . FileStream Questa classe offr e funzionalità gener iche per l'accesso a un file. Il suo costr uttor e più semplice accetta due par ametr i: il pr imo è il per cor so del file a cui acceder e ed il secondo indica le modalità di aper tur a. Quest'ultimo par ametr o è di tipo IO.FileMode, un enumer ator e che contiene questi campi: Append : apr e il file e si posiziona alla fine (in questo modo, potr emo velocemente aggiun gere dati senza sovr ascr iver e quelli pr ecedentemente esistenti); Cr eate : cr ea un nuovo file con il per cor so dato nel pr imo par ametr o; se il file esiste già, sar à sovr ascr itto; Cr eateNew : cr ea un nuovo file con il per cor so dato nel pr imo par ametr o del costr uttor e; se il file esiste già, ver r à sollevata un'eccezione; Open : apr e il file e si posiziona all'inizio; OpenOr Cr eate : apr e il file, se esiste, e si posiziona all'inizio; se non esiste, cr ea il file; Tr uncate : apr e il file, cancella tutto il suo contenuto, e si posiziona all'inizio. Un ter zo par ametr o opzionale può specificar e i per messi (solo lettur a, solo scr ittur a o entr ambe), ma per or a non lo user emo. Pr ima di veder e un esempio del suo utilizzo, è necessar io dir e che questa classe consider a i file aper ti come file binar i. Si par la di file binar io quando esiste una cor r ispondenza biunivoca tr a i bytes esistenti in esso e i dati letti. Questa condizione non si ver ifica con i file di testo, in cui, ad esempio, il singolo car atter e "a capo" cor r isponde a due bytes: in questo caso non si può par lar e di file binar i, ma è comunque possibile legger li come tali, e ciò che si otter r à sar à solo
  • 267. una sequenza di numer i. Ma vedr emo meglio queste differ enze nel par agr afo successivo. Or a, ammettendo di aver e aper to il file, sia che si voglia legger e, sia che si voglia scr iver e, sar à necessar io adottar e un buffer, ossia un ar r ay di bytes che conter r à tempor aneamente i dati letti o scr itti. Tutti i metodi di lettur a/scr ittur a binar i del Fr amew or k, infatti, r ichiedono come minimo tr e par ametr i: buffer : un ar r ay di bytes in cui por r e i dati letti o da cui pr elevar e i dati da scr iver e; index : indice del buffer da cui iniziar e l'oper azione; length : numer o di bytes da pr ocessar e. Seguendo questa logica, avr emo la funzione Read: Read(buffer, index, length) che legge length bytes dallo str eam aper to e li pone in buffer (a par tir e da index ); e, par imenti, la funzione Wr ite: Write(buffer, index, length) che scr ive sullo str eam length bytes pr elevati dall'ar r ay buffer (a par tir e da index ). Ecco un esempio: 01. Module Module1 02. 03. Sub Main() 04. Dim File As IO.FileStream 05. Dim FileName As String 06. 07. Console.WriteLine("Inserire il percorso di un file:") 08. FileName = Console.ReadLine 09. 10. 'IO.File.Exists(path) restituisce True se il percorso 11. 'path indica un file esistente e False in caso contrario 12. If Not IO.File.Exists(FileName) Then 13. Console.WriteLine("Questo file non esiste!") 14. Console.ReadKey() 15. Exit Sub 16. End If 17. 18. Console.Clear() 19. 20. 'Apre il file specificato, posizionandosi all'inizio 21. File = New IO.FileStream(FileName, IO.FileMode.Open) 22. 23. Dim Buffer() As Byte 24. Dim Number, ReadBytes As Int32 25. 26. 'Chiede all'utente quanti bytes vuole leggere, e 27. 'memorizza tale numero in Number 28. Console.WriteLine("Quanti bytes leggere?") 29. Number = CType(Console.ReadLine, Int32) 30. 'Se Number è un numero positivo e non siamo ancora 31. 'arrivati alla fine del file, allora legge quei bytes. 32. 'La proprietà Position restituisce la posizione 33. 'corrente all'interno del file (a iniziare da 0), mentre 34. 'File.Length restituisce la lunghezza del file, in bytes. 35. Do While (Number > 0) And (File.Position < File.Length - 1) 36. 'Ridimensiona il buffer 37. ReDim Buffer(Number - 1) 38. 'Legge Number bytes e li mette in Buffer, a partire 39. 'dall'inizio dell'array. Read è una funzione, e 40. 'restituisce come risultato il numero di bytes 41. 'effettivamente letti dallo stream. 42. ReadBytes = File.Read(Buffer, 0, Number) 43. 44. Console.WriteLine("Bytes letti:") 45. For I As Int32 = 0 To ReadBytes - 1 46. Console.Write("{0:000} ", Buffer(I)) 47.
  • 268. Next 48. Console.WriteLine() 49. 50. 'Se abbiamo letto tanti bytes quanti ne erano stati 51. 'chiesti, allora non siamo ancora arrivati alla 52. 'fine del file. Richiede all'utente un numero 53. If ReadBytes = Number Then 54. Console.WriteLine("Quanti bytes leggere?") 55. Number = CType(Console.ReadLine, Int32) 56. End If 57. Loop 58. 59. 'Controlla se si è raggiunta la fine del file. 60. 'Infatti, il ciclo potrebbe terminare anche se l'utente 61. 'immettesse 0. 62. If File.Position >= File.Length - 1 Then 63. Console.WriteLine("Raggiunta fine del file!") 64. End If 65. 66. 'Chiude il file 67. File.Close() 68. 69. Console.ReadKey() 70. End Sub 71. 72. End Module Bisogna sempr e r icor dar si di chiuder e il flusso di dati quando si è finito di utilizzar lo. FileStr eam, e in gener ale anche Str eam, implementa l'inter faccia IDisposable e il metodo Close non è altr o che un modo indir etto per r ichiamar e Dispose (a cui, comunque, possiamo far e r icor so). Allo stesso modo, possiamo usar e la funzione Wr ite per scr iver e dati, oppur e Wr iteByte per scr iver e un byte alla volta. Come avr ete notato, la classe Str eam espone anche delle pr opr ietà in sola lettur a come CanRead, CanWr ite e CanSeek. Infatti, non tutti i flussi di dato suppor tano tutte le oper azioni di lettur a, scr ittur a e r icer ca: un esempio può esser e il Netw or kStr eam (che analizzer emo nella sezione dedicata al Web) associato alle r ichieste http, il quale non suppor ta le oper azioni di r icer ca e r estituisce un er r or e se si pr ova ad utilizzar e il metodo Seek. Questo metodo ser ve per spostar si velocemente da una par te all'altr a del flusso di dati, e accetta solo due ar gomenti: Seek(offset, origin) offset è un inter o che specifica la posizione a cui r ecar si, mentr e or igin è un valor e enumer ato di tipo IO.SeekOr igin che può assumer e tr e valor i: Begin (si r ifer isce all'inizio del file), Cur r ent (si r ifer isce alla posizione cor r ente) ed End (si r ifer isce alla fine del file). Ad esempio: 1. 'Si sposta alla posizione 100 2. File.Seek(100, IO.SeekOrigin.Begin) 3. 'Si sposta di 250 bytes indietro rispetto alla posizione corrente 4. File.Seek(-250, IO.SeekOrigin.Current) 5. 'Si sposta a 100 bytes dalla fine del file 6. File.Seek(-100, IO.SeekOrigin.End) Cer to che legger e e scr iver e dati un byte alla volta non è molto comodo. Vediamo, allor a, la pr ima categor ia di file: i file testuali. Lettura/sc rittura di file testuali I file testuali sono così denominati per chè contengono solo testo, ossia bytes codifcabili in una delle codifiche standar d dei car atter i (ASCII, UTF-8, ecceter a...). Alcuni par ticolar i bytes vengono intepr etati in modi diver si, come ad esempio la tabulazione, che viene r appr esentata con uno spazio più lungo; altr i vengono tr alasciati nella visualizzazione e sembr ano non esister e, ad esempio il NULL ter minator , che r appr esenta la fine di una str inga, oppur e l'EOF (End Of File); altr i ancor a vengono pr esi a gr uppi, come il car atter e a capo, che in r ealtà è for mato da una sequenza di due
  • 269. bytes (Car r iage Retur n e Line Feed, r ispettivamente 13 e 10). La differ enza insita in questi tipi di file r ispetto a quelli binar i è il fatto di non poter legger e i singoli bytes per chè non ce n'è necessità: quello che impor ta è l'infor mazione che il testo por ta al suo inter no. La classe usata per la lettur a è Str eamReader , mentr e quella per la scr ittur a Str eamWr iter : il costr uttor e di entr ambi accetta un unico par ametr o, ossia il per cor so del file in questione; esistono anche altr i over loads dei costr uttor i, ma il più usato e quindi il più impor tante di tutti è quello appena citato. Ecco un piccolo esempio di come utilizzar e tali classi in una semplice applicazione console: 01. Module Module1 02. Sub Main() 03. Dim File As String 04. Dim Mode As Char 05. 06. Console.WriteLine("Premere R per leggere un file, W per scriverne uno.") 07. 'Console.ReadKey restituisce un oggetto ConsoleKeyInfo, 08. 'al cui interno ci sono tre proprietà: Key, 09. 'enumeratore che definisce il codice del pulsante premuto; 10. 'KeyChar, il carattere corrispondente a quel pulsante; 11. 'Modifier, enumeratore che definisce i modificatori attivi, 12. 'ossia Ctrl, Shift e Alt. 13. 'Quello che serve ora è solo KeyChar 14. Mode = Console.ReadKey.KeyChar 15. 'Dato che potrebbe essere attivo il Bloc Num, ci si 16. 'assicura che Mode contenga un carattere maiuscolo 17. 'con la funzione statica ToUpper del tipo base Char 18. Mode = Char.ToUpper(Mode) 19. 'Pulisce lo schermo 20. Console.Clear() 21. 22. Select Case Mode 23. Case "R" 24. Console.WriteLine("Inserire il percorso del file da leggere:") 25. File = Console.ReadLine 26. 27. 'Cosntrolla che il file esista 28. If Not IO.File.Exists(File) Then 29. 'Se non esiste, visualizza un messggio ed esce 30. Console.WriteLine("Il file specificato non esiste!") 31. Console.ReadKey() 32. Exit Sub 33. End If 34. 35. Dim Reader As New IO.StreamReader(File) 36. 37. 'Legge ogni singola riga del file, fintanto che non 38. 'si è raggiunta la fine 39. Do While Not Reader.EndOfStream 40. 'Come Console.Readline, la funzione d'istanza 41. 'ReadLine restituisce una linea di testo 42. 'dal file 43. Console.WriteLine(Reader.ReadLine) 44. Loop 45. 46. 'Quindi chiude il file 47. Reader.Close() 48. Case "W" 49. Console.WriteLine("Inserire il percorso del file da creare:") 50. File = Console.ReadLine 51. 52. Dim Writer As New IO.StreamWriter(File) 53. Dim Line As String 54. 55. Console.WriteLine("Immettere il testo del file, " & _ 56. "premere due volte invio per terminare") 57. 'Fa immettere righe di testo fino a quando 58. 'si termina 59. Do 60. Line = Console.ReadLine 61. 'Come Console.WriteLine, la funzione d'istanza 62. 'WriteLine scrive una linea di testo sul file 63.
  • 270. Writer.WriteLine(Line) 64. Loop While Line <> "" 65. 66. 'Chiude il file 67. Writer.Close() 68. Case Else 69. Console.WriteLine("Comando non valido!") 70. End Select 71. 72. Console.ReadKey() 73. End Sub 74. End Module Ovviamente esistono anche i metodi Read e Wr ite, che scr ivono del testo senza mandar e a capo: inoltr e, Wr ite e Wr iteLine hanno degli over loads che accettano anche str inghe di for mato come quelle viste nei capitoli pr ecedenti. Come si è visto, le classi analizzate (e quelle che andr emo a veder e tr a br eve) hanno metodi molti simili a quelli di Console: questo per chè anche la console è uno str eam, capace di input e output allo stesso tempo. Per color o che pr ovengono dal C non sar à difficile r ichiamar e questo concetto. Lettura/sc rittura di file binari Come già accennato nel par agr afo pr ecedente, la distinzione tr a file binar i e testuali avviene tr amite l'inter pr etazione dei singoli bytes. Con questo tipo di file, c'è una cor r ispondenza biunivoca tr a i bytes del file e i dati letti dal pr ogr amma: infatti, non a caso, l'I/O viene gestito attr aver so un ar r ay di byte. Binar yWr iter e Binar yReader espongono, oltr e alle canoniche Read e Wr ite già analizzate per FileStr eam, altr e pr ocedur e di lettur a e scr ittur a, che, di fatto, scendono a più basso livello. Ad esempio, all'inizio della guida ho illustr ato alcuni tipi di dato basilar i, r ipor tando anche la lor o gr andezza (in bytes). Integer occupa 4 bytes, Int16 ne occupa 2, Single he occupa 4 e così via. Valor i di tipo base vengono quindi salvati in memor ia in notazione binar ia, r ispettando quella specifica dimensione. Or a, esistono modi ben definiti per conver tir e un numer o in base 10 in una sequenza di bit facilmente manipolabile dall'elabor ator e: mi r ifer isco, ad esempio, alla notazione in complemento a 2 per gli inter i e al for mato in vir gola mobile per i r eali. Potete documentar vi su queste modalità di r appr esentazione dell'infor mazione altr ove: in questo momento ci inter essa saper e che i dati sono "pensati" dal calcolator e in manier a diver sa da come li concepiamo noi. Binar yWr iter e Binar yReader sono classi appositamente cr eate per far da tr amite tr a ciò che capiamo noi e ciò che capisce il computer . Pr opr io per chè sono dei "mezzi", il lor o costr uttor e deve specificar e lo str eam (già aper to) su cui lavor ar e. Ecco un esempio: 01. Module Module1 02. 03. Sub Main() 04. 'Apre il file "prova.dat", creandolo o sovrascrivendolo 05. Dim File As New IO.FileStream("prova.dat", IO.FileMode.Create) 06. 'Writer è lo strumento che ci permette di scrivere 07. 'sullo stream File con codifica binaria 08. Dim Writer As New IO.BinaryWriter(File) 09. Dim Number As Int32 10. 11. Console.WriteLine("Inserisci 10 numeri da scrivere sul file:") 12. For I As Int32 = 1 To 10 13. Console.Write("{0}: ", I) 14. Number = CType(Console.ReadLine, Int32) 15. Writer.Write(Number) 16. Next 17. Writer.Close() 18. 19. Console.ReadKey() 20. End Sub 21. 22. End Module Io ho inser ito questi numer i: -10 -5 0 1 20 8000 19001 -345 90 22. Pr ovando ad apr ir e il file con un editor di testo
  • 271. vedr ete solo car atter i str ani, in quanto questo non è un file testuale. Apr endolo, invece, con un editor esadecimale, otter r ete questo: f6 ff ff ff fb ff ff ff 00 00 00 00 01 00 00 00 14 00 00 00 40 lf 00 00 39 4a 00 00 a7 fe ff ff 5a 00 00 00 16 00 00 00 Ogni gr uppetto di quattr o bytes r appr esenta un numer o inter o codificato in binar io. Potr emmo far e la stessa cosa con Single, Double, Date, Boolean, Str ing e altr i tipi base per veder e cosa succede. Binar yWr iter e Binar yReader sono molto utili quando bisogna legger e dati in codifica binar ia, ad esempio per molti famosi for mati di file, come mp3, w av (vedi sezione FFS), zip, mpg, ecceter a... Esempio: steganografia su immagini La steganogr afia è l'ar te di nasconder e del testo all'inter no di un'immagine. Per i più cur iosi, mi avventur er ò nella scr ittur a di un semplicissimo pr ogr amma di steganogr afia su immagini, nascondendo del testo al lor o inter no. Per pr ima cosa, si costr uisca l'inter faccia gr afica, con questi contr olli: Una Label, Label1, Tex t = "Intr odur r e il per cor so di un'immagine:" Una Tex tBox , tx tPath, cone AutoCompleteMode = Suggest e AutoCompleteSour ce = FileSystem. In questo modo, la tex tbox sugger ir à il nome di file e car telle esistenti mentr e state digitando, r endendo più semplice l'indtr oduzione del per cor so; Una Tex tBox , tx tTex t, Scr ollBar s = Both, MultiLine = Tr ue Un Button, btnHide, Tex t = "Nascondi" Un Button, btnRead, Tex t = "Leggi" Ed ecco il codice ampiamente commentato: 01. Public Class Form1 02. 03. Private Sub btnHide_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnHide.Click 04. If Not IO.File.Exists(txtPath.Text) Then 05. MessageBox.Show("File inesistente!", Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Error) 06. Exit Sub 07. End If 08. 09. If IO.Path.GetExtension(txtPath.Text) <> ".jpg" Then 10. MessageBox.Show("Il file deve essere in formato JPEG!", Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Exclamation) 11.
  • 272. Exit Sub 12. End If 13. 14. Dim File As New IO.FileStream(txtPath.Text, IO.FileMode.Open) 15. 'Converte il testo digitato in una sequenza di bytes, 16. 'secondo gli standard della codifica UTF8 17. Dim TextBytes() As Byte = _ 18. System.Text.Encoding.UTF8.GetBytes(txtText.Text) 19. 20. 'Va alla fine del file 21. File.Seek(0, IO.SeekOrigin.End) 22. 'Scrive i bytes 23. File.Write(TextBytes, 0, TextBytes.Length) 24. File.Close() 25. 26. MessageBox.Show("Testo nascosto con successo!", Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Information) 27. End Sub 28. 29. Private Sub btnRead_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnRead.Click 30. If Not IO.File.Exists(txtPath.Text) Then 31. MessageBox.Show("File inesistente!", Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Error) 32. Exit Sub 33. End If 34. 35. If IO.Path.GetExtension(txtPath.Text) <> ".jpg" Then 36. MessageBox.Show("Il file deve essere in formato JPEG!", Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Exclamation) 37. Exit Sub 38. End If 39. 40. Dim File As New IO.FileStream(txtPath.Text, IO.FileMode.Open) 41. Dim TextBytes() As Byte 42. Dim B1, B2 As Byte 43. 44. 'Legge un byte 45. B1 = File.ReadByte() 46. Do 47. 'Legge un altro byte 48. B2 = File.ReadByte() 49. 'Se i bytes formano la sequenza FF D9, si ferma. 50. 'In Visual Basic, in numeri esadecimali si scrivono 51. 'facendoli precedere da "&H" 52. If B1 = &HFF And B2 = &HD9 Then 53. Exit Do 54. End If 55. 'Passa il valore di B2 in B1 56. B1 = B2 57. Loop While (File.Position < File.Length - 1) 58. 59. ReDim TextBytes(File.Length - File.Position - 1) 60. 'Legge ciò che rimane dopo FF D9 61. File.Read(TextBytes, 0, TextBytes.Length) 62. File.Close() 63. 64. txtText.Text = System.Text.Encoding.UTF8.GetString(TextBytes) 65. End Sub 66. End Class Il testo accodato può esser e r ilevato facilmente con un Hex Editor , per questo lo si dovr ebbe cr iptar e con una passw or d: per ulter ior i infor mazioni sulla cr iptazione in .NET, veder e capitolo r ekativo.
  • 273. B6. ListBox e ComboBox Questi contr olli sono liste con stile visuale pr opr io in gr ado di contener e elementi. La gestione di tali elementi è molto simile a quella delle List gener ic o degli Ar r ayList. L'unica differ enza sta nel fatto che in questo caso, tutte le modifiche vengono r ese visibili sull'inter faccia e influiscono, quindi, su ciò che l'utente può veder e. Una volta aggiunte alla w indow s for m, il lor o aspetto sar à simile a questo: ListBo x Co m bo Box Le pr opr ietà più inter essanti sono: Solo per ListBox : ColumnWidth : indica la lar ghezza delle colonne in una listbox in cui MultiColumn = Tr ue. Lasciar e 0 per il valor e di default Hor izontalEx tent : indica di quanti pix el è possibile scor r er e la listbox in or izzontale, se la scr ollbar or izzontale è attiva Hor izontalScr ollbar : deter mina se attivar e la scr ollbar or izzontale. Di solito, questa pr opr ietà viene impostata a Tr ue quando la listbox dispone di più colonne MultiColumn : deter mina se la listbox è a più colonne. In questa modalità, una volta ter minata l'altezza della lista, gli elementi vengono posizionati di lato anzichè sotto, ed è quindi possibile visualizzar li spostandosi a destr a o a sinistr a. Un esempio visuale:
  • 274. ListBo x M ultiCo lum n Scr ollAlw aysVisible : deter mina se le scr ollbar vengono visualizzate sempr e, indipendentemente dal numer o di elementi pr esenti. Infatti quando questa pr opr ietà è disabilitata, se gli elementi sono pochi e possono esser e posizionati nell'ar ea della lista senza nasconder ne nessuno, non viene visualizzata la scr ollbar , che appar e quando gli elementi cominciano a diventar e tr oppi. Con questa pr opr ietà attiva, essa è sempr e visibile e, se inutilizzata, si disabilita automaticamente SelectionMode : pr opr ietà enumer ata che deter mina in quale modo sia possibile selezionar e gli elementi. Può assumer e quattr o valor i: None (non è possibile selezionar e niente), One (un solo elemento alla volta), MultiSimple (più elementi selezionabili con un click), MultiEx tended (più elementi, selezionabili solo tenendo pr emuto Ctr l e spostando il mouse sopr a di essi) Solo per ComboBox : AutoComplete... : tutte le pr opr ietà il cui nome inizia per "AutoComplete" sono uguali a quelle citate nella lezione pr ecedente Dr opDow nHeight : deter mina l'altezza, in pix el, del menù a discesa Dr opDow nStyle : deter mina lo stile del menù a discesa. Può assumer e tr e valor i: Simple (il menù a discesa è sempr e visibile, e può esser e assimilato a una listbox ), Dr opDow n (stile nor male come nell'immagine di esempio pr oposta a inizio capitolo, ma è possibile modificar e il testo dell'elemento selezionato scr ivendo entr o la casella), Dr opDow nList (stile nor male, non è possibile modificar e l'elemento selezionato in alcun modo, se non selezionandone un altr o). Questa è un'immagine di una combobox con Dr opDow nStyle = Simple: Co m bo Box Sim ple Dr o pDo w n FlatStyle : lo stile visuale della ComboBox . Può assumer e quattr o valor i: Flat o Popup (la combobox è gr igia e schiacciata, senza contor ni 3D), System o Pr ofessional (la combobox è azzur r a e r ilevata, con contor ni 3D) Max Dr opDow nItems : il numer o massimo di elementi visualizzabili nel menù a discesa Max Length : deter mina il massimo numer o di car atter i di testo che possono esser e inser iti come input nella casella della combobox . Questa pr opr ietà ha senso solo se Dr opDow nStyle non è impostata su Dr opDow nList, poichè tale stile impedisce di modificar e il contenuto della combobox tr amite tastier a, come già detto Per entr ambe le liste: Dr aw Mode : deter mina la modalità con cui ogni elemento viene disegnato. Può assumer e tr e valor i: Nor mal, Ow ner Dr aw Fix ed e Ow ner Dr aw Var iable. Il pr imo è quello di default; il secondo ed il ter zo specificano che i contr olli devono esser e disegnati da una speciale pr ocedur a definita dal pr ogr ammator e nell'evento Dr aw Item. Per ulter ior i infor mazioni su questo pr ocedimento, veder e l'ar ticolo Fo nt e
  • 275. diseg ni nelle liste nella sezione Appunti. For matStr ing : dato che queste liste possono contener e anche numer i e date (e altr i oggetti, ma non è consigliabile aggiunger e tipi diver si da quelli base), la pr opr ietà For matStr ing indica come tali valor i debbano esser e visualizzati. Cliccando sul pulsante con i tr e puntini nella finestr a delle pr opr ietà su questa voce, appar ir à una finestr a di dialogo con i seguenti for mati standar d: No For matting, Numer ic, DateTime e Scientific. For matEnabled : deter mina se è abilitata la for mattazione degli elementi tr amite For matStr ing Integr alHeight : quando attiva, questa pr opr ietà for za la lista ad assumer e un valor e di altezza (Size.Height) che sia un multiplo di ItemHeight, in modo tale che gli elementi siano sempr e visibili inter amente. Se disattivata, gli elementi possono anche venir e "tagliati" fuor i dalla lista. Un esempio: Lista co n Integ r alHe ig ht = False ItemHeight : altezza, in pix el, di un elemento Items : collezione a tipizzazione debole di tutti gli elementi. Gode di tutti i metodi consueti delle liste, quali Add, Remove, Index Of, Inser t, ecceter a... Sor ted : indica se gli elementi devono esser e or dinati alfabeticamente Detto ciò, è possibile pr oceder e con un semplice esempio. Il pr ogr amma che segue per mette di aggiunger e un qualsiasi testo ad una lista. Pr ima di iniziar e a scr iver e il codice, bisogna includer e nella w indow s for m questi contr olli: Una listbox , di nome lstItems Un pulsante, di nome cmdAdd, con Tex t = "Aggiungi" Un pulsante, di nome cmdRemove, con Tex t = "Rimuovi" Il codice: 01. Public Class Form1 02. Private Sub cmdAdd_Click(ByVal sender As Object, _ 03. ByVal e As EventArgs) Handles cmdAdd.Click 04. Dim S As String 05. 06. 'Inputbox( 07. ' ByVal Prompt As Object, 08. ' ByVal Title As String, 09. ' ByVal DefaultResponse As String) 10. 'Visualizza una finestra con una label esplicativa 11. 'il cui testo è racchiuso in Prompt, con un titolo 12. 'Title e una textbox con un testo di default 13. 'DeafultResponse: una volta che l'utente ha inserito 14. 'la stringa nella textbox e cliccato OK, la funzione 15. 'restituisce la stringa immessa 16. S = InputBox("Inserisci una stringa:", "Inserimento stringa", _ 17. "[Stringa]") 18. 19. 'Aggiunge la stringa alla lista 20. lstItems.Items.Add(S) 21. End Sub 22. 23.
  • 276. Private Sub cmdRemove_Click(ByVal sender As Object, _ 24. ByVal e As EventArgs) Handles cmdRemove.Click 25. 'Se è selezionato un elemento... 26. If lstItems.SelectedIndex >= 0 Then 27. 'Lo elimina 28. lstItems.Items.RemoveAt(lstItems.SelectedIndex) 29. End If 30. End Sub 31. End Class Non solo stringhe Nell'esempio pr ecedente, ho mostr ato che è possibile aggiunger e agli elementi della listbox delle str inghe, ed esse ver r anno visualizzate come testo sull'inter faccia del contr ollo. Tuttavia, la pr opr ietà Items è di tipo ObjectCollection, quindi può contener e un qualsiasi tipo di oggetto e non necessar iamente solo str inghe. Quello che ci pr eoccupa, in questo caso, è ciò che viene mostr ato all'utente qualor a noi inser issimo un oggetto nella listbox : quale testo sar à visualizzato per l'elemento? Ecco un esempio (un for m con una listbox e un pulsante): 01. Class Form1 02. 03. Class Item 04. Private Shared IDCounter As Int32 = 0 05. 06. Private _ID As Int32 07. Private _Description As String 08. 09. Public ReadOnly Property ID() As Int32 10. Get 11. Return _ID 12. End Get 13. End Property 14. 15. Public Property Description() As String 16. Get 17. Return _Description 18. End Get 19. Set(ByVal value As String) 20. _Description = value 21. End Set 22. End Property 23. 24. Sub New() 25. _ID = IDCounter 26. IDCounter += 1 27. End Sub 28. 29. End Class 30. 31. Private Sub btnDoSomething_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnDoSomething.Click 32. lstItems.Items.Add(New Item() With {.Description = "Asus Eee PC 900"}) 33. lstItems.Items.Add(New Item() With {.Description = "Hp Pavillion Dv6000"}) 34. End Sub 35. 36. End Class Una volta pr emuto btnDoSomething, nella lista ver r anno aggiunti due oggetti, tuttavia la GUI della listbox visualizzer à questi due elementi: WindowsApplication4.Form1+Item WindowsApplication4.Form1+Item Questo nel mio caso, poiché il pr ogetto (e quindi il namespace pr incipale) si chiama Window sApplication4. Da ciò si può capir e che, in assenza d'altr o, la listbox tenta di conver tir e l'oggetto in una str inga, ossia un dato
  • 277. intellegibile all'uomo: l'unico modo per poter avviar e questa conver sione consiste nell'utilizzar e il metodo ToStr ing, il quale, tuttavia, non è stato r idefinito dalla classe Item e pr ovoca l'uso del suo omonimo der ivante dalla classe base Object. Quest'ultimo, infatti, r estituisce il tipo dell'oggetto, che in questo caso è pr opr io Window sApplication4.For m+Item. Per modificar e il compor tamento del contr ollo, dobbiamo aggiunger e alla classe un metodo ToStr ing, ad esempio così: 1. Class Item 2. '... 3. 4. Public Overrides Function ToString() As String 5. Return Me.Description 6. End Function 7. End Class Avviando di nuovo l'applicazione, gli elementi visualizzati sulla lista sar anno: Asus Eee PC 900 Hp Pavillion Dv6000 Esiste, tuttavia, un altr o modo per ottener e lo stesso r isultato senza dover r idefinir e il metodo ToStr ing. Questa seconda alter nativa si dimostr a par ticolar mente utile quando non possiamo acceder e o modificar e il codice della classe di cui stiamo usando istanze: ad esempio per chè si tr atta di una classe definita in un assembly diver so. Possiamo specificar e nella pr opr ietà ListBox .DisplayMember il nome della pr opr ietà che deve ser vir e a visualizzar e l'elemento nella lista. Nel nostr o caso, vogliamo visualizzar e la descr izione, quindi user emo questo codice: 1. lstItems.DisplayMember = "Description" 2. lstItems.Items.Add(New Item() With {.Description = "Asus Eee PC 900"}) 3. lstItems.Items.Add(New Item() With {.Description = "Hp Pavillion Dv6000"}) Ed otter r emo lo stesso r isultato. Par allelamente, c'è anche la pr opr ietà ValueMember , che per mette di specificar e quale pr opr ietà dell'oggetto deve esser e r estituita quando si r ichiede il valor e di un elemento selezionato mediante la pr opr ietà SelectedValue.
  • 278. B7. CheckBox e RadioButton Mentr e ListBox e ComboBox mir avano a r ender e visuale un insieme di elementi, questi due contr olli r appr esentano una valor e Booleano: infatti possono esser e spuntati oppur e no. Chec kBox La CheckBox è la classica casella di spunta, che si può segnar e con un segno di spunta (tick). Le pr opr ietà car atter istiche sono poche: Appear ance : pr opr ietà enumer ata che deter mina come la checkbox viene visualizzata. Il pr imo valor e, Nor mal, specifica che deve esser ci una casellina di spunta con il testo a fianco; il secondo valor e, Button, specifica che deve esser e r ender izzata come un contr ollo Button. In questo secondo caso, se Checked è Tr ue il pulsante appar e pr emuto, altr imenti no AutoCheck : deter mina se la checkbox cambia automaticamente stato (ossia da spuntata a non spuntata) quando viene cliccata. Se questa pr opr ietà è False, l'unico modo per cambiar e la spunta è tr amite codice AutoEllipsis : se Appear ance = Button, questa pr opr ietà deter mina se il contr ollo si debba automaticamente r idimensionar e sulla base del pr opr io testo CheckAlign : se Appear ance = Nor mal, deter mina in quale posizione della checkbox si tr ovi la casellina di spunta Checked : indica se la checkbox è spuntata oppur e no CheckState : per le checkbox a tr e stati, indica lo stato cor r ente FlatStyle : deter mina lo stile visuale del testo attr aver so un enumer ator e a quattr o valor i, come nelle combobox Tex tAlign : allineamento del testo Tex tImageRelation : deter mina la r elazione testo-immagine, qualor a la pr opr ietà Image sia impostata. Può assumer e diver si valor i che specificano se il testo sia sotto, sopr a, a destr a o a sinistr a dell'immagine Thr eeState : deter mina se la checkbox suppor ta i tr e stati. In questo caso, le combinazioni possibili sono tr e, ossia: spuntato, senza spunta e indeter minato. Può esser e utile per offr ir e una gamma di scelta più ampia o per implementar e visualmente la logica booleana a tr e valor i Ecco un esempio di tutte le possibili combinazioni di checkbox : In definitiva, la CheckBox r ende visuale il legame Or tr a più condizioni. RadioButton A differ enza di CheckBox , RadioButton può assumer e solo due valor i, che non sono sempr e accessibili. La spiegazione di questo sta nel fatto che solo un RadioButton può esser e spuntato allo stesso tempo in un dato contenitor e. Ad esempio, in una finestr a che contenga tr e di questi contr olli, spuntando il pr imo, il secondo ed il ter zo sar anno depennati; spuntando il secondo lo sar anno il pr imo ed il ter zo e così via. Tale meccanismo è del tutto automatico e aiuta moltissimo nel caso si debbano pr opor r e all'utente scelte non sovr apponibili. Gode di tutte le pr opr ietà di CheckBox , tr anne ovviamente Thr eeState e CheckState, e r appr esenta visualmente il legame Xor tr a più condizioni.
  • 279. GroupBox Par lando di contenitor i, non si può non far e menzione al Gr oupBox . Tr a tutti i contenitor i disponibili, Gr oupBox è il più semplice dotato di inter faccia gr afica pr opr ia. La sua funzione consiste unicamente nel r aggr uppar e in uno spazio solo più contr olli uniti da un qualche legame logico, ad esempio tutti quelli iner enti alla for mattazione del testo. Oltr e a r ender e la str uttur a della finestr a più or dinata, dà un tocco di stile all'applicazione e, cosa più impor tante, può condizionar e lo stato di tutti i suoi membr i (o contr olli figli). Dato che gode solamente delle pr opr ietà comuni a tutte le classi der ivate da Contr ol, la modifica di una di esse si r iper cuoter à su tutti i contr olli in esso contenuti. Di solito si sfr utta questa peculiar ità per disabilitar e o r ender e invisibile un gr uppo di elementi. L'inter faccia si pr esenta in questo modo:
  • 280. B8. NumericUpDown e DomainUpDown Numeric UpDow n Questo contr ollo tor na utile quando si vuole pr opor r e all'utente una scelta di un numer o, inter o o decimale, compr eso tr a un minimo e un massimo. Ad esempio, il semplice pr ogr amma che andr ò a illustr ar e in questo capitolo chiede di indovinar e un numer o casuale da 0 a 100 gener ato dal computer . Con l'uso di una tex tbox , l'utente potr ebbe commetter e un er r or e di battitur a e inser ir e in input car atter i non validi, mandando così in cr ash il pr ogr amma: la soluzione potr ebbe esser e usar e un Tr y, ma si spr echer ebbe spazio, o un contr ollo Masked Tex tBox , ma in altr i casi potr ebbe r isultar e limitativo o pedante, dato che r ichiede un pr eciso numer o di car atter i immessi. Usando invece una combobox o una listbox si dovr ebber o aggiunger e manualmente tutti i numer i con un for , spr ecando spazio nel codice. La soluzione ideale sar ebbe far e uso di Numer icUpDow n. Le pr opr ietà car atter istiche: DecimalPlaces : i posti decimali dopo la vir gola. Se impostata a 0, sar à possibile immetter e solo numer i inter i Hex adecimal : deter mina se visualizzar e il numer o in notazione esadecimale (solo per numer i inter i positivi) Incr ement : il fattor e di incr emento/decr emento automaticamente aggiunto/sottr atto quando l'utente clicca sulle fr ecce del contr ollo Inter ceptAr r ow Key : deter mina se il contr ollo debba inter cettar e e inter pr etar e la pr essione delle fr ecce dir ezionali su/giù da testier a Max imum : massimo valor e numer ico Minimum : minimo valor e numer ico ThousandSepar ator : indica se visualizzar e il separ ator e delle migliaia Value : il valor e indicato UpDow nAlign : la posizione delle fr ecce sul contr ollo Dopo aver posizionato questi contr olli: Una Label Label1, Tex t = "Clicca Gener a per gener ar e un numer o casuale, quindi pr ova a indovinar e!" Un pulsante cmdGener ate, Tex t = "Gener a" Un pulsante cmdTr y, Tex t = "Pr ova" Un Numer icUpDow n nudValue, con le pr opr ietà standar d Una Label lblNumber , Tex t = "***", Font = Micr osoft Sans Ser if Gr assetto 16pt, AutoSize = False, Tex tAlign = MiddleCenter Disponeteli in modo simile a questo: Ed ecco il codice: 01. Class Form1 02. 'Il numero da indovinare 03. Private Number As Byte 04. 'Determina se l'utente ha già dato la sua risposta 05. Private Tried As Boolean 06. 'Crea un nuovo oggetto Random in grado di generare numeri 07. 'casuali 08. Dim Rnd As New Random() 09. 10. Private Sub cmdGenerate_Click(ByVal sender As System.Object, _ 11. ByVal e As System.EventArgs) Handles cmdGenerate.Click 12. 'Genera un numero aleatorio tra 0 e 99 e lo deposita in 13.
  • 281. 'Number 14. Number = Rnd.Next(0, 100) 15. 'Imposta Tried su False 16. Tried = False 17. 'Nasconde la soluzione 18. lblNumber.Text = "***" 19. End Sub 20. 21. Private Sub cmdTry_Click(ByVal sender As System.Object, _ 22. ByVal e As System.EventArgs) Handles cmdTry.Click 23. 'Se si è già provato, esce dalla procedura 24. If Tried Then 25. MessageBox.Show("Hai già fatto un tentativo! Premi " & _ 26. "Genera e prova con un altro numero!", "Numeri a caso", _ 27. MessageBoxButtons.OK, MessageBoxIcon.Exclamation) 28. Exit Sub 29. End If 30. 31. 'Se NumericUpDown corrisponde al numero generato, 32. 'l'utente vince 33. If nudValue.Value = Number Then 34. MessageBox.Show("Complimenti, hai vinto!", "Numeri a caso", _ 35. MessageBoxButtons.OK, MessageBoxIcon.Information) 36. Else 37. MessageBox.Show("Risposta sbagliata!", "Numeri a caso", _ 38. MessageBoxButtons.OK, MessageBoxIcon.Exclamation) 39. End If 40. 41. 'Ormai l'utente ha fatto la sua scelta 42. Tried = True 43. 'Fa vedere la soluzione 44. lblNumber.Text = Number 45. End Sub 46. End Class DomainUpDow n Questo contr ollo è molto simile come stile gr afico a quello appena analizzato solo che, anzichè visualizzar e numer i in successione, visualizza semplici elementi testuali come le liste dei capitoli pr ecedenti. È una specie di incr ocio fr a questi tipi di contr ollo: gode delle pr opr ietà Minimum e Max imum, ma anche della pr opr ietà Items, che stabilisce la lista or dinata di elementi da cui pr elevar e le str inghe.
  • 282. B9. PictureBox e ProgressBar Pic tureBox La Pictur eBox è uno di quei contr olli visibili solamente nel designer , poichè i suoi contor ni, di default, sono invisibili. L'unica car atter istica che la r ende visibile a r untime è la sua pr opr ietà fondamentale, Image. Infatti, questo contr ollo può contener e un'immagine: di solito viene usata per posizionar e loghi, banner o scr itte all'inter no dell'inter faccia di un pr ogr amma. Le pr opr ietà più impor tanti sono: Er r or Image : l'immagine visualizzata qualor a non sia possibile car icar e un'immagine con la pr opr ietà Image Image : l'immagine visualizzata InitialImage : l'immagine visualizzata all'inizio, pr ima che sia impostata qualsiasi altr a immagine con la pr opr ietà Image SizeMode : modalità di r idimensionamento dell'immagine. Può assumer e cinque valor i: Nor mal (l'immagine r imane delle dimensioni nor mali, e ignor a ogni r idimensionamento della pictur ebox : per questo può anche venir e tagliata), Str etchImage (l'immagine si r idimensiona a seconda della pictur ebox , assumendone le stesse dimensioni), AutoSize (la pictur ebox si r idimensiona sulla base dell'immagine contenuta), Center Image (l'immagine viene sempr e posta al centr o della pictur ebox , ma mantiene le pr opr ie dimensioni iniziali), Zoom (l'immagine si r idimensiona sulla base della pictur ebox , ma mantiene sempr e lo stesso r appor to tr a lar ghezza e altezza) Er r or Image, Image e InitialImage sono pr opr ietà di tipo Image: quest'ultima è una classe astr atta e quindi non esiste mai ver amente, anche se espone comunque dei metodi statici per il car icamento delle immagini. La classe che r appr esenta ver amente e mater ialmente l'immagine è System.Dr aw ing.Bitmap, o solo Bitmap per gli amici. Nonostante il nome sugger isca diver samente, essa fa da w r apper a un numer o elevato di for mati di immagini, tr a cui bmp, gif, jpg, png, ex if, emf, tiff e w mf. In questo capitolo user ò tale classe in modo molto par ticolar e, quindi è meglio pr ima analizzar ne i membr i: Classe astr atta Image Fr omFile(File) : car ica un'immagine da File e ne r estituisce un'istanza Fr omStr eam(Str eam) : car ica un'immagine dallo str eam Str eam e ne r estituisce un'istanza (per ulter ior i infor mazioni sugli str eam, veder e capitolo 56) Fr omHbitmap : car ica un'immagine a par tir e da un puntator e che punta al suo indir izzo in memor ia Hor izontalResolution : r isoluzione sull'asse x , in pix els al pollice (=2.54cm) Pix elsFor mat : r estituisce il for mato dell'immagine, sottofor ma di enumer ator e Raw For mat : r estituisce il for mato dell'immagine, in un oggetto ImageFor mat RotateFlip(F) : r uota e/o inver te l'immagine secondo il for mato F, esposto da un enumer ator e codificato a bit Save(File) : salva l'immagine sul file File: l'estensione del file influenzer à il metodo di scr ittur a dell'immagine Size : dimensione dell'immagine Ver ticalResolution : r isoluzione sull'asse y, in pix els al pollice Classe der ivata Bitmap GetPix el(X, Y) : r estituisce il color e del pix el alle coor dinate (X, Y), r ifer ite al mar gine super ior e sinistr o MakeTr anspar ent(C) : r ende il color e C tr aspar ente su tutta l'immagine SetPix el(X, Y, C) : imposta il color e del pix el alle coor dinate (X, Y) a C
  • 283. SetResolution(x R, yR) : imposta la r isoluzione or izzontale su x R e quella ver ticale su yR, entr ambe misur ate in punti al pollice ProgressBar La Pr ogr essBar è la classica bar r a di car icamento, usata per visualizzar e sull'inter faccia lo stato di un'oper azione. Le pr opr ietà pr incipali sono poche: Max imum : il valor e massimo r appr esentabile dal contr ollo Minimum : il valor e minimo r appr esentabile dal contr ollo Step : valor e che definisce il valor e di incr emento quando viene r ichiamata il metodo Per for mStep Style : pr opr ietà enumer ata che indica lo stile della bar r a. Può assumer e tr a valor i: Block (a blocchi), Continuos (i blocchi possono venir e tagliati, a seconda delle per centuale) e Mar quee (un blocchetto che si muove da sinistr a a destr a, che r appr esenta quindi un'oper azione in cor so della quale non si sa lo stato) Value : il valor e r appr esentato Esempio: Bianc o e nero L'esempio di questa lezione è un pr ogr amma capace di car icar e un'immagine, conver tir la in bianco e ner o, e poi r isalvar la sullo stesso o su un altr o file. I contr olli da usar e sono: Una Pictur eBox , imgPr eview , ancor ata a tutti i bor di, con SizeMode = Str ecthImage Un Button, cmdLoad, Tex t = "Car ica", Anchor = Left Or Bottom Un Button, cmdSave, Tex t = "Salva", Anchor = Bottom Un Button, cmdConver t, Tex t = "Conver ti", Anchor = Right Or Bottom Una Pr ogr essBar , pr gConver t, Style = Continuos Disposti come in figur a: Ecco il codice: 01. Class Form1 02. 'Funzione che converte un colore in scala di grigio 03. Private Function ToGreyScale(ByVal C As Color) As Color 04. 'Per convertire un colore in scala di grigio è sufficiente 05. 'prendere le sue componenti di rosso, verde e blu (red, 06. 'green e blue), farne la media aritmetica e quindi 07. 'assegnare tale valore alle nuove coordinate RGB del 08. 'colore risultante 09. 10. 'Ottiene le componenti (coordinate RGB) 11. Dim Red As Int32 = C.R 12. Dim Green As Int32 = C.G 13. Dim Blue As Int32 = C.B 14. 'Fa la media 15. Dim Grey As Int32 = (Red + Green + Blue) / 3 16. 17. 'Quindi crea un nuovo colore, mettendo tutte le 18. 'componenti uguali alla media ottenuta 19. Return Color.FromArgb(Grey, Grey, Grey) 20. End Function 21. 22. Private Sub cmdLoad_Click(ByVal sender As System.Object, _ 23. ByVal e As System.EventArgs) Handles cmdLoad.Click 24. 'Per ulteriori informazioni sui controlli OpenFileDialog e 25.
  • 284. 'SaveFileDialog vedere capitolo relativo 26. Dim Open As New OpenFileDialog 27. Open.Filter = "File immagine|*.jpg;*.jpeg;*.gif;*.png;*.bmp;" & _ 28. "*.tif;*.tiff;*.emf;*.exif;*.wmf" 29. 30. If Open.ShowDialog = Windows.Forms.DialogResult.OK Then 31. 'Apre l'immagine, caricandola dal file selezionato 32. 'nella finestra di dialogo tramite la funzione 33. 'statica FromFile 34. imgPreview.Image = Image.FromFile(Open.FileName) 35. End If 36. End Sub 37. 38. Private Sub cmdSave_Click(ByVal sender As System.Object, _ 39. ByVal e As System.EventArgs) Handles cmdSave.Click 40. 'Se c'è un'immagine da salvare, la salva 41. If imgPreview.Image IsNot Nothing Then 42. Dim Save As New SaveFileDialog 43. Save.Filter = "File Jpeg|*.jpeg;*.jpg|File Bitmap|*.bmp|" & _ 44. "File Png|*.png|File Gif|*.gif|File Tif|*.tif;" & _ 45. "*.tiff|File Wmf|*.wmf|File Emf|*.emf" 46. 47. If Save.ShowDialog = Windows.Forms.DialogResult.OK Then 48. 'Dato che la proprietà Image è di tipo Image, usa 49. 'il metodo statico Save per salvare l'immagine 50. imgPreview.Image.Save(Save.FileName) 51. End If 52. End If 53. End Sub 54. 55. Private Sub cmdConvert_Click(ByVal sender As System.Object, _ 56. ByVal e As System.EventArgs) Handles cmdConvert.Click 57. 'Prima si converte l'immagine in Bitmap, dato che Image 58. 'è una classe astratta 59. Dim Image As Bitmap = imgPreview.Image 60. 'Variabile ausiliaria per i calcoli 61. Dim TempColor As Color 62. 63. 'Attenzione! 64. 'Alcuni formati non supportano SetPixel, come il formato 65. 'Gif. Controllare di passare immagini di formato adeguato 66. 67. 'Itera su ogni pixel, e lo cambia di colore 68. 'Scorre le righe di pixel una alla volta 69. For X As Int32 = 0 To Image.Width - 1 70. 'Quindi ogni pixel nella riga 71. For Y As Int32 = 0 To Image.Height - 1 72. 'Converte il colore 73. TempColor = Image.GetPixel(X, Y) 74. TempColor = ToGreyScale(TempColor) 75. Image.SetPixel(X, Y, TempColor) 76. Next 77. 'Imposta il valore della progressbar su una percentuale 78. 'che esprime il numero di righe analizzate 79. prgConvert.Value = X * 100 / Image.Width 80. 'Evita di bloccare il programma. Per ulteriori 81. 'informazioni su Application e il namespace My, 82. 'vedere capitolo relativo 83. Application.DoEvents() 84. Next 85. 86. 'Reimposta l'immagine finale 87. imgPreview.Image = Image 88. End Sub 89. End Class
  • 285. B10. Un semplice editor di testi Per r ealizzar e un editor di testi bisogna pr ima di tutto saper e come per metter e all'utente di sceglier e quale file apr ir e e in quale file salvar e ciò che ver r à scr itto. Queste semplici inter azioni vengono amministr ate da due contr olli: OpenFileDialog e SaveFileDialog. In questo br eve capitolo esemplificher ò il caso di un semplicissimo editor di testi, con le funzionalità base di aper tur a e salvataggio dei file *.tx t. Pr ima di pr oceder e, ecco una lista delle pr opr ietà più significative dei contr olli in questione: AddEx tension : se il nome del file da apr ir e/salvar e non ha un estensione, il contr ollo l'aggiunge automaticamente sulla base della pr opr ietà DefaultEx t o Filter CheckFileEx ists : contr olla se il file selezionato esista CheckPathEx ists : contr olla se la car tella selezionata esista DefaultEx t : l'estenzione pr edefinita su cui si basa la pr opr ietà AddEx tension FileName : il nome del file visualizzato di default nella tex tbox del contr ollo, e modificato dopo l'inter azione con l'utente Filter : la pr opr ietà più impor tante dopo FileName. Ser ve a definir e quali tipi di file siano visualizzati dal contr ollo. Nella finestr a di dialogo, infatti, come mostr a l'immagin sopr a r ipor tata, poco sotto alla tex tbox contenente il nome del file, c'è una combobox che per mette di selezionar e il "filtr o", per l'appunto, ossia quali estensioni pr ender e in consider azione (nell'esempio "File di testo", con estensione *.tx t, quella che si pr ender à in esame nell'esempio). Ci sono delle r egole standar d per la costr uzione della str inga che deve esser e passata a questa pr opr ietà. Il for mato cor r etto è: 1. Descrizione file|*.estensione1;*.estensione2|Descrizione file|... Se, quindi, si volesser o visualizzar e solo file multimediali, divisi in musica e video, questo sar ebbe il valor e di Filter : "Musica|*.mp3;*.w av;*.w ma;*.ogg;*.mid|Video|*.mpg;*.mp4;*.w mv;*.avi". Per i file di testo "File di testo|*.tx t" e per tutti i file "Tutti i file|*.*" InitialDir ector y: la car tella iniziale pr edefinita MultiSelect: se ver o, si potr anno selezionar e più file (cr eando un r iquadr o col puntator e o selezionandoli manualmente uno ad uno tenendo pr emuto Ctr l) Title: il titolo della finestr a di dialogo ValidatesName: contr olla che i nomi dei file non contengano car atter i vietati Over Wr itePr ompt: (solo per SaveFileDialog) contr olla se il file selezionato ne sovr ascr ive un altr o e chiede se pr oceder e o no Esempio: Editor di testi Dopo aver analizzato le pr opr ietà impor tanti, si può pr oceder e alla stesur a del codice, ma pr ima una pr ecisazione. Non avendo inter faccia gr afica sulla finestr a, ma costituendo w indow s for ms a sè stante, i contr olli OpenFileDialog e SaveFileDialog possono esser e inser iti nel designer oppur e inizializzati da codice indiffer entemente (per quanto r iguar da lo scopo). La diver sità nell'usar e un metodo piuttosto che un altr o sta nel fatto che il pr imo utilizza sempr e lo stesso contr ollo, che potr ebbe dar e dei FileName er r ati in casi speciali, mentr e il secondo ne inizializza uno nuovo ad ogni evento, costando di più in ter mini di memor ia. Nell'esempio seguente utilizzo il pr imo metodo, ma potr à capitar e che sfr utti anche il secondo in diver se altr e occasioni. Or a si aggiungano i contr olli necessar i:
  • 286. Button : Name = cmdOpen, Tex t = "Apr i", Anchor = Bottom Or Left Button : Name = cmdSave, Tex t = "Salva", Anchor = Bottom Or Right Button : Name = cmdClose, Tex t = "Chiudi", Anchor = Bottom Tex tBox : Name = tx tFile, Multiline = Tr ue, Anchor = Top Or Right Or Bottom Or Left OpenFileDialog : Name = FOpen, Filter = "File di testo|*.tx t", FileName = "Testo" SaveFileDialog : Name = FSave, Filter = "File di testo|*.tx t", DefaultEx t = "tx t" 01. Private Sub cmdOpen_Click(ByVal sender As Object, ByVal e As EventArgs)_ 02. Handles cmdOpen.Click 03. 'La funzione ShowDialog visualizza la finestra di dialogo e 04. 'restituisce quale pulsante è stato premuto 05. 'Se il pulsante corrisponde con OK, procediamo 06. If FOpen.ShowDialog = Windows.Forms.DialogResult.OK Then 07. 'Apre un file in lettura 08. 'Usa la proprietà FileName di FOpen, che restituisce il 09. 'path del file selezionato: è sicuro che il file esista 10. 'perchè l'utente ha premuto Ok e non ha chiuso la 11. 'finestra di dialogo 12. Dim R As New IO.StreamReader(FOpen.FileName) 13. 14. 'Legge tutto il testo del file e lo deposita nella textbox 15. txtFile.Text = R.ReadToEnd 16. 17. 'Chiude il file 18. R.Close() 19. End If 20. End Sub 21. 22. Private Sub cmdSve_Click(ByVal sender As Object, ByVal e As EventArgs) _ 23. Handles cmdSave.Click 24. 'Viene visualizzata la finestra di dialogo 25. If FSave.ShowDialog = Windows.Forms.DialogResult.OK Then 26. 'Apre un file in scrittura, di ci si assicura che 27. 'l'utente acconsenta alla sovrascrittura se già esistente 28. 'mediante la proprietà OverwritePrompt 29. Dim W As New IO.StreamWriter(FSave.FileName) 30. 31. 'Scrive tutto il contenuto della textbox nel file 32. W.Write(txtFile.Text) 33. 34. 'Chiude il file 35. W.Close() 36. End If 37. End Sub 38. 39. Private Sub cmdClose_Click(ByVal sender As Object, ByVal e As EventArgs) _ 40. Handles cmdClose.Click 41. If txtFile.Text <> "" And _ 42. FSave.ShowDialog = Windows.Forms.DialogResult.OK Then 43. Dim W As New IO.StreamWriter(FSave.FileName) 44. 45. W.Write(txtFile.Text) 46. 47. W.Close() 48. End If 49. End Sub Il sor gente può esser e r eso ancor a più br eve usando i metodi IO.File.Wr iteAllTex t e IO.File.ReadAllTex t.
  • 287. B11. Scrivere un INI Reader - Parte I I file INI Dato che l'esempio di questo capitolo consiste nel r ealizzar e un lettor e di file *.ini, è bene spiegar e pr ima, per chi non li conoscesse, come sono fatti e quale è lo scopo di questo tipo di file. Sono file di sistema contr addistinti dalla dicitur a "Impostazioni di Configur azione", poichè tale è la lor o funzione: ser vono a definir e il valor e delle opzioni di un pr ogr amma. Nelle applicazioni .NET ci sono altr i modo molto più efficienti per r aggiunger e lo stesso r isultato ma li vedr emo in seguito. La str uttur a di un file ini è composta sostanzialmente da due nuclei: cam pi e v alor i. I campi sono r aggr uppamenti concettuali atti a divider e funzionalmente più valor i di ambito diver so e sono delimitati da una coppia di par entesi quadr e. I valor i costituiscono qualcosa di simile alle pr opr ietà delle classi .NET e possono esser e assegnati con l'oper ator e di assegnamento =. Un ter zo tipo di elemento è costituito dai commenti, che, come ben si sa, non influiscono sul r isultato: questi sono pr eceduti da un punto e vir gola e possono esser e sia su una linea inter a che sulla stessa linea di un valor e. Ecco un esempio: ;Ipotetico INI di un gioco [General Info] Name = ProofGame Version = 1.1.0.2 Company = FG Corporation Year = 2006 [Run Info] Diffucult = easy ;difficoltà Lives = 10 ;numero di vite Health = 90 ;salute Level = 20 ;livello Il pr ogr amma di esempio analizzer à il file, r appr esentando campi e valor i in un gr afico ad alber o simile a quello che w indow s usa per r appr esentar e la str uttur a ger ar chica delle car telle. MenuStrip È il classico menù di w indow s. Una volta aggiunto al for m designer , viene cr eato uno spazio apposito sotto all'antepr ima del for m, nel quale appar e l'icona cor r ispondente; inoltr e viene visualizzata una str iscia gr igia sul lato super ior e della finestr a, ossia l'inter faccia gr afica che MenuStr ip pr esenter à a r un-time. Per aggiunger e una voce, basta far e click su "Type her e" e digitar e il testo associato; è possibile cancellar ne uno pr emendo Canc o modificar lo cliccandoci sopr a due volte lentamente. Ogni sottovoce dispone di eventuali altr i sotto-menù per sonalizzabili all'infinito. Si può aggiunger e un separ ator e, ossia una linea or izzontale, semplicemente inser endo "-" al posto del testo. Ogni elemento così cr eato è un oggetto ToolStr ipMenuItem, inser ito nella pr opr ietà Dr opDow nItems del menù. Ecco alcune pr opr ietà inter essanti: M enuStr ip Allow ItemReor der : deter mina se consentir e il r ior dinamento dei menù da par te dell'utente; quest'ultimo potr ebbe, tenendo pr emuto ALT e tr ascinando gli header , cambiar e la posizione delle sezioni sulla bar r a del MenuStr ip Items : collezione di oggetti der ivati da MenuItem che costituiscono le sezioni pr incipali del menu'
  • 288. Render Mode : pr opr ietà enumer ata che definisce lo stile gr afico del contr ollo. Può assumer e tr e valor i: System (dipende dal sistema oper ativo), Pr ofessional o Manager Render Mode (stile simile a Micr osoft Office) Show ItemToolTips : deter mina se visualizzar e i sugger imenti (tool tip) di ogni elemento Tex tDir ection : dir ezione del testo, or izzontale, ver ticale a 90? o a 270? To o lStr ipM enuItem AutoToolTip : deter mina se usar e la pr opr ietà Tex t (Tr ue) o ToolTipTex t (False) per visualizzar e i tool tip Checked : deter mina se il contr ollo ha la spunta CheckOnClick : specifica sa sia possibile spuntar e il contr ollo con un click CheckState : uno dei tr e stati di spunta DisplayStyle : specifica cosa visualizzar e, se solo il testo, solo l'immagine, entr ambi o nessuno Dr opDow nItems : uguale alla pr opr ietà Items di MenuStr ip Shor tcutKeyDisplayStr ing : la str inga che deter mina quale sia la scor ciatoia da tastier a per il contr ollo, che ver r à visualizzata a destr a del testo (ad esempio "CTRL + D") Shor tcutKeys : deter mina la combinazione di tasti usata come scor ciator ia Show Shor tcutKeys : deter mina se visualizzar e la scor ciatoia da tastier a di fianco al testo Tex tImageRelation : r elazione di posizione tr a immagine e testo TooltipTex t : testo dell'eventuale tool tip Dopo aver inser ito un MenuStr ip str MainMenu, una sezione str File e tr e sottosezioni, str Open, Separ ator e e str Ex it, la scher mata appar ir à così: StatusStrip La bar r a di stato, sul lato basso del for m, che indica le infor mazioni aggiuntive o lo stato dell'applicazione. È un contenitor e che può includer e altr i contr olli, come label, pr ogr essbar , dr opdow nitem, ecceter a. Per or a basta inser ir e una label, di nome lblStatus, con testo impostato su "In attesa...". Dato che le pr opr ietà sono quasi identiche a quelle di MenuStr ip, ecco subito un'antepr ima del for m con questi due contr olli posizionati:
  • 289. ContextMenuStrip È il menù contestuale, ossia quel menù che appar e ogniqualvolta viene pr emuto il pulsante destr o del mouse su un deter minato contr ollo. Per far sì che esso appaia bisogna pr ima cr ear e un legame tr a questo e il contr ollo associato, impostando la r elativa pr opr ietà Contex tMenuStr ip, comune a tutte le classi der ivate da Contr ol. La fase di cr eazione avviene in modo identico a quanto è già stato analizzato per MenuStr ip, e anche l'inser imento delle sottovoci è simile. Non dovr este quindi aver e pr oblemi a cr ear ne uno e inser ir e una voce str Clear View , Tex t = "Pulisci lista". TreeV iew Ecco il contr ollo clou della lezione, che per mette di visualizzar e dati in una str uttur a ad alber o. Le pr opr ietà più impor tanti sono: CheckBox es: deter mina se ogni elemento debba aver e alla pr opr ia sinistr a una checkbox FullRow Select: deter mina se, quando un elemento viene selezionato, sia evidenziato solo il nome o tutta la r iga su cui sta il nome ImageList: specifica quale ImageList è associata al contr ollo; un'imagelist è una lista di immagini or dinata, ognuna delle quali è accessibile attr aver so un indice, come se fosse un ar r aylist ImageIndex : pr opr ietà che deter mina l'indice di default di ogni elemento, da pr elevar e dall'imagelist associata; nel caso la pr opr ietà sia r ifer ita a un elemento, indica quale immagine bisogna visualizzar e a fianco dell'elemento Nodes: la pr opr ietà più impor tante: al par i di Items delle listbox e delle combobox . Contiene una collezione di Tr eeNode (ossia "nodi d'alber o"): ogni elemento Node ha molteplici pr opr ietà e costituisce un'unità dalla quale possono dipar tir si altr e unità. Cosa impor tante, ogni nodo gode di una pr opr ietà Nodes equivalente, la quale implementa la str uttur a suddetta SelectedNode: r estituisce il nodo selezionato Show Lindes: indica se visualizzar e le linee che congiungono i nodi Show PlusMinus: indica se visualizzar e i '+' per espander e i nodi contenuti in un elemento e i '-' per eseguir e l'oper azione opposta Show RootLindes: deter mina se visualizzar e le linee che congiungono i nodi che non dipendono da niente, ossia le unità dalle quali si dipar tono gli altr i elementi
  • 290. In una Tr eeView , ogni elemento è detto appunto no do ed è r appr esentato dalla classe Tr eeNode: ogni nodo può a sua volta dipar tir si in più sotto-elementi, ulter ior i nodi, in un ciclo lungo a piacer e. Gli elementi che non der ivano da nulla se non dal contr ollo stesso sono detti r oots, r adici. Allo stesso modo delle car telle e dei file del computer , ogni nodo può esser e indicato con un per cor so di for mato simile, dove i nome dei nodi sono separ ati da "". La pr opr ietà di Tr eeNode non sono niente di speciale o innovativo: sono già state tutte analizzate, o der ivate da Contr ol. Ecco come appar e l'inter faccia, dopo aver aggiunto una Tr eeView tr w Ini con Dock = Fill e un Contex tMenuStr ip cntTr eeView ad essa associato:
  • 291. B12. Scrivere un INI Reader - Parte II Dopo aver spiegato e posizionato i var i contr olli con le pr opr ietà adatte, si deve stender e il codice che per mette al pr ogr amma di legger e i file e visualizar li cor r ettamente. Ecco il sor gente commentato: 01. Class Form1 02. Private Sub ReadFile(ByVal File As String) 03. 'Lo stream da cui leggere il file 04. Dim Reader As New IO.StreamReader(File) 05. 'Una stringa che rappresenta ogni singola riga del file 06. Dim Line As String 07. 'L'indice associato al numero di campi letti. Dato che ogni 08. 'campo costituirà una radice del grafico, bisogna sapere da 09. 'dove far derivare i relativi valori. 10. 'Questa variabile è opzionale, in quanto è possibile usare 11. 'la proprietà trwIni.Nodes.Count-1, poichè si aggiungono 12. 'valori sempre soltanto all'ultimo campo aperto 13. Dim FieldCount As Int16 = -1 14. 15. 'Imposta il testo della label di stato 16. lblStatus.Text = "Apertura del file in corso..." 17. 18. 'Finchè non si raggiunge la fine del file si continua 19. 'a leggere 20. While Not Reader.EndOfStream 21. 'Leggiamo una linea di file (S) 22. Line = Reader.ReadLine 23. 'Se la linea è diversa da una riga vuota 24. If Line <> Nothing Then 25. 'Se la linea inizia per "[" (significa che è 26. 'un campo) 27. If Line.StartsWith("[") Then 28. 'Si aumenta FieldCount, che indica quanti campi 29. 'si sono già letti (in base 0) 30. FieldCount += 1 31. 'Rimuove il primo carattere, ossia "[" 32. Line = Line.Remove(0, 1) 33. 'Rimuove dalla linea l'ultimo carattere, 34. 'ossia "]" 35. Line = Line.Remove(Line.Length - 1, 1) 36. 'Aggiunge una radice alla TreeView 37. trwIni.Nodes.Add(Line) 38. Else 39. 'Altrimenti, se la linea non inzia per ";", 40. 'ossia non è un commento 41. If Not Line.StartsWith(";") Then 42. 'Aggiunge la linea come sotto-nodo 43. 'dell'ultimo campo inserito. La linea 44. 'conterrà il valore in forma 45. ' [nome]=[contenuto] 46. 'Attenzione! Possono esserci commenti in 47. 'riga, quindi si deve prima controllare 48. 'di eliminarli 49. 'Se l'indice del carattere ";" nella riga 50. 'è positivo... 51. If Line.IndexOf(";") > 0 Then 52. 'Rimuove tutto quello che viene dopo 53. 'il commento 54. Line = Line.Remove(Line.IndexOf(";")) 55. End If 56. trwIni.Nodes(FieldCount).Nodes.Add(Line) 57. End If 58. End If 59. End If 60. End While 61.
  • 292. 'Chiude il file 62. Reader.Close() 63. 64. lblStatus.Text = "File aperto" 65. End Sub 66. 67. Private Sub strOpen_Click(ByVal sender As Object, _ 68. ByVal e As EventArgs) Handles strOpen.Click 69. 'Ecco un esempio di OpenFileDialog da codice 70. Dim FOpen As New OpenFileDialog 71. FOpen.Filter = "Impostazioni di configurazione|*.ini" 72. If FOpen.ShowDialog = Windows.Forms.DialogResult.OK Then 73. ReadFile(FOpen.FileName) 74. End If 75. End Sub 76. 77. Private Sub strExit_Click(ByVal sender As Object, _ 78. ByVal e As EventArgs) Handles strExit.Click 79. 'Esce dal programma, chiudendo il form corrente 80. Me.Close() 81. End Sub 82. 83. Private Sub strClearList_Click(ByVal sender As Object, _ 84. ByVal e As EventArgs) Handles strClearList.Click 85. 'Mostra un messaggio di conferma prima di procedere 86. If MessageBox.Show("Eliminare tutti gli elementi dela lista?", _ 87. "INI Reader", MessageBoxButtons.YesNo, MessageBoxIcon.Question) = _ 88. Windows.Forms.DialogResult.No Then 89. 'Se si risponde di no, esce dalla procedura 90. Exit Sub 91. End If 92. 93. 'Elimina tutti i nodi 94. trwIni.Nodes.Clear() 95. End Sub 96. End Class Il codice degli eventi è molto semplice, mentr e più inter essante è quello della pr ocedur a ReadFile. Per aver e una panor amica delle oper azioni sulle str inghe usate, veder e capitolo r elativo. Per quanto r iguar da la logica del sor gente, ecco una br eve spiegazione: viene letto il file r iga per r iga e, sulla base delle condizioni che si incontr ano man mano, vengono eseguite istr uzioni diver se: La linea è vuota : può capitar e che si lascino linee di testo vuote per separ ar e ulter ior mente i campi o valor i dell'inter no dello stesso campo; in questo caso, poichè non c'è niente da legger e, semplicemente si passa oltr e La linea inizia per "[" : come già detto, in un file ini, i campi sono r acchiusi tr a par entesi quadr e, per ciò la linea costituisce il nome di un campo. Dopo aver eliminato le par entesi con oppor tune funzioni, si usa il r isultato per aggiunger e alla Tr eeView una r oot mediante Nodes.Add. Questo metodo accetta, tr a i var i over loads, un par ametr o str inga che costituisce il testo del nodo La linea inizia per ";" : è un commento e semplicemente viene omesso. Potr este comunque includer lo come nodo ausiliar lo e color ar lo con un color e differ ente La linea non ha nessun delle car atter istiche indicate : è un valor e. Quindi si aggiunge il suo contenuto come sotto-nodo all'ultimo nodo r oot aggiunto, con l'accor tezza di contr ollar e pr ima se ci sono dei commenti cosiddetti in-line e di eliminar li Ecco uno scr eenshot di come si pr eseta il pr ogr amma finito con un file ini car icato: Ed ecco uno scr eenshot di come potr este far lo diventar e:
  • 294. B13. DateTimePicker - Lavorare con le date Il tipo di dato standar d che il .NET Fr amew or k mette a disposizione per lavor ar e cone le date e gli or ar i è Date, facente par te del Namespace System. Per compatibilità con il vecchio Visual Basic 6, è pr esenta anche System.DateTime, che r appr esenta la stessa identica entità. Con questo semplice tipo è possibile far e di tutto e per ciò non è necessar io definir e manualmente alcun metodo nuovo quando si lavor a con le date. Ecco un elenco dei metodi e delle pr opr ietà più impor tanti: Add(t): aggiunge alla data un fattor e t di tipo TimeSpan contenente una dur ata di tempo AddYear s, AddMonths, AddDays, AddHour s, AddMinutes, AddSeconds, AddMilliseconds: aggiungono un fattor e t di anni, mesi, gior ni, or e, minuti, secondi, millisecondi alla data, specificata come unico par ametr o Year , Month, Day, Hour , Minute, Second, Millisecond: r estituiscono l'anno, il mese, il gior no, l'or a, i minuti, i secondi o i millisecondi della data contenuta nella var iabile DayOfWeek: r estituisce un enumer ator e che r appr esenta il gior no della settimana contenuto nella data della var iabile DayOfYear : r estituisce un numer o che indica il numer o del gior no in tutto l'anno DaysInMonth(y, m): r estituisce il numer o di gior ni del mese m dell'anno y Now : pr opr ietà shar ed che r estituisce la data cor r ente (Date.Now ) Par se(s): funzione shar ed che conver te la str inga s in una data; utile per quando si deve salvar e una data su file Subtr act(d): sottr ae alla data della var iabile la data d, r estituendo un valor e di tipo TimeSpan (ossia 'tempo tr ascor so') ToLongDateStr ing: conver te la data in una str inga, espandendo la data in questo for mato: [gior no della settimana] [gior no del mese] [mese] [anno] (esempio: vener dì 30 giugno 2006) ToLongTimeStr ing: conver te l'or a della data in una str inga, espandendola in questo for mato: [or e].[minuti]. [secondi] (esempio: 13.13.07) ToShor tDateStr ing: conver te la data in una str inga, contr aendola in questo for mato: [gior no del mese][mese] [anno] (esempio: 30/6/2006) ToShor tTimeStr ing: conver te l'or a della data in una str inga, contr aendola in questo for mato: [or e].[minuti] (esempio: 13.13) ToFileTime : funzione cur iosa, che r estituisce la data in for mato file, ossia come multiplo di inter valli di 100 nanosecondi tr ascor si dal pr imo gennaio 1601 alle or e 12.00 di mattina Tr yPar se(s, r ): tenta di conver tir e la str inga s in una data: se ci r iesce, r assume il valor e della data (r è passata per indir izzo) e r estituisce Tr ue; se non ci r iece, r estituisce False Par allelamente, viene definito anche il tipo TimeSpan ("tempo tr ascor so") che r appr esenta un lasso di tempo e si ottiene con la differ enza di due valor i Date. Ha le stesse pr opr ietà sopr a elencate, fatta eccezione per alcune che possono r ivelar si inter essanti, come Fr omDays, Fr omHour s, Fr omSeconds, Fr omMinutes, Fr omMilliseconds: funzioni shar ed che cr eano un valor e di tipo TimeSpan a par tir e da un ammontar e di gior ni, or e, minuti, secondi o millisecondi. Esempio: A long, long life Ecco un esempio molto semplice e diver tito che applica i concetti sopr a esposti. Lo scopo del pr ogr amma è di calcolar e con una buona pr ecisione la dur ata della nostr a vita, avendo immesso pr ecedentemente la data di nascita. Il contr ollo usato è DateTimePicker , le cui pr opr ietà sono autoesplicative. Per or a pr ender ò in analisi solo le pr opr ietà For mat e CustomFor mat. La pr ima per mette di definir e il for mato del contr ollo: è r appr esentata da un enumer ator e che può
  • 295. assumer e quattr o valor i, Long (data in for mato esteso, come la r estituisce la funzione Date.ToLongDateStr ing), Shor t (data in for mato br eve, come la r estituisce la funzione Date.ToShor tdateStr ing), Time (or a in for mato esteso) e Custom (per sonalizzato). Se viene scelta l'ultima opzione, si deve impostar e la str inga CustomFor mat in modo da r ipr odur r e il valor e in confor mità ai pr opr i bisogni. Nella str inga possono pr esenziar e queste sequenze di car atter i: d : gior no del mese, con una o due cifr e a seconda dei casi dd : gior no del mese, sempr e con due cifr e (vengono aggiunti zer i sulla sinistr a nel caso manchino posti) ddd : gior no della settimana, abbr eviato a tr e car atter i secondo la cultur a cor r ente dddd : gior no della settimana, con nome completo M : mese, con una o due cifr e a seconda dei casi M M : mese, sempr e con due cifr e M M M : nome del mese, abbr eviato a tr e car atter i secondo la cultur a cor r ente M M M M : nome completo del mese y : anno, con una o due cifr e a seconda dei casi yy : anno, sempr e con due cifr e yyyy : anno, a quattr o cifr e H : or a, in for mato 24 or e con una o due cifr e HH : or a, in for mato 24 or e con due cifr e h : or a, in for mato 12 or e, con una o due cifr e hh : or a, in for mato 12 or e, con due cifr e m : minuti, con una o due cifr e m m : minuti, con due cifr e s : secondi, con una o due cifr e ss : secondi, con due cifr e f : fr azioni di secondo (un numer o qualsiasi da uno a sette di "f" consecutive cor r isponde ad altr ettanti decimali) Dato che il contr ollo dovr à espor r e il valor e in for mato: [nome giorno] [giorno] [nome mese] [anno], ore [ora]:[minuti] La str inga di for mato da inser ir e sar à: dddd d MMMM yyyy, ore HH:mm Gli stessi patter n valgono anche se posti come ar gomento della funzione Date.ToStr ing("For mato"). I contr olli da aggiunger e sono un DateTimePicker (dtpBir thday), con una label di spiegazione a fianco, una label che visualizzi i r isultati (lblAge) e un timer (tmr Refr esh) per aggior nar e il r isultato a ogni secondo che passa. Or a non r esta che scr iver e il codice, per altr o molto semplice. Il sogente fa uso di un contr ollo Timer , che una volta abilitato (Timer .Enabled=Tr ue o Timer .Star t()), lancia un evento Tick ogni Timer .Inter val millisecondi cir ca (il valor e è molto var iabile, a seconda della velocità del computer su cui viene fatto cor r er e). 01. Class Form1 02. Private Sub tmrRefresh_Tick(ByVal sender As Object, _ 03. ByVal e As EventArgs) Handles tmrRefresh.Tick 04. 'Ottiene la differenza tra le due date 05. Dim Age As TimeSpan = (Date.Now - dtpBirthDay.Value) 06. 'La trasforma in secondi 07. Dim Seconds As Double = Age.TotalSeconds 08. 'Variabile temporanea che serve alla costruzione 09. Dim AgeStr As New System.Text.StringBuilder 10. 11. With AgeStr 12. .AppendLine("Hai vissuto") 13. 14.
  • 296. 'Calcola i giorni secondo il modo già visto nelle prime 15. 'lezioni sulle classi e le proprietà 16. .AppendFormat("{0} giorni{1}", Seconds (60 * 60 * 24), vbCrLf) 17. Seconds -= (Seconds (60 * 60 * 24)) * (60 * 60 * 24) 18. 19. 'E così anche ore, minuti e secondi 20. .AppendFormat("{0} ore{1}", Seconds 3600, vbCrLf) 21. Seconds -= (Seconds 3600) * 3600 22. 23. .AppendFormat("{0} minuti{1}", Seconds 60, vbCrLf) 24. Seconds -= (Seconds 60) * 60 25. 26. .AppendFormat("{0:n0} secondi", Seconds) 27. 28. 'Quindi mette il risultato come testo della label 29. lblAge.Text = .ToString 30. End With 31. End Sub 32. End Class Per il mio caso, il r isultato è questo:
  • 297. B14. ImageList In fase di pr ogettazione, se si vogliono aggiunger e immagini a contr olli come Button, Label, SplitButton, ToolBox et similia è sufficiente selezionar e la pr opr ietà Image (o Backgr oundImage), apr ir e la finestr a di dialogo mediante pr essione sul pulsante che appar e, sceglier e quindi un file immagine dall'Har d Disk o dalle r isor se del pr ogetto, e confer mar e la scelta per ottener e un effetto ottimo. Tuttavia, ciò non è sempr e possibile, ad esempio se a r un-time si vogliono associar e deter minate icone a elementi di una lista che non è possibile pr eveder e dur ante la stesur a del codice. In situazioni simili, il contr ollo che viene in aiuto del pr ogr ammator e si chiama ImageList. Esso costituisce una lista, or dinata secondo indici e chiavi, che contiene immagini pr ecedentemente car icate dallo sviluppator e: tutte queste vengono r idimensionate secondo una dimensione fissata dalle pr opr ietà del contr ollo e hanno una limitazione di pr ofondità di color e, sempr e pr edeter minata, da 8 a 32 bit. Per ottener e effetti di gr ande impatto, è consigliabile utilizzar e for mati ad ampio spettr o di color e e con tr aspar enza come il Por table Netw or k Gr aphics (*.png), oppur e il JPEG (*.jpg) se si vuole r ispar miar e spazio pur conser vando una discr eta qualita'; il for mato ideale è 32x 32 pix el per le icone gr andi e 22x 22 o 16x 16 in quelle piccole come nei menù a discesa o nelle ListView a dettagli. Il meccanismo che per mette ai contr olli di fr uir e delle r isor se messe a disposizione da un'ImageList è lo stesso usato dal Contex tMenuStr ip. Ogni contr ollo con inter faccia che suppor ti questo pr ocesso, dispone di una pr opr ietà ImageList, che deve esser e impostata di conseguenza a seconda della lista di immagini che si vuole quel contr ollo possa utilizzar e. Successivamente, i singoli elementi al suo inter no sono dotati delle pr opr ietà ImageIndex e ImageKey, che per mettono di associar vi un'immagine pr elevandola mediante l'indice o la chiave impostata. Ecco alcuni esempi di come potr ebber o pr esentar si contr olli di questo tipo: Im ag eList su ListVie w Im ag eList su Tr eeView Im ag eList su TabCo ntr o l Reperire le ic one Indubbiamente questo contr ollo offr e moltissime possibilità di per sonalizzar e la veste gr afica dell'applicazione a piacer e del pr ogr ammator e, tuttavia se non si dispone di mater iale adatto, il suo gr ande aiuto viene meno. Per questo motivo, dar ò alcuni sugger imenti su come r eper ir e un buon numer o di icone fr eew ar e o al limite sotto licenza lgpl (il che le r ende disponibili per l'uso da par te di softw ar e commer ciali). Come pr ima r isor sa, c'è il pr ogr amma AllEx Icon, scr itto da Maur o Rossi in Visual Basic 6, che potete tr ovar e a questo indir izzo . Dopo aver lo avviato, basta impostar e la dir ector y di r icer ca su C:WINDOWSSystem32 (per sistemi oper ativi Window s XP) e il filtr o su "*.dll". Ver r anno estr atte moltissime belle icone, con la possibilità di salvar le in for mato bitmap una alla volta o in massa. Dato il lor o for mato, anche conver tite in JPEG, r imar r à un color e di sfondo, che può venir e par zialmente eliminato impostando la pr opr ietà ImageTr anspar ency del for m su Tr anspar ent o su White, r endendo quindi tr aspar ente il lor o sfondo. Come seconda possibilità ci sono alcuni pacchetti di icone r eper ibili dal w eb. Il pr imo che consiglio è "Nuvola", lo stesso che uso per le mie applicazioni, distr ibuito sotto licenza LGPL su questo sito; il secondo è "500.000 Icone!", una collezione di cir ca 8000 icone, divise in *.ico e *.png, messe insieme da svar iate fonti del w eb: ogni r isor sa è stata r esa pubblica dal
  • 298. suo cr eator e e non ci sono limitazioni al lor o uso. Il pacchetto può esser e tr ovato solo attr aver so eMule. La ter za possibilità consiste nel cer car e sulla r ete insiemi di immagini messe liber amente a disposizione di tutti da qualche volenter oso designer , ad esempio su questa pagina di Wikipedia, dove, navigando tr a le var ie categor ie, è possibile ottener e svar iate centinaia di icone.
  • 299. B15. ListView La ListView è un contr ollo complesso e di gr ande impatto visivo. È lo stesso tipo di lista usato dall'ex plor er di w indow s per visualizzar e files e car telle. Le sue pr opr ietà per mettono di per sonalizzar ne la visualizzazione in cinque stili diver si: i più impor tanti di questi sono Lar ge Icone (Icone gr andi), Small Icon (Icone piccole) e Details (Dettagli). Ci sono poi anche Tile e List, ma vengono usati meno spesso. Ecco alcuni esempi: Lar g e Icon Sm all Icon Details ListV iew Come al solito, ecco la compilation delle pr opr ietà più inter essanti: CheckBox es : indica se la listview debba visualizzar e delle CheckBox vicino ad ogni elemento Columns : collezione delle colonne disponibili. Ogni colonna è contr addistinta da un testo (Tex t), un indice d'immagine (ImageIndex ) e un indice di visualizzazione (DisplayIndex ) che specifica la sua posizione or dinale nella visualizzazione. Le colonne sono visibili sono con View = Details FullRow Select : indica se evidenziar e tutta la r iga o solo il pr imo elemento, quando View = Details Gr idLines : indica su visualizzar e le r ighe della gr iglia, quando View = Details Gr oups : collezione dei gr uppi disponibili Header Style : specifica se le intestazioni delle colonne possano esser e cliccate o meno HideSelection : specifica se la listview debba nasconder e la selezione quando per de il Focus, ossia quando un altr o contr ollo diventa il contr ollo attivo HotTr acking : abilita gli elementi ad appar ir e come collegamenti iper testuali quando il mouse ci passa sopr a Hover Selection : se impostata su Tr ue, sar à possibile selezionar e un elemento semplicemente sostandoci sopr a con il mouse Items : collezione degli elementi della listview LabelEdit : specifica se sia possibile modificar e il testo dei SubItems da par te dell'utente, quando View = Details Lar geImageList : ImageList per View = Lar ge Icon / Tile / List MultiSelect : indica se si possano selezionar e più elementi contempor aneamente Ow ner Dr aw : indica se gli elementi debbano esser e disegnati dal contr ollo o dal codice del pr ogr ammator e. Vedi ar ticolo r elativo Show Gr oups : deter mina se visualizzar e i gr uppi Show ItemToolTips : deter mina se visualizzar e i ToolTips dei r ispettivi elementi SmallIconList : ImageList per View = Small Icon / Details Sor ting : il tipo di or dinamento, se alfabetico ascendente o discendente.
  • 300. ListV iew Item Ogni elemento della ListView è contr addistinto da un oggetto ListView Item, che, a differ enza di quanto avveniva cone le nor mali liste come ListBox e ComboBox , non costituisce una semplice str inga (o un tipo base facilmente r appr esentabile) ma un nucleo a sè stante, del quale si possono per sonalizzar e tutte le car atter istiche visive e stilistiche. Poichè la ListView è compatibile con l'ImageList, tutti i membr i della collezione Items sono in gr ado di impostar e l'indice d'immagine associato, come si è analizzato nella lezione scor sa. Inoltr e, sempr e manipolando le pr opr ietà, si può attr ibuir e ad ogni elemento un testo, un font, un color e diver so a seconda delle necessità. Nella visualizzazione a dettagli si possono impostar e tutti i valor i cor r ispettivi ad ogni colonna mediante la pr opr ietà SubItems, la quale contiene una collezione di oggetti ListView SubItem: di questi è possibile modificar e il font e il color e, oltr e che il testo. ListV iew Group Un gr uppo è un insieme di elementi r aggr uppati sotto la stessa etichetta. I gr uppi vengono aggiunti alla lista utilizzando l'apposita pr opr ietà Gr oups e ogni elemento può esser e assegnato ad un gr uppo con la stessa pr opr ietà (ListView Item.Gr oup). Oggetti di questo tipo godono di poche car atter istiche: solo il testo ed il nome sono modificabili. Pr ima di pr oceder e con ogni oper azione, per ò, bisogna assicur ar si che la pr opr ietà Show Gr oups della ListView sia impostata a Tr ue, altr imenti... beh, niente festa. Ecco un esempio di codice: 01. 'Nuovo gruppo 'Testo esplicativo' 02. Dim G As New ListViewGroup("Testo esplicativo") 03. 'Nuovo elemento 'Elemento' 04. Dim L As New ListViewItem("Elemento") 05. 'Aggiunge il gruppo alla listview 06. ListView1.Groups.Add(G) 07. 'Setta il gruppo a cui L apparterrà 08. L.Group = G 09. 'Aggiunge l'elemento alla lista 10. ListView1.Items.Add(L) ListV iew c on V iew = Details La ListView a dettagli è la ver sione più complessa di questo contr ollo, ed è contr addistinta dalla classica visualizzazione a colonne. Ogni colonna viene deter minata in fase di sviluppo o a r un-time agendo sulla collezione Columns nelle pr opr ietà della lista e bisogna r icor dar e l'or dine in cui le colonne vengono inser ite poichè questo influisce in manier a significativa sul compor tamento dei SubItems. Infatti il pr imo SubItem di un ListView Item andr à sotto la pr ima colonna, quella con indice 0, il secondo sotto la seconda e così via. Un er r or e di or dine potr ebbe pr odur r e quindi, r isultati sgr adevoli. Nell'esempio che segue, scr iver ò un br eve pr ogr amma per calcolar e la spesa totale conoscendo i singoli pr odotti e le singole quantità di una lista della spesa. Ecco un'antepr ima di come dovr ebbe appar ir e la finestr a: I contr olli usati sono deducibili dal nome e dall'utilizzo. Ecco quindi il codice: 01. Class Form1 02. Private Sub cmdAdd_Click(ByVal sender As System.Object, _ 03. ByVal e As System.EventArgs) Handles cmdAdd.Click 04. 'Nuovo elemento 05. Dim Item As ListViewItem 06. 'Array dei valori che andranno a rappresentare i campi di 07. 'ogni singola colonna 08.
  • 301. Dim Values() As String = _ 09. {txtProduct.Text, nudPrice.Value, nudQuantity.Value} 10. 11. 'Inizializza Item sulla base dei valori dati 12. Item = New ListViewItem(Values) 13. 'E lo aggiunge alla lista 14. lstProducts.Items.Add(Item) 15. End Sub 16. 17. Private Sub cmdDelSelected_Click(ByVal sender As System.Object, _ 18. ByVal e As System.EventArgs) Handles cmdDelSelected.Click 19. 'Analizza tutti gli elementi selezionati 20. For Each SelItem As ListViewItem In lstProducts.SelectedItems 21. 'E li rimuove dalla lista 22. lstProducts.Items.Remove(SelItem) 23. Next 24. End Sub 25. 26. Private Sub cmdCalculate_Click(ByVal sender As System.Object, _ 27. ByVal e As System.EventArgs) Handles cmdCalculate.Click 28. 'Totale spesa 29. Dim Total As Single = 0 30. 'Prezzo unitario 31. Dim Price As Single 32. 'Quantit? 33. Dim Quantity As Int32 34. 35. For Each Item As ListViewItem In lstProducts.Items 36. 'Ottiene i valori da ogni elemento 37. Price = CSng(Item.SubItems(1).Text) 38. Quantity = CInt(Item.SubItems(2).Text) 39. Total += Price * Quantity 40. Next 41. 42. MessageBox.Show("Totale della spesa: " & Total & "€.", _ 43. "Spesa", MessageBoxButtons.OK, MessageBoxIcon.Information) 44. End Sub 45. End Class Per i più cur iosi, mi addentr er ò ancor a un pò di più nel par ticolar e, nella fattispecie su una car atter istica molto appr ezzata in una ListView a dettagli, ossia la possibilità di or dinar e tutti gli elementi con un click. In questo caso, facendo click sull'intestazione (header ) di una colonna, sar ebbe possibile or dinar e gli elementi sulla base della qualità che quella colonna espone: per nome, per pr ezzo, per quantità. Dato che il metodo Sor t non accetta alcun over load che consenta di usar e un Compar er , bisogner à implementar e un algor itmo che compar i tutti gli elementi e li or dini. In questo esempio si fa r icor so a due classi che implementano l'inter faccia gener ica ICompar er (Of ListView Item): il pr imo compar a il nome del pr odotto, mentr e il secondo il pr ezzo e la quantità. Ecco il codice da aggiunger e: 01. Class Form1 02. 'Queste classi saranno i comparer usati per ordinare 03. 'le righe della ListView, al pari di come si è imparato nelle 04. 'lezioni sulle interfacce 05. Private Class ProductComparer 06. Implements IComparer(Of ListViewItem) 07. 08. Public Function Compare(ByVal x As ListViewItem, _ 09. ByVal y As ListViewItem) As Integer _ 10. Implements IComparer(Of ListViewItem).Compare 11. 'Gli oggetti da comparare sono ListViewItem, mentre la proprietà 12. 'che bisogna confrontare è il nome del prodotto, ossia 13. 'il primo sotto-elemento 14. Dim Name1 As String = x.SubItems(0).Text 15. Dim Name2 As String = y.SubItems(0).Text 16. Return Name1.CompareTo(Name2) 17. End Function 18. End Class 19. 20. Private Class NumberComparer 21. Implements IComparer(Of ListViewItem) 22. 23.
  • 302. Private Index As Int32 24. 25. 'Price è True se ci si riferisce al Prezzo (elemento 1) 26. 'oppure False se ci si riferisce alla quantità (elemento 2) 27. Sub New(ByVal Price As Boolean) 28. If Price Then 29. Index = 1 30. Else 31. Index = 2 32. End If 33. End Sub 34. 35. Public Function Compare(ByVal x As ListViewItem, _ 36. ByVal y As ListViewItem) As Integer _ 37. Implements IComparer(Of ListViewItem).Compare 38. 'Qui bisogna ottenere il prezzo o la quantità: ci si basa 39. 'sul parametro passato al costruttore 40. Dim Val1 As Single = x.SubItems(Index).Text 41. Dim Val2 As Single = y.SubItems(Index).Text 42. Return Val1.CompareTo(Val2) 43. End Function 44. End Class 45. 46. 'Scambia due elementi in una lista: dato che ListViewItem sono 47. 'variabili reference, bisognerebbe clonarli per spostarne il valore, 48. 'come si faceva con i valori value, in questo modo: 49. 'Dim Temp As ListViewItem = L1.Clone() 50. 'L1 = L2.Clone() 51. 'L2 = Temp 52. 'Ma si userebbe troppa memoria. Perciò la via più facile è 53. 'usare i metodi della lista per scambiare gli elementi 54. Private Sub SwapInList(ByVal List As ListView, ByVal Index As Int32) 55. Dim Temp As ListViewItem = List.Items(Index + 1) 56. List.Items.RemoveAt(Index + 1) 57. List.Items.Insert(Index, Temp) 58. End Sub 59. 60. 'Ordina gli elementi con l'algoritmo Bubble Sort già 61. 'descritto nell'ultima lezione teorica 62. Private Sub SortListViewItems(ByVal List As ListView, _ 63. ByVal Comparer As IComparer(Of ListViewItem)) 64. Dim Occurrences As Int32 = 0 65. 66. Do 67. Occurrences = 0 68. For I As Int32 = 0 To List.Items.Count - 1 69. If I = List.Items.Count - 1 Then 70. Continue For 71. End If 72. If Comparer.Compare(List.Items(I), List.Items(I + 1)) = 1 Then 73. SwapInList(List, I) 74. Occurrences += 1 75. End If 76. Next 77. Loop Until Occurrences = 0 78. End Sub 79. 80. Private Sub lstProducts_ColumnClick(ByVal sender As System.Object, _ 81. ByVal e As ColumnClickEventArgs) Handles lstProducts.ColumnClick 82. Select Case e.Column 83. Case 0 84. 'Nome 85. Me.SortListViewItems(lstProducts, New ProductComparer()) 86. Case 1 87. 'Prezzo 88. Me.SortListViewItems(lstProducts, New NumberComparer(True)) 89. Case 2 90. 'Quantità 91. Me.SortListViewItems(lstProducts, New NumberComparer(False)) 92. End Select 93. End Sub 94. End Class
  • 304. B16. ToolStrip e TabControl ToolStrip Tutti conoscono benissimo l'inter faccia di Micr osoft Wor d, dove sopr a lo spazio in cui si scr ive ci sono mir idai di icone, ognuna con la pr opr ia funzione (Salva, Apr i, Nuovo, Copia, Incolla ecc...): la bar r a degli str umenti, così chiamata, dove sono collocate quelle icone è una ToolStr ip. Ecco osser var e un esempio di toolstr ip cr eata con vb.net: Le pr opr ietà pr incipali di una toolstr ip: ImageScalingSize: molto impor tante, deter mina di che dimensione sar anno le immagini della toolstr ip; per impostar le della dimensione di quelle di Wor d si lasci pur e 16;16 (16x 16), mentr e per far la appar ir e con la stessa dimensione di quelle in immagine un 24;24 è accettabile (consiglier ei di non andar e tr oppo oltr e) Items: l'insieme degli elementi della toolstr ip; ciò che si può metter e nella toolstr ip è un piccolo gr uppo di contr olli nor malissimi, già analizzati, ossia: Button (un nor male pulsante: nell'immagine, Testi e Cr onologia sono Button); Dr opDow nItems (menù a discesa, identico a MenuStr ip: nell'immagine Documenti è un dr opdow nitem); SplitButton (una fusione tr a button e dr opdow nitems, poichè gode di un evento click pur essendo una lista a discesa); Label (una nor malissima etichetta di testo: nell'immagine Nome è una label); Tex tBox (casella di testo: nell'immagine il testo "Nicolo'" contenuto in una tex tbox ); ComboBox (lista a cascata: nell'immagine è il pr imo contr ollo della seconda r iga); Pr ogr essBar (ultimo contr ollo); Separ ator (un separ ator e, ossia una bar r a ver ticale che separ a gli elementi: nell'immagine è pr esente fr a Documenti e Nome) Tex tDir ection: dir ezione del testo Per r ender e più aggr aziata la veste gr afica del pr ogr amma, una toolstr ip è molto utile. Un'ultima cosa: facendo click col pulsante destr o sulla toolstr ip in fase di pr ogettazione, si dispor r à di var ie opzioni, fr a cui quelle di Aggiunger e uno Str umento, Conver tir e un contr ollo in altr o tipo e Aggiunger e alla bar r a le icone standar d di lavor o (ossia Apr i, Nuovo, Salva, Copia, Incolla e Taglia, già cor r edate di icona). TabControl TabContr ol è un contr ollo for mato da più schede sovr apposte, ognuna delle quali contiene al pr opr io inter no una inter faccia diver sa. Questo contr ollo, infatti, insieme a Gr oupBox , SplitPanel e pochi altr i, è un contenitor e cr eato appositamente per or dinar e più contr olli, in questo caso c'è la possibilità di stipar e una enor me quantità di layout in poco spazio: ogni scheda (Tab) è accessibile con un click e cambia l'inter faccia visualizzata. A seconda della pr opr ietà Appear ance, TabContr ol può pr esentar si sotto tr e vesti gr afiche differ enti, come mostr ato in figur a: in or dine dall'alto al basso sono Nor mal, Buttons e FlatButtons. Per inser ir e uno o più contr olli all'inter no di una scheda è sufficiente tr ascinar li con il mouse oppur e aggiunger li da codice facendo r ifer imento alla pr opr ietà TabPages. Ecco la lista delle pr opr ietà più r ilevanti: Appear ance : lo stile di visualizzazione ItemSize : la dimensione dell'intestazione delle schede Multiline : se impostato su Tr ue, qualor a le schede fosser o tr oppe, ver r anno visualizzate diver se file di header una sopr a all'altr a; altr imenti appar ir anno due pulsantini a mò di scr ollbar che per metter anno di scor r er le
  • 305. or izzontalmente. Nel pr imo caso, la pr opr ietà in sola lettur a Row Count r estituisce il numer o di r ighe visualizzate TabPages : collezione di tutte le schede disponibili sotto for ma di oggetti TabPage Per por tar e in pr imo piano una scheda è possibile r ichiamar e da codice il metodo TabContr ol.SelectTab(Index ), passando come unico par ametr o l'indice della scheda da r ilevar e, o, in alter nativa, tutto l'oggetto TabPage.
  • 306. B17. NotifyIcon e SplitContainer Notify Ic on La par te infer ior e destr a della bar r a delle applicazioni di Window s è denominata System Tr ay e r aggr uppa tutte le icone dei pr ogr ammi cor r entemente in esecuzione sul sistema oper ativo, ovviamente solo se questi ne r ichiedono una. Ecco uno scr eenshot: Per aggiunger e un'icona al pr ogetto, che ver r à automaticamente visualizzata in questo spazio dopo l'avvio dell'applicazione, è sufficiente aggiunger e al designer un contr ollo NotifyIcon, che non ha inter faccia gr afica in ambiente di sviluppo. Le pr opr ietà inter essanti sono queste: BaloonTipIcon : deter mina l'icona da visualizzar e a sinistr a del titolo del fumetto (si può sceglier e tr a er r or , info e w ar ning) BaloonTipTex t : testo del fumetto BaloonTipTitle : titolo del fumetto Icon : icona visualizzata nella system tr ay (si possono sceglier e solo file icona *.ico) Show BaloonTip(x ) : visualizza il fumetto dell'icona per x millisecondi (2000 è una buona media) Tex t : descr izione visualizzata quando il mouse sosta per qualche secondo sull'icona Come molti altr i contr olli, anche questo suppor ta un menù contestuale gr azie al quale si possono eseguir e molte oper azioni anche in assenza dell'inter faccia utente completa. Inoltr e vengono r egistr ati anche eventi come il Click o il doppio Click del mouse sull'icona e mediante questi si può r idur r e il for m in modo che non appaia nella bar r a delle applicazioni ma che pr esenzi solamente l'icona nella System Tr ay. Il codice da usar e in casi simili è molto semplice: 1. 'Nasconde il form dalla barra delle applicazioni 2. Me.ShowInTaskBar = False 3. 'Rende il form invisibile 4. Me.Visible = False 5. 'Se l'icona non è già visibile, la rende visibile 6. Me.nftIcon.Visible = True Per r ipor tar e tutto allo stato pr ecedente è sufficiente inver tir e i valor i booleani. Fum etto SplitContainer Anche lo SplitContainer è un contenitor e, e può r ivelar si davver o molto utile. La sua peculiar ità consiste nel poter r idimensionar e con il mouse, spostando quello che viene chiamato splitter , le due par ti del contr ollo. Ogni par te è una super ficie contenitor e a sè stante e viene r appr esentata da un oggetto Panel. Ecco le pr opr ietà più significative: Bor der Style : pr opr ietà enumer ata che descr ive lo stile dei bor di: assenti (None), a linea singola (Single) o 3D (Fix ed3D) Fix edPanel : specifica quale dei due pannelli debba r estar e di dimensioni fisse dur ante l'atto di r idimensionamento IsSplitter Fix ed : deter mina se lo splitter è fisso o può muover si
  • 307. Or ientation : indica l'or ientamento dei pannelli, se ver ticale o or izzontale Panel1 : r ifer imento al pannello 1; gli Splitter Panel non hanno alcuna pr opr ietà differ ente da Contr ol, e per ciò non vale la pena di soffer mar si altr o tempo su questi Panel1Collapsed : deter mina se all'inizio il Panello 1 sia collssato, ossia pr ivo di dimensione, il che implica che solo il Pannello 2 sia visibile Panel1MinSize : la dimensione minima del Pannello 1; si r ifer isce alla lar ghezza se Or ientation = Ver tical, altr imenti all'altezza Panel2... : le stesse di Panel 1 Splitter Distance : la distanza dello splitter dall'angolo super ior e sinistr o, in pix el Splitter Incr ement : l'incr emento della posizione splitter quando viene mosso dal mouse, in pix el Splitter Width : la lar ghezza dello splitter , in pix el
  • 308. B18. RichTextBox e Syntax Highlightning La RichTex tBox è un contr ollo molto potente e dallo stile simile ai fogli di micr osoft w or d, che mantiene, tuttavia, un layout w indow s 98. Costituisce un potenziamento della tex tbox nor male poichè è in gr ado di visualizzar e dei testi for mattati, ossia contenenti tag che ne definiscono lo stile: gr assetto, sottolineato, bar r ato, cor sivo, color e, gr andezza, font ecc... Come sugger isce il nome, in questi contr olli il più delle volte viene car icato un file con estensione .r tf (r ich tex t for mat). Un esempio gr afico di come potr ebbe appar ir e un testo in una r ichtex tbox : La pr opr ietà e i metodi più impor tanti di una r ichtex tbox sono: AppendTex t(t): aggiunge la str inga t al testo della r ichtex tbox CanRedo / CanUndo: pr opr ietà che deter minano qualor a sia possibile r ifar e o annullar e dei cambiamenti appor tati al testo CaseSensitive: deter mina se la r ix htex tbox faccia differ enza tr a le maiuscole o le minuscole o consider i solamente il testo (vedi Opzioni di Compilazione->Compar e) Clear : cancella tutto il testo della r ichtex tbox Clear Undo: cancella la lista che r ipor ta tutti i cambiamenti effettuati, così che non sia più possibile r ichiamar e la pr ocedur a Undo Copy / Cut / Paste: copia, taglia e incolla il testo selezionato dalla o nella clipboar d DefaultFont / DefaultFor eColor / DefaultBackColor : deter minano r ispettivamente il font, il color e del testo e il color e di sfondo pr eimpostati nella r ichtex tbox DeselectAll: deseleziona tutto (equivale a por r e SelectionLength = 0) DetectUr ls: deter mina qualor a tutti gli indir izzi ur l siano for mattati secondo il calssico stile blu sottlineato dei collegamenti iper testuali Find: impor tantissima funzione che per mette di tr ovar e qualsiasi str inga all'inter no del testo. Ne esistono 4 ver sioni (in r ealtà 7, ma le altr e non sono impor tanti per or a) modificate tr amite over loading: la pr ima chiede di specificar e solo la str inga, la seconda anche le opzioni di r icer ca, la ter za anche l'indice da cui iniziar e la r icer ca e la quar ta anche l'indice a cui ter minar e la r icer ca. Gli indici r ifer iscono una posizione nel testo basandosi sul numer o di car atter i (r icor date, per ò, che gli indici in vb.net sono sempr e a base 0, quindi il pr imo car atter e avr à indice uguale a 0, il secondo a 1 e così via). Le opzioni di r icer ca sono 5, deter minate da un enumer ator e: MatchCase indica se pr ender e in consider azione anche la maiuscole e le minuscole; NoHighlight indica di non evidenziar e il testo tr ovato; None specifica di non far niente; Rever se specifica che bisogna tr ovar e la str inga al contr ar io; WholeWor d, invece, pr ecisa che la str inga deve esser e una par ola a sè stante, quindi, nalla maggior par te dei casi, separ ata da spazi o da punteggiatur a dalle altr e GetChar Fr omPosition(p) / GetChar Index Fr omPosition(p): funzioni che r estituiscono il car atter e (o il suo indice) che si tr ova in un punto pr eciso specificato come par ametr o p GetChar Index Fr omLine(n) / GetChar Index OfCur r entLine: funzioni che r estituiscono r ispettivamente l'indice del pr imo car atter e della linea n e l'indice del pr imo della linea cor r ente, ossia quella su cui è fer mo il cur sor e Lines: r estituisce un ar r ay di str inghe r appr esentanti il testo di ogni r iga della r ichtex tbox LoadFile(f): car ica il file f nella r ix htex tbox : f può esser e anche un nor male file di testo Rtf: r estituisce il testo della r ichtex tbox , includendo tutti i tag r tf SaveFile(f): salva il testo for mattato in un file Select(i, l) / SelectAll: la pr ima pr ocedur a seleziona un testo lungo l a par tir e dall'indice i, mentr e la seconda
  • 309. seleziona tutto SelectedRtf / SelectedTex t: imposta o r estituisce il testo selezionato, sia in modo r tf (con i tag) che in modo nor male (solo testo) Selection...: tutte le pr opr ietà che iniziano con 'Selection' impostano o r estituiscono le opzioni del testo selezionato, come il font, il color e, l'indentazione, l'allineamento ecc... SelectionStar t indica l'indice a cui inizia la selezione, mentr e SelectionLength la sua lunghezza: impostar e questi due par ametr i equivale a r ichiamar e la funzione Select Undo / Redo: annulla l'ultima azione o la r ipete. Le pr opr ietà UndoActionName e RedoActionName r estituiscono il nome di quell'azione ZoomFactor : imposta o r estituisce il fattor e di ingr andimento della r ichtex tbox Si è visto che le oper azioni che si possono eseguir e su questo contr ollo sono numer osissime, una più utile dell'altr a, ma non è finita qui. Oltr e a esser e anche utilissima per contener e testo for mattato, la r ichtex tbox offr e anche str umenti per modificar lo: uno di questi è il Syntax Highlighting, ossia l'evidenziator e di sintassi, pr esente in quasi ogni IDE per linguaggi. Sy ntax Highlighting Questa tecnica consente di evidenziar e deter minate par ole chiave nel testo del contr ollo con un color e o uno stile diver so dal r esto. È il caso delle par ole r iser vate. Sia con Visual Basic Ex pr ess che con Shar pDevelop o Visual Studio, le keyw or d vengono evidenziate con un color e differ ente, di solito in blu. È possibile r ipr odur r e lo stesso compor tamento nella Rix hTex tBox . Ho impiegato del tempo a tr ovar e un codice già fatto r iguar do questo ar gomento e, dopo aver cer cato molto, ci sono r iuscito: sono giunto alla conclusione che questo sia il miglior e della r ete, anche se si può sempr e appor tar e qualche cor r ezione. Si apr a un nuovo pr ogetto Libr er ia di Classi, e s'incolli tutto il codice nella classe Syntax RTB, dopodichè si clicchi Build->Build [Nome pr ogetto] per gener ar e il contr ollo. Nonostante non si sia specificato che la classe r appr esenti un contr ollo, il fatto che essa der ivi da RichTex tBox l'ha implicitamente sugger ito al compilator e. Syntax RTB non è altr o che una RichTex tBox con dei metodi in più per il syntax highlighting. Si tr ascini il contr ollo sul for m nor malmente come una tex tbox . Ecco la classe commentata e r ior dinata: 001. Public Class SyntaxRTB 002. Inherits System.Windows.Forms.RichTextBox 003. 004. 'La funzione SendMessage serve per inviare dati messaggi 005. 'a una finestra o un dispositivo allo scopo di ottenere 006. 'dati valori od eseguire dati compiti 007. Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" _ 008. (ByVal hWnd As IntPtr, ByVal wMsg As Integer, _ 009. ByVal wParam As Integer, ByVal lParam As Integer) As Integer 010. 011. 'Blocca il Refresh della finestra 012. Private Declare Function LockWindowUpdate Lib "user32" _ 013. (ByVal hWnd As Integer) As Integer 014. 015. 'Campo privato che specifica se il meccanismo di syntax 016. 'highlighting è case sensitive oppure no 017. Private _SyntaxHighlight_CaseSensitive As Boolean = False 018. 'La tabella delle parole 019. Private Words As New DataTable 020. 021. Public Property CaseSensitive() As Boolean 022. Get 023. Return _SyntaxHighlight_CaseSensitive 024. End Get 025. Set(ByVal Value As Boolean) 026. _SyntaxHighlight_CaseSensitive = Value 027.
  • 310. End Set 028. End Property 029. 030. 'Contiene costanti usate nell'inviare messaggi all'API 031. 'di windows 032. Private Enum EditMessages 033. LineIndex = 187 034. LineFromChar = 201 035. GetFirstVisibleLine = 206 036. CharFromPos = 215 037. PosFromChar = 1062 038. End Enum 039. 040. 'OnTextChanged è una procedura privata che ha il compito 041. 'di generare l'evento TextChanged: prima di farlo, colora il 042. 'testo, ma in questo caso l'evento non viene più generato 043. Protected Overrides Sub OnTextChanged(ByVal e As EventArgs) 044. ColorVisibleLines() 045. End Sub 046. 047. 'Colora tutta la RichTextBox 048. Public Sub ColorRtb() 049. Dim FirstVisibleChar As Integer 050. Dim i As Integer = 0 051. 052. While i < Me.Lines.Length 053. FirstVisibleChar = GetCharFromLineIndex(i) 054. ColorLineNumber(i, FirstVisibleChar) 055. i += 1 056. End While 057. End Sub 058. 059. 'Colora solo le linee visibili 060. Public Sub ColorVisibleLines() 061. Dim FirstLine As Integer = FirstVisibleLine() 062. Dim LastLine As Integer = LastVisibleLine() 063. Dim FirstVisibleChar As Integer 064. 065. If (FirstLine = 0) And (LastLine = 0) Then 066. 'Non c'è testo 067. Exit Sub 068. Else 069. While FirstLine < LastLine 070. FirstVisibleChar = GetCharFromLineIndex(FirstLine) 071. ColorLineNumber(FirstLine, FirstVisibleChar) 072. FirstLine += 1 073. End While 074. End If 075. 076. End Sub 077. 078. 'Colora una linea all'indice LineIndex, a partire dal carattere 079. 'lStart 080. Public Sub ColorLineNumber(ByVal LineIndex As Integer, _ 081. ByVal lStart As Integer) 082. Dim i As Integer = 0 083. Dim SelectionAt As Integer = Me.SelectionStart 084. Dim MyRow As DataRow 085. Dim Line() As String, MyI As Integer, MyStr As String 086. 087. 'Blocca il refresh 088. LockWindowUpdate(Me.Handle.ToInt32) 089. 090. MyI = lStart 091. 092. If CaseSensitive Then 093. Line = Split(Me.Lines(LineIndex).ToString, " ") 094. Else 095. Line = Split(Me.Lines(LineIndex).ToLower, " ") 096. End If 097. 098. For Each MyStr In Line 099.
  • 311. 'Seleziona i primi MyStr.Length caratteri della linea, 100. 'ossia la prima parola 101. Me.SelectionStart = MyI 102. Me.SelectionLength = MyStr.Length 103. 104. 'Se la parola è contenuta in una delle righe 105. If Words.Rows.Contains(MyStr) Then 106. 'Seleziona la riga 107. MyRow = Words.Rows.Find(MyStr) 108. 'Quindi colora la parola prelevando il colore da 109. 'tale riga 110. If (Not CaseSensitive) Or _ 111. (CaseSensitive And MyRow("Word") = MyStr) Then 112. Me.SelectionColor = Color.FromName(MyRow("Color")) 113. End If 114. Else 115. 'Altrimenti lascia il testo in nero 116. Me.SelectionColor = Color.Black 117. End If 118. 119. 'Aumenta l'indice di un fattore pari alla lunghezza 120. 'della parola più uno (uno spazio) 121. MyI += MyStr.Length + 1 122. Next 123. 124. 'Ripristina la selezione 125. Me.SelectionStart = SelectionAt 126. Me.SelectionLength = 0 127. 'E il colore 128. Me.SelectionColor = Color.Black 129. 130. 'Riprende il refresh 131. LockWindowUpdate(0) 132. End Sub 133. 134. 'Ottiene il primo carattere della linea LineIndex 135. Public Function GetCharFromLineIndex(ByVal LineIndex As Integer) _ 136. As Integer 137. Return SendMessage(Me.Handle, EditMessages.LineIndex, LineIndex, 0) 138. End Function 139. 140. 'Ottiene la prima linea visibile 141. Public Function FirstVisibleLine() As Integer 142. Return SendMessage(Me.Handle, EditMessages.GetFirstVisibleLine, 0, 0) 143. End Function 144. 145. 'Ottiene l'ultima linea visibile 146. Public Function LastVisibleLine() As Integer 147. Dim LastLine As Integer = FirstVisibleLine() + _ 148. (Me.Height / Me.Font.Height) 149. 150. If LastLine > Me.Lines.Length Or LastLine = 0 Then 151. LastLine = Me.Lines.Length 152. End If 153. 154. Return LastLine 155. End Function 156. 157. Public Sub New() 158. Dim MyRow As DataRow 159. Dim arrKeyWords() As String, strKW As String 160. 161. Me.AcceptsTab = True 162. 163. 'Carica la colonna Word e Color 164. Words.Columns.Add("Word") 165. Words.PrimaryKey = New DataColumn() {Words.Columns(0)} 166. Words.Columns.Add("Color") 167. 168. 'Aggiunge le keywords del linguaggio SQL all'array 169. arrKeyWords = New String() {"select", "insert", "delete", _ 170. "truncate", "from", "where", "into", "inner", "update", _ 171.
  • 312. "outer", "on", "is", "declare", "set", "use", "values", "as", _ 172. "order", "by", "drop", "view", "go", "trigger", "cube", _ 173. "binary", "varbinary", "image", "char", "varchar", "text", _ 174. "datetime", "smalldatetime", "decimal", "numeric", "float", _ 175. "real", "bigint", "int", "smallint", "tinyint", "money", _ 176. "smallmoney", "bit", "cursor", "timestamp", "uniqueidentifier", _ 177. "sql_variant", "table", "nchar", "nvarchar", "ntext", "left", _ 178. "right", "like", "and", "all", "in", "null", "join", "not", "or"} 179. 180. 'Quindi le aggiunge una alla volta alla tabella con 181. 'colore rosso 182. For Each strKW In arrKeyWords 183. MyRow = Words.NewRow() 184. MyRow("Word") = strKW 185. MyRow("Color") = Color.LightCoral.Name 186. Words.Rows.Add(MyRow) 187. Next 188. End Sub 189. End Class Il costr uttor e New ha il compito di inizializzar e tutte le infor mazioni iner enti alle par ole ed al lor o color e. La str uttur a della classe utilizza una DataTable in cui ci sono due colonne: Wor d, la par ola da evidenziar e, e Color , il color e da usar e per l'evidenziazione. Ogni r iga contiene quindi queste due infor mazioni, e ci sono tante r ighe quante sono le keyw or ds del linguaggio che si desider a. Color LineNumber è invece commentata nel sor gente. Questi metodi, per ò, sebbene funzionino con il linguaggio di r ifer imento (SQL), per dono di ogni validità con l'HTML, dove le par ola chiave sono attaccate le une alle altr e, ad esempio in: <a href='http://totem.altervista.org'>Link</a> a viene subito dopo la par entesi angolar e, mentr e hr ef pr ima di un uguale. Nonostante il modo più pr eciso in assoluto per scovar e le keyw or ds sia usar e le espr essioni r egolar i, non ancor a anlizzate, per or a si far à in altr o modo. Ecco la classe r iscr itta da me, in modo da adeguar e il funzionamento all'HTML e miglior ando le pr estazioni: 001. Public Class SHRichTextBox 002. Inherits System.Windows.Forms.RichTextBox 003. 004. Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" _ 005. (ByVal hWnd As IntPtr, ByVal wMsg As Integer, _ 006. ByVal wParam As Integer, ByVal lParam As Integer) As Integer 007. 008. Private Declare Function LockWindowUpdate Lib "user32" _ 009. (ByVal hWnd As Integer) As Integer 010. 011. Private Enum EditMessages 012. LineIndex = 187 013. LineFromChar = 201 014. GetFirstVisibleLine = 206 015. CharFromPos = 215 016. PosFromChar = 1062 017. End Enum 018. 019. Protected Overrides Sub OnTextChanged(ByVal e As EventArgs) 020. 'Non colora tutte le linee visibili, bensì solo la riga 021. 'dove si trova il cursorse: in questo modo l'applicazione 022. 'risulta più veloce. L'unico caso in cui questo 023. 'approccio non funzione è quando si copia un testo 024. 'all'interno della richtextbox. In quel caso ci sarà 025. 'un pulsante apposito 026. Dim LineIndex As Int32 = Me.GetLineFromCharIndex(Me.SelectionStart) 027. Me.ColorLineNumber(LineIndex) 028. End Sub 029. 030. 'Colora tutta la RichTextBox 031. Public Sub ColorRtb() 032. For I As Int32 = 0 To Me.Lines.Length - 1 033. ColorLineNumber(I) 034. Next 035.
  • 313. End Sub 036. 037. 'Colora solo le linee visibili 038. Public Sub ColorVisibleLines() 039. Dim FirstLine As Integer = FirstVisibleLine() 040. Dim LastLine As Integer = LastVisibleLine() 041. 042. If (FirstLine = 0) And (LastLine = 0) Then 043. 'Non c'è testo 044. Exit Sub 045. Else 046. While FirstLine < LastLine 047. ColorLineNumber(FirstLine) 048. FirstLine += 1 049. End While 050. End If 051. End Sub 052. 053. 'Questa è la nuova versione: nelle stesse condizioni sopra 054. 'citate, impiega 50ms, quasi la metà! L'algoritmo vecchio 055. 'per SQL ne impiegava 10, ma non era in grado di supportare tag 056. 'vicini come quelli dell'HTML 057. Public Sub ColorLineNumber(ByVal LineIndex As Int32) 058. Try 059. If Me.Lines(LineIndex).Length = 0 Then 060. Exit Sub 061. End If 062. Catch Ex As Exception 063. Exit Sub 064. End Try 065. 066. 'Indice del primo carattere della linea 067. Dim FirstCharIndex As Int32 = _ 068. Me.GetFirstCharIndexFromLine(LineIndex) 069. 'Tiene traccia del cursore 070. Dim SelectionAt As Integer = Me.SelectionStart 071. 072. 'Blocca il refresh 073. LockWindowUpdate(Me.Handle.ToInt32) 074. 075. 'Tiene traccia se ci siano tag aperti 076. Dim TagOpened As Boolean = False 077. 'Indica se il tag ha degli attributi 078. Dim Attribute As Boolean = False 079. 'Indica se un attributo è stato assegnato 080. Dim Assigned As Boolean = False 081. 'Indica, per gli attributi come [readonly], se le parentesi 082. 'sono state aperte 083. Dim AttributeOpened As Boolean = False 084. 'Variabili locali che rappresentano Me.SelectionStart e 085. 'Me.SelectionLength: usando la variable enregistration si 086. 'guadagna qualche millisecondo 087. Dim Start, Length As Int32 088. Dim Max As Int32 = _ 089. (FirstCharIndex + Me.Lines(LineIndex).Length) - 1 090. 091. Me.Select(FirstCharIndex, Max + 1) 092. 093. For Index As Int32 = FirstCharIndex To Max 094. If Char.IsLetterOrDigit(Me.Text(Index)) Then 095. Continue For 096. End If 097. 'Viene aperto un tag, inizia a selezionare 098. 'Es.: <a 099. If Me.Text(Index) = "<" Then 100. Start = Index 101. TagOpened = True 102. Attribute = False 103. Assigned = False 104. ElseIf Me.Text(Index) = ">" Then 105. 'Viene chiuso un tag: se sono stati definiti 106. 'attributi, evidenzia solo la parentesi angolare, 107.
  • 314. 'Es.: <a href='www.example.com'> 108. 'altrimenti tutta la stringa da "<" a ">" 109. 'Es.: <div> 110. If Not Attribute Then 111. Length = Index - Start 112. Me.Select(Start, Length) 113. Me.SelectionColor = Color.Blue 114. End If 115. Me.Select(Index, 1) 116. Me.SelectionColor = Color.Blue 117. Me.DeselectAll() 118. TagOpened = False 119. Attribute = False 120. Assigned = False 121. ElseIf TagOpened AndAlso Me.Text(Index) = " " Then 122. 'Uno spazio: se un attributo è già stato impostato, 123. 'si tratta di uno spazio che separa due attributi, 124. 'quindi passa oltre, definendo solo 125. 'Assigned = False; 126. 'Es.: <div id='1' class='prova'> 127. 'altrimenti è uno spazio che precede qualsiasi 128. 'attributo, che quindi viene dopo la dichiarazione 129. 'del tag, che viene colorato in blu 130. 'Es.: <div id='1'> 131. If Assigned Then 132. Assigned = False 133. Else 134. Length = Index - Start 135. Me.Select(Start, Length) 136. Me.SelectionColor = Color.Blue 137. End If 138. Me.DeselectAll() 139. Start = Index + 1 140. ElseIf TagOpened AndAlso Me.Text(Index) = "=" Then 141. 'Un uguale: a un attributo viene assegnato un 142. 'valore, perciò evidenzia l'attributo, 143. 'dallo spazio precedente fino a = non compreso, 144. 'e lo colore in rosso 145. 'Es.: <table width='100'> 146. Length = Index - Start 147. Me.Select(Start, Length) 148. Me.SelectionColor = Color.Red 149. Me.DeselectAll() 150. Attribute = True 151. Assigned = True 152. ElseIf Me.Text(Index) = "[" Then 153. 'Apre un attributo 154. Start = Index 155. AttributeOpened = True 156. ElseIf Me.Text(Index) = "]" And AttributeOpened Then 157. 'Chiude un attributo 158. 'Es.: <input type='text' [readonly]> 159. Length = Index - Start 160. Me.Select(Start, Length) 161. Me.SelectionColor = Color.Red 162. Me.DeselectAll() 163. AttributeOpened = False 164. End If 165. Next 166. 167. 'Ripristina la selezione 168. Me.SelectionStart = SelectionAt 169. Me.SelectionLength = 0 170. 'E il colore 171. Me.SelectionColor = Color.Black 172. 173. 'Riprende il refresh 174. LockWindowUpdate(0) 175. End Sub 176. 177. 'Ottiene la prima linea visibile 178. Public Function FirstVisibleLine() As Integer 179.
  • 315. Return SendMessage(Me.Handle, EditMessages.GetFirstVisibleLine, 0, 0) 180. End Function 181. 182. 'Ottiene l'ultima linea visibile 183. Public Function LastVisibleLine() As Integer 184. Dim LastLine As Integer = FirstVisibleLine() + _ 185. (Me.Height / Me.Font.Height) 186. 187. If LastLine > Me.Lines.Length Or LastLine = 0 Then 188. LastLine = Me.Lines.Length 189. End If 190. 191. Return LastLine 192. End Function 193. End Class In questa ver sione modificate ci sono par ecchie diver genze: Non viene utilizzata una tabella dei color i: il motivo è semplice; viene eseguito un contr ollo un car atter e alla volta e, quale che sia il nome del tag e dell'attr ibuto specificato, viene comunque color ato. Questa car atter istica ha dei pr egi e dei difetti. Non evidenzia gli er r or i, ma in questo caso si può sempr e r ipr istinar e la tabella per dendo un po' di velocità. Tuttavia evidenzia anche i tag nuovi che vengono usati dai css: ad esempio, questa pagina usava dei tag "<k>", che non esistono nell'HTML ma sono pur sempr e tag, e vengono usati per definir e le keyw or ds e per color ar e il listato. Se si consider a la pr ima ipotesi, sar ebbe meglio utilizzar e una collezione a dizionar io a tipizzazione for te, per spr ecar e meno memor ia. Non divide la str inga: analizza semplicemente un car atter e per volta dall'inizio alla fine. Questo pr ocedimento è assai più r apido e ovviamente non funzioner ebbe con uno split, dato che i tag sono attaccati l'uno all'altr o Non utilizza Color Rtb su OnTex tChanged: dato che il contr ollo è pr ogettato per aiutar e nella scr ittur a, si suppone che chi immetta il codice stia scr ivendo, quindi color a soltanto la linea su cui si sta oper ando e non tutte le linee visibili. Questo contr ibuisce a velocizzar e il meccanismo Per chi avesse letto la ver sione pr ecedente della guida, si sar à cer tamente notato il cambiamento r adicale di algor itmo utilizzato, r ispetto a quello più r udimentale: 01. For Each Word As String In Words 02. I = FirstCharIndex 03. Do 04. I = Me.Find(Word, I, I + Me.Lines(LineIndex).Length, _ 05. RichTextBoxFinds.None) 06. If I >= 0 Then 07. Me.SelectionStart = I 08. Me.SelectionLength = Word.Length 09. 'Qui utilizo un dictionary 10. Me.SelectionColor = Words(Word) 11. I += Word.Length 12. End If 13. Loop While I >= 0 14. Next Quest'ultimo color a solo le par ole indicate, ma esegue almeno (almeno!) un centinaio di contr olli ogni volta, ossia uno per ogni par ola data: se poi queste appaiono nella r iga, il conto r addoppia! Questo appr occio, per far e un esempio, su una linea di 37 car atter i con cinque o sei par ole r iser vate, impiega cir ca 90ms per color ar e, ed il tempo aumenta ver tiginosamente di 10/20ms per ogni car atter e in più. Nel nuovo algor itmo, il tempo è r idotto a cir ca 50ms, con un aumento di 2/3ms per ogni car atter e in più. L'algor itmo iniziale, invece, dovendo analizzar e solo il numer o di par ole della str inga, impiegava, sempr e nelle stesse condizioni, cir ca 10ms, con un aumento di 1/2ms ogni par o la in più. (Bisogna per ò r icor dar e che il pr imo pr oposto color ava tutte le linee visibili ad ogni modifica). Si può capir e quindi come sia vantaggioso quello iniziale in ter mini di tempo, e quanto svantaggioso in ter mini di pr estazioni.
  • 316. Esem pio di Syntax Hig hlig hting
  • 317. B19. PropertyGrid Questo contr ollo è davver o molto complesso: r appr esenta una gr iglia delle pr opr ietà, esattamente la stessa che lo sviluppator e usa per modificar e le car atter istiche dei var i contr olli nel for m designer . La sua enor me potenza sta nel fatto che, attr aver so la r eflection, r iesce a gestir e qualsiasi oggetto con facilità. Le si può associar e un contr ollo del for m, su cui l'utente può agir e a pr opr io piacimento, ma anche una classe, ad esempio le opzioni del pr ogr amma, con cui sar à quindi possibile inter agir e molto semplicemente da un'unica inter faccia. Le pr opr ietà e i metodi impor tanti sono: CollapseAllGr idItems : r iduce al minimo tutte le categor ie Ex pandAllGr idItems : espande al massimo tutto le categor ie Pr oper tySor t : pr opr ietà enumer ata che indica come debbano esser e or dinati gli elementi, se alfabeticamente, per categor ie, per categor ie e alfabeticamente oppur e senza alcun or dinamento Pr oper tyTabs : collezione di tutte le possibili schede della Pr oper tyGr id. Una scheda, ad esempio, è costituita dal pulsante "Or dina alfabeticamente", oppur e, nell'ambiente di sviluppo, dal pulsante "Mostr a eventi" (quello con l'icona del fulmine). Aggiunger ne una significa aggiunger e un pulsante che possa modificar e il modo in cui il contr ollo legge i dati dell'oggetto. Ecco un esempio pr eso da un ar ticolo sull'ar gomento r eper ibile su The Co de Pr o ject: SelectedGr idItem : r estituisce l'elemento selezionato, un oggetto Gr idItem che gode di queste pr opr ietà: Ex pandable : indica se l'elemento è espandibile. Sono espandibili tutte quelle pr opr ietà il cui tipo sia un tipo r efer ence: in par ole pover e, essa deve espor r e al pr opr io inter no altr e pr opr ietà (non sono soggetti a questo compor tamento le str uttur e, in quanto tipi value, a meno che esse non espongano a lor o volta delle pr opr ieta'). Per i tipi definiti dal pr ogr ammator e, la Pr oper tyGr id non è in gr ado di for nir e una r appr esentazione che possa esser e espansa a r un-time: a questo si può supplir e in modo semplice facendo uso di cer ti attr ibuti come si vedr à fr a poco Ex panded : indica se l'elemento è cor r entemente espanso (sono visibili tutti i suoi membr i) Gr idItems : se Ex pandable = Tr ue, questa pr opr ietà r estituisce una collezione di oggetti Gr idItem che r appr esentano tutte le pr opr ietà inter ne a quella cor r ente Gr idItemType : pr opr ietà enumer ata in sola lettur a che specifica il tipo di elemento. Può assumer e quattr o valor i: Ar r ayValue (un oggetto ar r ay o a una collezione in gener e), Categor y (una categor ia), Pr oper ty (una qualsiasi pr opr ieta') e Root (una pr opr ietà di pr imo livello, ossia che non possiede alcun livello ger ar chico al di sopr a di se stessa) Label : il testo dell'elemento Par ent : se la pr opr ietà è un membr o d'istanza di un'altr a pr opr ietà, r estituisce quest'ultima (ossia quella che sta al livello ger ar chico super ior e) Pr oper tyDescr iptor : r estituisce un oggetto che indica come si compor ta la pr opr ietà nella gr iglia, quale sia il suo testo, la descr izione, se sia modificabile o meno (a r un-time o solo dur ante la scr ittur a del pr ogr amma), se sia visualizzata nella gr iglia, quale sia il delegate da invocar e nel momento in cui questa viene modificata e infine, il più impor tante, l'oggetto usato per conver tir e tutta la pr opr ietà in un valor e sintetico di tipo str inga. Tutti questi attr ibuti sono specificati dur ante la scr ittur a di una pr opr ietà che suppor ti la visualizzazione in una Pr oper tyGr id, come si vedr à in seguito Value : r estituisce il valor e della pr opr ieta'
  • 318. SelectedObject : la pr opr ietà più impor tante. Imposta l'oggetto che Pr oper tyGr id gestisce: ogni modifica dell'utente sul contr ollo si r iper cuoter à in manier a identica sull'oggetto, esattamente come avviene nell'ambiente di sviluppo; vengono anche inter cettati tutti gli er r or i di casting e gestiti automaticamente SelectedObjects : è anche possibile far sì che vengano gestiti più oggetti contempor aneamente. Se questi sono dello stesso tipo, ogni modifica si r iper cuoter à su ognuno nella stessa manier a. Se sono di tipo diver so, ver r anno visualizzate solo le pr opr ietà in comune SelectedTab : r estituisce la scheda selezionata In questo capitolo mi concentr er ò sul caso in cui si debba inter facciar e Pr oper tyGr id con un oggetto nuovo cr eato da codice. Binding di c lassi c reate dal programmatore Per far sì che Pr oper tyGr id visualizzi cor r ettamente una classe cr eata dal pr ogr ammator e, basta assegnar e un oggetto di quel tipo alla pr opr ietà SelectedObject, poichè tutto il pr ocesso viene svolto tr amite r eflection. Tuttavia ci sono alcune situazioni in cui questo pr ocesso ha bisogno di un aiuto ester no per funzionar e: quando le pr opr ietà sono di tipo r efer ence (str inghe escluse), non vengono visulizzati tutti i lor o membr i, poichè il contr ollo non è in gr ado di conver tir e un valor e adatto in str inga. Ad esempio, se si deve legger e un oggetto di tipo Per son, il nome e la data di nascita ver r anno analizzati cor r ettamente, ma il campo Fr etello As Per son come ver r à inter pr etato? Non è possibile far star e una classe su una sola r iga, poichè non si conosce il modo di conver tir la in un valor e r appr esentabile (in questo caso, in una str inga). Lo str umento che Vb.Net for nisce per ar ginar e questo pr oblema è un attr ibuto, di nome TypeConver ter , definito nel namespace System.ComponentModel (dove, tr a l'altr o, sono situati tutti gli altr i attr ibuti usati in questo capitolo). Questo accetta come costr uttor e un par ametr o di tipo Type, che espone il tipo di una classe con la funzione di conver titor e. Ad esempio: 01. 'Questa classe ha la funzione di convertire Person in stringa 02. Public Class PersonConverter 03. '(Per convenzione, i convertitori di questo tipo, devono 04. 'terminare con la parola "Converter" 05. '... 06. End Class 07. 08. Public Class Person 09. Private _Name As String 10. Private _Birthday As Date 11. Private _Brother As Person 12. 13. '... 14. 15. 'Per la proprietà Brother (fratello), si applica l'attributo 16. 'TypeConverter, specificando quale sia la classe convertitore. 17. 'Si utilizza solo il tipo perchè la classe, come vedremo 18. 'in seguito, espone solo metodi d'istanza, ma che possono 19. 'essere utilizzati da soli semplicemente fornendo i parametri 20. 'adeguati. Perciò sarà il programma stesso a creare, 21. 'a runtime, un oggetto di questo tipo e ad usarne la funzioni 22. <TypeConverter(GetType(PersonConverter))> _ 23. Public Property Brother() As Person 24. '... 25. End Class Ecco un esempio di come si pr esenter à il contr ollo dopo aver for nito queste dir ettive: La classe che implementa il conver titor e deve er editar e da Ex pandableObjectConver ter (una classe definita anch'essa in System.ComponentModel) e deve sovr ascr iver e tr amite polimor fismo alcune funzioni: CanConver tFr om (deter mina se
  • 319. si può conver tir e da tipo dato), CanConver tTo (deter mina se si può conver tir e nel tipo dato), Conver tFr om (conver te, in questo caso, da Str ing a Per son, e in gener ale al tipo di cui si sta scr ivendo il conver titor e), Conver tTo (conver te, in questo caso, da Per son a Str ing, e in gener ale dal tipo in questione a str inga). Questa er a la par te più difficile, di cui si avr à un buon esempio nel codice a seguir e: quello che bisogna anlizzar e or a consente di definir e alcune piccole car atter istiche per per sonalizzar e l'aspetto di una pr opr ietà. Ecco una lista degli attr ibuti usati e delle lor o descr izioni: DisplayName : modifica il nome della pr opr ietà in modo che venga visualizzata a r un-time un'altr a str inga. Accetta un solo par ametr o del costr uttor e, il nuovo nome (nell'esempio, si r impiazza la denominazione inglese con la r ispettiva tr aduzione italiana) Descr iption : definisce una piccola descr izione per la pr opr ieta' Br ow sable : deter mina se il valor e della pr opr ietà sia modificabile dal contr ollo: l'unico par ametr o del costr uttor e è un valor e Boolean [ReadOnly] : indica se la pr opr ietà è in sola lettur a oppur e no. Come Br ow sable accetta un unico par ametr o booleano DesignOnly : specifica se la pr opr ietà si possa modificar e solo dur ante la scr ittur a del codice e non dur ante l'esecuzione Categor y : il nome della categor ia sotto la quale deve venir e r ipor tata la pr opr ietà: l'unico par ametr o è di tipo Str ing DefaultValue : il valor e di default della pr opr ietà. Accetta diver si over load, a seconda del tipo DefaultPr oper ty : applicato alla classe che r appr esenta il tipo dell'oggetto visualizzato, indica il nome della pr opr ietà che è selezionata di default nella Pr oper tyGr id Pr ima di pr oceder e con il codice, ecco uno scr eenshot di come dovr ebbe appar ir e la veste gr afica in fase di pr ogettazione: C'è anche un'ImageList con un'immagine per gli elementi della listview lstBooks e un Contex tMenuStr ip che contiene le voci "Aggiungi" e "Rimuovi", sempr e assegnato alla listview . Inoltr e, sia la lista che la Pr oper tyGr id sono inser ite all'inter no di uno SplitContiner . Ecco il codice della libr er ia (nel Solution Ex plor er , cliccar e con il pulsante destr o sul pr ogetto, quindi sceglier e Add New Item e poi Class Libr ar y): 001. 'Questo namespace contiene gli attributi necessari a 002. 'impostare le proprietà in modo che si interfaccino 003. 'correttamente con PropertyGrid 004. Imports System.ComponentModel 005. 006. 'Quando si usa uno statementes Imports, la prima voce 007. 'si riferisce al nome del file *.dll in s?. Dato che si 008. 'vuole BooksManager sia consierato come una namespace, non 009. 'bisogna aggiungere un altro namespace BooksManager in questo file 010. 011. 'L'autore del libro, con eventuale biografia 012. Public Class Author 013. 'Il nome completo 014. Private _Name As String 015. 'Data di nascita e morte 016. Private _Birth, _Death As Date 017. 'Indica se l'autore è ancora vivo 018. Private _IsStillAlive As Boolean 019. 'Una piccola biografia 020. Private _Biography As String 021. 022. <DisplayName("Nome"), _ 023. Description("Il nome dell'autore."), _ 024. Browsable(True), _ 025.
  • 320. Category("Generalita'")> _ 026. Public Property Name() As String 027. Get 028. Return _Name 029. End Get 030. Set(ByVal Value As String) 031. _Name = Value 032. End Set 033. End Property 034. 035. <DisplayName("Piccola biografia"), _ 036. Description("Un riassunto delle parti più significative della " & _ 037. "vita dell'autore."), _ 038. Browsable(True), _ 039. Category("Dettagli")> _ 040. Public Property Biography() As String 041. Get 042. Return _Biography 043. End Get 044. Set(ByVal Value As String) 045. _Biography = Value 046. End Set 047. End Property 048. 049. <DisplayName("Data di nascita"), _ 050. Description("La data di nascita dell'autore."), _ 051. Browsable(True), _ 052. Category("Generalita'")> _ 053. Public Property Birth() As Date 054. Get 055. Return _Birth 056. End Get 057. Set(ByVal Value As Date) 058. 'Nessun controllo: la data di nascita può essere 059. 'spostata a causa di uno sbaglio, che altrimenti 060. 'potrebbe produrre un'eccezione 061. _Birth = Value 062. End Set 063. End Property 064. 065. <DisplayName("Data di morte"), _ 066. Description("Data di morte dell'autore."), _ 067. Browsable(True), _ 068. Category("Generalita'")> _ 069. Public Property Death() As Date 070. Get 071. Return _Death 072. End Get 073. Set(ByVal Value As Date) 074. 'Bisogna assicurarsi che la data di morte sia 075. 'posteriore a quella di nascita 076. If Value.CompareTo(Me.Birth) < 1 Then 077. 'Genera un'eccezione 078. Throw New ArgumentException("La data di morte deve " & _ 079. "essere posteriore a quella di nascita!") 080. Else 081. 'Prosegue l'assegnazione 082. _Death = Value 083. 'Impostando la data di morte si suppone che l'autore 084. 'non sia più in vita... 085. Me.IsStillAlive = False 086. End If 087. End Set 088. End Property 089. 090. <DisplayName("Vive"), _ 091. Description("Determina se l'autore è ancora in vita."), _ 092. Browsable(True), _ 093. Category("Generalita'")> _ 094. Public Property IsStillAlive() As Boolean 095. Get 096. Return _IsStillAlive 097.
  • 321. End Get 098. Set(ByVal Value As Boolean) 099. _IsStillAlive = Value 100. End Set 101. End Property 102. 103. 'Un nome e una data di nascita sono obbligatori 104. Sub New(ByVal Name As String, ByVal Birth As Date) 105. Me.Name = Name 106. Me.Birth = Birth 107. Me.IsStillAlive = True 108. End Sub 109. 110. 'Tuttavia, il controllo PropertyGrid richiede un costruttore 111. 'senza parametri 112. Sub New() 113. Me.Birth = Date.Now 114. Me.IsStillAlive = True 115. End Sub 116. End Class 117. 118. Public Class IsbnConverter 119. 'Facendo derivare questa classe da ExpandableObjectConverter 120. 'si comunica al compilatore che questa classe è usata per 121. 'convertire in stringa un valore rappresentabile in una 122. 'PropertyGrid. Così facendo, sarà possibile modificare 123. 'il codice agendo sulla stringa complessiva e non 124. 'obbligatoriamente sulle varie parti 125. Inherits ExpandableObjectConverter 126. 127. 'Determina se sia possibile convertire nel tipo dato 128. Public Overrides Function CanConvertTo(ByVal Context As ITypeDescriptorContext, _ 129. ByVal DestinationType As Type) As Boolean 130. 'Si può convertire in Isbn, dato che questa classe è 131. 'scritta apposta per questo 132. If (DestinationType Is GetType(Isbn)) Then 133. Return True 134. End If 135. Return MyBase.CanConvertFrom(Context, DestinationType) 136. End Function 137. 138. 'Determina se sia possibile convertire dal tipo dato 139. Public Overrides Function CanConvertFrom(ByVal Context As ITypeDescriptorContext, _ 140. ByVal SourceType As Type) As Boolean 141. 'Si può convertire da String, dato che questa classe è 142. 'scritta apposta per questo 143. If (SourceType Is GetType(String)) Then 144. Return True 145. End If 146. Return MyBase.CanConvertFrom(Context, SourceType) 147. End Function 148. 149. 'Converte da stringa a Isbn 150. Public Overrides Function ConvertFrom(ByVal Context As ITypeDescriptorContext, _ 151. ByVal Culture As Globalization.CultureInfo, _ 152. ByVal Value As Object) As Object 153. If TypeOf Value Is String Then 154. Dim Str As String = DirectCast(Value, String) 155. 'Cerca di creare un nuovo oggetto isbn 156. Try 157. Dim Obj As Isbn = Isbn.CreateNew(Str) 158. Return Obj 159. Catch ex As Exception 160. MessageBox.Show(ex.Message, "Books Manager", _ 161. MessageBoxButtons.OK, MessageBoxIcon.Exclamation) 162. Return New Isbn 163. End Try 164. End If 165. Return MyBase.ConvertFrom(Context, Culture, Value) 166. End Function 167. 168. 'Converte da Isbn a stringa 169.
  • 322. Public Overrides Function ConvertTo(ByVal Context As ITypeDescriptorContext, _ 170. ByVal Culture As Globalization.CultureInfo, _ 171. ByVal Value As Object, ByVal DestinationType As Type) As Object 172. If DestinationType Is GetType(String) And _ 173. TypeOf Value Is Isbn Then 174. Dim Temp As Isbn = DirectCast(Value, Isbn) 175. Return Temp.ToString 176. End If 177. Return MyBase.ConvertTo(Context, Culture, Value, DestinationType) 178. End Function 179. End Class 180. 181. 'Il codice ISBN, dal primo gennaio 2007, deve obbligatoriamente 182. 'essere a tredici cifre. Per questo motivo metterò solo 183. 'questo tipo nel sorgente 184. 'P.S.: per convenzione, gli acronimi con più di due lettere devono 185. 'essere scritti in Pascal Case 186. Public Class Isbn 187. 'Un codice è formato da: 188. 'Un prefisso (3 cifre) - solitamente 978 indica un libro in generale 189. Private _Prefix As Int16 = 978 190. 'Un identificativo linguistico (da 1 a 5 cifre): indica 191. 'il paese di provenienza dell'autore - in Italia è 88 192. Private _LanguageID As Int16 = 88 193. 'Un prefisso editoriale (da 2 a 6 cifre): indica l'editore 194. Private _PublisherID As Int64 = 89637 195. 'Un identificatore del titolo 196. Private _TitleID As Int32 = 15 197. 'Un codice di controllo che può variare da 0 a 10. 10 viene 198. 'indicato con X, perciò lo imposto come Char 199. Private _ControlChar As Char = "9" 200. 201. <DisplayName("Prefisso"), _ 202. Description("Prefisso del codice, costituito da tre cifre."), _ 203. Browsable(True), _ 204. Category("Isbn")> _ 205. Public Property Prefix() As Int16 206. Get 207. Return _Prefix 208. End Get 209. Set(ByVal Value As Int16) 210. If Value = 978 Or Value = 979 Then 211. _Prefix = Value 212. Else 213. Throw New ArgumentException("Prefisso non valido!") 214. End If 215. End Set 216. End Property 217. 218. <DisplayName("ID Lingua"), _ 219. Description("Identifica l'area da cui previene l'autore."), _ 220. Browsable(True), _ 221. Category("Isbn")> _ 222. Public Property LanguageID() As Int16 223. Get 224. Return _LanguageID 225. End Get 226. Set(ByVal Value As Int16) 227. _LanguageID = Value 228. End Set 229. End Property 230. 231. <DisplayName("ID Editore"), _ 232. Description("Identifica il marchio dell'editore."), _ 233. Browsable(True), _ 234. Category("Isbn")> _ 235. Public Property PublisherID() As Int32 236. Get 237. Return _PublisherID 238. End Get 239. Set(ByVal Value As Int32) 240. _PublisherID = Value 241.
  • 323. End Set 242. End Property 243. 244. <DisplayName("ID Titolo"), _ 245. Description("Identifica il titolo del libro."), _ 246. Browsable(True), _ 247. Category("Isbn")> _ 248. Public Property TitleID() As Int32 249. Get 250. Return _TitleID 251. End Get 252. Set(ByVal Value As Int32) 253. _TitleID = Value 254. End Set 255. End Property 256. 257. <DisplayName("Carattere di controllo"), _ 258. Description("Verifica la correttezza degli altri valori."), _ 259. Browsable(True), _ 260. Category("Isbn")> _ 261. Public Property ControlChar() As Char 262. Get 263. Return _ControlChar 264. End Get 265. Set(ByVal Value As Char) 266. _ControlChar = Value 267. End Set 268. End Property 269. 270. Public Sub New() 271. 272. End Sub 273. 274. 'Restituisce in forma di stringa il codice 275. Public Overrides Function ToString() As String 276. Return String.Format("{0}-{1}-{2}-{3}-{4}", _ 277. Me.Prefix, Me.LanguageID, Me.PublisherID, _ 278. Me.TitleID, Me.ControlChar) 279. End Function 280. 281. 'Metodo statico factory per costruire un nuovo codice ISBN. Se 282. 'si mettesse questo codice nel costruttore, l'oggetto verrebbe 283. 'comunque creato anche se il codice inserito fosse errato. 284. 'In questo modo, la creazione viene fermata e restituito 285. 'Nothing in caso di errori 286. Shared Function CreateNew(ByVal StringCode As String) As Isbn 287. 'Con le espressioni regolari, ottiene le varie parti 288. Dim Split As New System.Text.RegularExpressions.Regex( _ 289. "(?<Prefix>d{3})-(?<Language>d{1,5})" & _ 290. "-(?<Publisher>d{2,6})-(?<Title>d+)-(?<Control>w)") 291. 292. Dim M As System.Text.RegularExpressions.Match = _ 293. Split.Match(StringCode) 294. 295. 'Se la lunghezza del codice, senza trattini, è di 296. '13 caratteri e il controllo tramite espressioni regolari 297. 'ha avuto successo, procede 298. If StringCode.Length = 17 And M.Success Then 299. Dim Result As New Isbn 300. With Result 301. .Prefix = M.Groups("Prefix").Value 302. .LanguageID = M.Groups("Language").Value 303. .PublisherID = M.Groups("Publisher").Value 304. .TitleID = M.Groups("Title").Value 305. .ControlChar = M.Groups("Control").Value 306. End With 307. Return Result 308. Else 309. Throw New ArgumentException("Il codice inserito è errato!") 310. End If 311. End Function 312. End Class 313.
  • 324. 314. 'Una classe che rappresenta un libro 315. Public Class Book 316. Private _Title As String 317. 'Si suppone che un libro abbia meno di 32767 pagine XD 318. Private _Pages As Int16 = 100 319. 'Si possono anche avere più autori: in questo caso si ha 320. 'una lista a tipizzazione forte. 321. Private _Authors As New List(Of Author) 322. 'L'eventuale serie a cui il libro appartiene 323. Private _Series As String 324. 'Casa editrice 325. Private _Publisher As String 326. 'Data di pubblicazione 327. Private _PublicationDate As Date 328. 'Argomento 329. Private _Subject As String 330. 'Costo in euro 331. Private _Cost As Single = 1.0 332. 'Ristampa 333. Private _Reprint As Byte = 1 334. 'Codice ISBN13 335. Private _Isbn As New Isbn 336. 337. <DisplayName("Titolo"), _ 338. Description("Il titolo del libro."), _ 339. Browsable(True), _ 340. Category("Editoria")> _ 341. Public Property Title() As String 342. Get 343. Return _Title 344. End Get 345. Set(ByVal Value As String) 346. _Title = Value 347. End Set 348. End Property 349. 350. <DisplayName("Collana"), _ 351. Description("La collana o la serie a cui il libro appartiene."), _ 352. Browsable(True), _ 353. Category("Editoria")> _ 354. Public Property Series() As String 355. Get 356. Return _Series 357. End Get 358. Set(ByVal Value As String) 359. _Series = Value 360. End Set 361. End Property 362. 363. <DisplayName("Editore"), _ 364. Description("La casa editrice."), _ 365. Browsable(True), _ 366. Category("Editoria")> _ 367. Public Property Publisher() As String 368. Get 369. Return _Publisher 370. End Get 371. Set(ByVal Value As String) 372. _Publisher = Value 373. End Set 374. End Property 375. 376. <DisplayName("Pagine"), _ 377. Description("Il numero di pagine da cui il libro è composto."), _ 378. DefaultValue("100"), _ 379. Browsable(True), _ 380. Category("Dettagli")> _ 381. Public Property Pages() As Int16 382. Get 383. Return _Pages 384. End Get 385.
  • 325. Set(ByVal Value As Int16) 386. If Value > 0 Then 387. _Pages = Value 388. Else 389. Throw New ArgumentException("Numero di pagine insufficiente!") 390. End If 391. End Set 392. End Property 393. 394. <DisplayName("Autore/i"), _ 395. Description("L'autore o gli autori."), _ 396. Browsable(True), _ 397. Category("Editoria")> _ 398. Public ReadOnly Property Authors() As List(Of Author) 399. Get 400. Return _Authors 401. End Get 402. End Property 403. 404. <DisplayName("Pubblicazione"), _ 405. Description("La data di pubblicazione della prima edizione."), _ 406. Browsable(True), _ 407. Category("Dettagli")> _ 408. Public Property PublicationDate() As Date 409. Get 410. Return _PublicationDate 411. End Get 412. Set(ByVal Value As Date) 413. _PublicationDate = Value 414. End Set 415. End Property 416. 417. <DisplayName("Codice ISBN"), _ 418. Description("Il codice ISBN conformato alla normativa di 13 cifre."), _ 419. Browsable(True), _ 420. Category("Editoria"), _ 421. TypeConverter(GetType(IsbnConverter))> _ 422. Public Property Isbn() As Isbn 423. Get 424. Return _Isbn 425. End Get 426. Set(ByVal Value As Isbn) 427. _Isbn = Value 428. End Set 429. End Property 430. 431. <DisplayName("Ristampa"), _ 432. Description("Il numero della ristampa."), _ 433. DefaultValue(1), _ 434. Browsable(True), _ 435. Category("Dettagli")> _ 436. Public Property Reprint() As Byte 437. Get 438. Return _Reprint 439. End Get 440. Set(ByVal Value As Byte) 441. If Value > 0 Then 442. _Reprint = Value 443. Else 444. Throw New ArgumentException("Ristampa: valore errato!") 445. End If 446. End Set 447. End Property 448. 449. <DisplayName("Costo"), _ 450. Description("Il costo del libro, in euro."), _ 451. Browsable(True), _ 452. Category("Editoria")> _ 453. Public Property Cost() As Single 454. Get 455. Return _Cost 456. End Get 457.
  • 326. Set(ByVal Value As Single) 458. If Value > 0 Then 459. _Cost = Value 460. Else 461. Throw New ArgumentException("Inserire prezzo positivo!") 462. End If 463. End Set 464. End Property 465. End Class E il codice del for m: 01. Class Form1 02. Private Sub strAddBook_Click(ByVal sender As Object, _ 03. ByVal e As EventArgs) Handles strAddBook.Click 04. Dim Title As String = _ 05. InputBox("Inserire il titolo del libro:", "Books Manager") 06. 07. 'Controlla che la stringa non sia vuota o nulla 08. If Not String.IsNullOrEmpty(Title) Then 09. Dim Item As New ListViewItem(Title) 10. Dim Book As New Book() 11. Book.Title = Title 12. Item.ImageIndex = 0 13. Item.Tag = Book 14. lstBooks.Items.Add(Item) 15. End If 16. End Sub 17. 18. Private Sub lstBooks_SelectedIndexChanged(ByVal sender As Object, _ 19. ByVal e As EventArgs) Handles lstBooks.SelectedIndexChanged 20. 'Esce dalla procedura se non ci sono elementi selezionati 21. If lstBooks.SelectedIndices.Count = 0 Then 22. Exit Sub 23. End If 24. 25. 'Altrimenti procede 26. pgBook.SelectedObject = lstBooks.SelectedItems(0).Tag 27. End Sub
  • 327. C1. Introduzione ai database relazionali Il modello r elazionale non è stato il pr imo in assoluto ad esser e usato per la gestione dei database, ma è stato intr odotto più tar di, negli anni '70, gr azie alle idee di E. F. Co dd. Ad oggi, è il modello più diffuso e utilizzato per la sua semplicità. Tale modello si basa su un unico concetto, la r elazio ne, una tabella costituita da r ig he (o r eco r d o tuple) e colonne (o attr ibuti). Per definir e una r elazione, basta specificar ne il nome e gli attr ibuti. Ad esempio: Person (FirstName, LastName, BirthDay) indica una r elazione di nome Per son, che pr esenta tr e colonne, denominate r ispettivamente Fir stName, LastName e Bir thDay. Una volta data la definizione, per ò, è necessar io anche for nir e dei dati che ne r ispettino le r egole: dobbiamo aggiunger e delle r ighe a questa tabella per r appr esentar e i dati che ci inter essano, ad esempio: Relazione Per son Fir stName LastName Bir thDay Mar io Rossi 1/1/1965 Luigi Bianchi 13/7/1971 ... L'insieme di tutte le r ighe della r elazione si dice estensio ne della r e lazio ne, mentr e ogni singola tupla viene anche chiamata istanza di r elazio ne. Facendo un par allelismo con la pr ogr ammazione ad oggetti, quindi, avr emo queste "somiglianze" (che si r iveler anno di vitale impor tanza nella tipizzazione for te, come vedr emo in seguito): Database Programmazione ad oggetti Relazione -> Classe Tupla -> Oggetto Estensione della relazione -> Lista di oggetti Attributo -> Proprietà dell'oggetto Istanza di relazione -> Istanza di classe (= Oggetto) Avendo or a chiar ito questi par allelismi, vi sar à più facile entr ar e nella mentalità del modello r elazionale, dato che, se siete ar r ivati fino a qui, si assume che sappiate già benissimo tutti gli aspetti e i concetti della pr ogr ammazione ad oggetti. Uno sguar do attento, tuttavia, far à notar e che, tr a i car atter i fondamentali che si possono r intr acciar e in questi par allelismi, manca il concetto di "tipo" di un attr ibuto. Infatti, per come abbiamo pr ima definito la r elazione, sar ebbe del tutto lecito immetter e un numer o inter o nel campo Fir stName o una str inga in Bir thDay. Per for tuna, il modello pr evede anche che ogni colonna possegga un dom inio , ossia uno specifico r ange di valor i che essa può assumer e: ciò che noi abbiamo sempr e chiamato tipo. Il tipo di un attr ibuto può esser e scelto tr a una gamma molto limitata: inter i, valor i a vir gola mobile, str inghe (a lunghezza limitata e non), date, car atter i, boolean e dati binar i (ar r ay di bytes). In sostanza, questi sono i tipi pr imitivi o atomici di ogni linguaggio e pr opr io per questo motivo, si dice che il dominio di un attr ibuto può esser e solo di tipo atomico, ossia non è possibile costr uir e tipi di dato complessi come le str uttur e o le classi. Questa peculiar ità sembr er ebbe molto limitativa, ma in r ealtà non è così, poiché possiamo instaur ar e dei collegamenti (o vincoli) tr a una r elazione e l'altr a, gr azie all'uso di elementi detti chiav i.
  • 328. La chiave più impor tante è la chiav e pr im ar ia (pr imar y key), che ser ve ad identificar e univocamente una tupla all'inter no della r elazione. Facendo un par agone con la pr ogr ammazione, se una tupla è assimilabile ad un oggetto ed esistono due tuple con attr ibuti identici, esse non r appr esentano comunque la stessa entità, pr opr io come due oggetti con pr opr ietà uguali non sono lo stesso oggetto. E se per gli oggetti esiste un codice "segr eto" per distinguer li (guid), a cui solo il pr ogr amma ha accesso, così esiste un par ticolar e valor e che ser ve per indicar e senza ombr a di dubbio se due r ecor d sono differ enti: questo valor e è la chiave pr imar ia. Essa è solitamente un numer o inter o positivo ed è anche la pr ima colonna definita dalla r elazione. Modificando la definizione di Per son data pr ecedente, ed intr oducendo anche il dominio degli attr ibuti, si otter r ebbe: 'Questa sintassi è del tutto inventata! 'Serve solo per esemplificare i concetti: Person (ID As Integer, FirstName As String, LastName As String, BirthDay As Date) Relazione Per son ID Fir stName LastName Bir thDay 1 Mar io Rossi 1/1/1965 2 Luigi Bianchi 13/7/1971 ... Per distinguer e le singole r ighe esiste, poi, un'altr a tipologia di chiave, detta chiav e candidata, costituita dal più piccolo insieme di attr ibuti per cui non esistono due tuple in cui quegli attr ibuti hanno lo stesso valor e. In gener ale, tutte le chiavi pr imar ie sono chiavi canditate, a causa della stessa definizione data poco fa; mentr e esistono chiavi candidate che non sono chiavi pr imar ie. Ad esempio, in questo caso, l'insieme degli attr ibuti Fir stName, LastName e Bir thDay costituisce una chiave candidata, poichè è pr aticamente impossibile tr ovar e due per sone con lo stesso nome nate nello stesso gior no alla stessa or a (almeno, è impossibile nella nostr a r elazione for mata da due elementi XD e questo ci basta): quindi, questi tr e attr ibuti soddisfano le condizioni della definizione e identificano univocamente un r ecor d. In gener e, si sceglie una fr a tutte le chiavi candidate possibili che viene assunta come chiave pr imar ia: a r igor di logica, essa dovr à esser e la più semplice di tutte. In questo caso, il singolo numer o ID è molto più maneggevole che non l'insieme di due str inghe e una data. Or a, ammettiamo di aver e una r elazione così definita: Program (ProgramID As Integer, Path As String, Description As String) che indica un qualsiasi pr ogr amma installato su un computer ; e quest'altr a r elazione: User (UserID As Integer, Name As String, MainFolder As String) che indica un qualsiasi utente di quel computer . Ammettiamo anche che la macchina sulla quale sono installati i pr ogr ammi pr esenti nell'estensione della r elazione sia condivisa da più utenti: vogliamo stabilir e, tr amite r elazioni, quale utente possa acceder e a quale pr ogr amma. In questa cir costanza, abbiamo diver se soluzioni possibili, ma una sola è la miglior e: Abbiamo detto che la r elazione Pr ogr am ha una chiave pr imar ia, e la r elazione User pur e. Dato che si tr atta di due tabelle diver se, potr ebbe venir e in mente di stabilir e questa policy di accesso: un utente può acceder e a un pr ogr amma solo se la sua chiave pr imar ia (User ID) coincide con la chiave pr imar ia del pr ogr amma (Pr ogr amID). In questo caso, tuttavia, le cir costanze sono molto r estr ittive, in quanto un utente può usar e uno e un solo pr ogr amma (e vicever sa). La r elazione (in senso letter ale, ossia il collegamento) tr a le due tabelle si dice uno a uno .
  • 329. Aggiungiamo alla r elazione Pr ogr am un altr o attr ibuto User ID, che dovr ebbe indicar ci l'utente che può usar e un dato pr ogr amma. Tuttavia, come abbiamo visto pr ima, i valor i delle colonne devono esser e atomici e per ciò non possiamo inser ir e in quella singola casella tutta un'altr a r iga (anche per chè non sapr emmo che tipo specificar e come dominio). Qui ci viene in aiuto la chiave pr imar ia: sappiamo che nella r elazione User , ogni tupla è univocamente identificata da una e una sola chiave pr imar ia chiamata User ID, quindi indicando una chiave, indichiamo anche la r iga ad essa associata. Per cui, possiamo ben cr ear e un nuovo attr ibuto di tipo inter o (in quanto la chiave è un numer o inter o in questo caso), nel quale specifichiamo l'User ID dell'utente che può usar e il nostr o pr ogr amma. Ad esempio: Program (ProgramID As Integer, Path As String, Description As String, UserID As String) Relazione Pr ogr am Pr ogr amID Path Descr iption User ID 1 C:WINDOWSnotepad.ex e Editor di testo 2 2 C:Pr ogr ammiFir eFox fir efox .ex e Fir eFox w eb br ow ser 1 3 C:Pr ogr ammiWor ld of War cr aftWoW.ex e Wor ld of War cr aft 2 ... Relazione User User ID Name MainFolder 1 Mar io Rossi C:User sMRossi 2 Luigi Bianchi C:User sGigi ... Come evidenziano i color i, il pr ogr amma 1 (notepad) e il pr ogr amma 3 (Wor ld of War cr aft) possono esser e usati dall'utente 2 (Luigi Bianchi), mentr e il pr ogr amma 2 (Ffir efox ) può esser e usato dall'utente 1 (Mar io Rossi). Da un pr ogr amma possiamo r isalir e all'utente associato, contr ollar ne l'identità e quindi consentir ne o pr oibir ne l'uso. Questa soluzione, tuttavia, per mette l'accesso a un dato pr ogr amma da par te di un solo utente, anche se tale utente può usar e più pr ogr ammi. La r elazione che collega User a Pr ogr am è detta uno a m o lti (un utente può usar e più pr ogr ammi). Se la guar diamo al contr ar io, ossia da Pr ogr am a User , è detta m o lti a uno (più pr ogr ammi possono esser e usati da un solo utente). Entr ambr e le pr ospettive sono le due facce della stessa r elazione uno a molti, la più utilizzata. Dato che il pr ecedente tentativo non ha funzionato, pr oviamo quindi a intr odur r e una nuova tabella: UsersPrograms (UserID As Integer, ProgramID As Integer) In questa tabella imponiamo che n on es is ta alcun a chiave primaria. Infatti lo scopo di questa r elazione è un altr o: ad un cer to pr ogr amma associa un utente, ma questo lo si può far e più volte. Ad esempio: Relazione Pr ogr am Pr ogr amID Path Descr iption
  • 330. 1 C:WINDOWSnotepad.ex e Editor di testo 2 C:Pr ogr ammiFir eFox fir efox .ex e Fir eFox w eb br ow ser 3 C:Pr ogr ammiWor ld of War cr aftWoW.ex e Wor ld of War cr aft ... Relazione User User ID Name MainFolder 1 Mar io Rossi C:User sMRossi 1 Luigi Bianchi C:User sGigi ... Relazione User sPr ogr ams User ID Pr ogr amID 1 1 1 2 2 2 2 3 ... Nell'ultima r elazione tr oviamo un 1 (due volte) associamo pr ima ad un 1 e poi ad un 2: significa che lo stesso utente 1 (Mar io Rossi) può acceder e sia al pr ogr amma 1 (notepad) sia al pr ogr amma 2 (fir efox ). Allo stesso modo, l'utente 2 può acceder e sia al pr ogr amma 2 (fir efox ) sia al pr ogr amma 3 (Wor ld of War cr aft). Con l'aggiunta di un'altr a tabella siamo r iusciti a legar e più utenti a più pr ogr ammi. Relazioni tr a tabelle di questo tipo si dicono m o lti a m o lti. In ognuno di questi esempi, l'inter o con cui ci si r ifer isce ad un'altr a tupla viene detto chiav e ester na.
  • 332. C2. Descrizione dei componenti principali Dettagli tec nic i Pr ima di iniziar e, qualche dettaglio tecnico. Per i pr ossimi esempi user ò MySql. Tr ovate una guida su come scar icar lo, configur ar lo e gestir lo nel capitolo A 1 del tutor ial dedicato a LINQ. Oltr e a ciò che viene descr itto in quella sezione, avr emo bisogno di alcune classi per inter facciar ci con MySql, e che non sono pr esenti nell'installazione standar d del fr amew or k. Potete scar icar e gli assemblies necessar i da qui. Essi ver r anno automaticamente installati nella GAC e sar anno accessibili sucessivamente assieme a tutti gli altr i assemblies fondamentali nella scheda ".NET" della finestr a di dialogo "Add Refer ence", già spiegata pr ecedentemente. Per usar li, impor tate i r ifer imenti e aggiungete le dir ettive Impor ts all'inizio del sor gente. Connessione La pr ima cosa da far e per iniziar e a smanettar e su un database consiste pr incipalmente nel collegar si alla macchina sulla quale esso esiste, che possiamo definir e ser ver . L'applicazione è quindi un client (vedi capitolo sui Socket), che instaur a un collegamento non solo fisico (tr amite Inter net), ma anche logico, con il pr ogr amma che for nisce il ser vizio di gestione dei database; nel nostr o caso si tr atta di MySql. Per gli esempi che user ò, l'host, ossia l'elabor ator e che ospita il ser vizio, coincider à con il vostr o stesso computer , ossia ci connetter emo a localhost (127.0.0.1). Se avete installato tutto senza pr oblemi e avviato MySql cor r ettamente, possiamo iniziar e ad analizzar e la pr ima classe impor tante: MySqlConnection. Essa for nisce le funzionalità di connessione sopr acitate mediante due semplici metodi: Open (apr e la connessione) e Close (la chiude). Il suo costr uttor e pr incipale accetta come ar gomento una str inga detta co nnectio n str ing , la quale definisce dove e come eseguir e il collegamento. Tipicamente, è for mata da var ie par ti, separ ate da punti e vir gola, ciascuna delle quali imposta una data pr opr ietà. Eccone un esempio: 01. Imports MySql.Data.MySqlClient 02. 03. '... 04. 05. 'Crea una nuova connessione all'host locale, 06. 'accedendo al servizio come utente "root" e con 07. 'password "root". Se non avete modificato le 08. 'impostazioni di sicurezza, questa coppia di username 09. 'e password è quella predefinita. 10. 'Naturalmente è un'idiozia mantenere queste 11. 'credenziali così ovvie e dovrebbero essere cambiate 12. 'subito, ma per i miei esempi userò sempre root. 13. Dim Conn As New MySqlConnection("Server=localhost; Uid=root; Pwd=root;") 14. 15. Try 16. 'Avvia la connessione 17. Conn.Close() 18. Catch Ex As Exception 19. '... 20. Finally 21. 'E la chiude. Ricordatevi che è sempre bene 22. 'chiudere la connessione anche quando si verifichino 23. 'errori (anzi, soprattutto in queste situazioni). 24. Conn.Close() 25. End Try Esiste anche un'altr a var iante della connection str ing, che si pr esenta come segue: "Data Source=localhost; UserId=root; PWD=root;"
  • 333. Una volta connessi, è possibile effettuar e oper azioni var ie sui database, anche se, a dir la ver ità, noi non abbiamo ancor a nessun database su cui oper ar e... Esec uzione di una query Il ter mine quer y indica una str inga mediante la quale si inter r oga un database per ottener ne infor mazioni, o per cr ear e/modificar e quelle già contenutevi. Le quer y pr esentano una lor o sintassi par ticolar e, la quale, pur var iando legger mente da un gestor e all'altr o (MySql, Sql Ser ver , Or acle, Access, ecceter a...), ader isce ad uno standar d univer sale: l'SQL, appunto (Str uctur ed Quer y Language). In questo capitolo e nei pr ossimi analizzer ò solo qualche semplice esempio di quer y, poiché la tr attazione del linguaggio inter o r ichieder ebbe svar iati capitoli suppletivi che esulano dalle intenzioni di questa guida. Vi invito, comunque, a sceglier e una guida a questo linguaggio, o almeno un r efer ence manual, da legger e in par allelo con i pr ossimi capitoli. Alcuni link inter essanti: W o r ld W ide W eb Co nsor tium , M o r pheus W eb, HTM L.it (SQL) HTM L.it (M ySQL). Per iniziar e, vediamo quale classe gestisca le quer y. Si tr atta di MySqlCommand. Essa espone alcuni costr uttor i, tr a cui uno senza par ametr i, ma tutti gli ar gomenti specificabili sono anche accessibili tr amite le sue pr opr ietà. I membr i significativi sono: Cancel() : cancella l'esecuzione di una quer y in cor so; CommandTex t : indica la quer y stessa; CommandTimeout : il tempo massimo, in millisecondi, oltr e il quale l'esecuzione della quer y viene annullata; Connection : deter mina l'oggetto MySqlConnecction mediante il quale si è connessi al database. Senza connessione, ovviamente, non si potr ebbe far e un bel niente; Ex ecuteNonQuer y() : esegue la quer y specificata e r estituisce il numer o di r ecor d da essa affetti, ossia selezionati, cr eati, cancellati o modificati. Restituisce -1 in caso di fallimento; Ex ecuteReader () : esegue la quer y e r estituisce un oggetto MySqlDataReader che per mette di scor r er e una alla volta le r ighe che sono state selezionate. Il r eader , come sugger isce il nome, "legge" i dati, ma questi sono vir tualmente posti su un flusso immaginar io, poiché la quer y non viene eseguita tutto in un colpo, ma di volta in volta. Vedr emo successivamente, più in dettaglio come usar e un oggetto di questo tipo e quali limitazioni compor ta; Ex ecuteScalar () : esegue la quer y e r estituisce il contenuto della pr ima colonna della pr ima r iga di tutti i r isultati. Restituisce Nothing se la quer y fallisce; Or a, per pr ima cosa, dobbiamo cr ear e un nuovo database: potete far lo manualmente da SQL Yog o da qualsiasi altr o pr ogr amma di gestione, ma io user ò solo codice, per evitar e di impor r e vincoli inutili: 01. 'Potete porre questo sorgente sia 02. 'in una Windows Application sia in una Console Application, 03. 'anche perchè lo useremo solo una volta. 04. Dim Conn As New MySqlConnection("Server=localhost; Uid=root; Pwd=root;") 05. Dim Cmd As New MySqlCommand() 06. 07. Try 08. Conn.Open() 09. Cmd.Connection = Conn 10. 'Crea un nuovo database di nome "appdata" 11. Cmd.CommandText = "CREATE DATABASE appdata;" 12. Cmd.ExecuteNonQuery() 13. Catch Ex As Exception 14. 15. Finally 16. Conn.Close() 17. End Try Quando il nuovo database è cr eato, possiamo modificar e la connection str ing in modo da apr ir e quello desider ato: la sintassi per indicar e il database di default è "Database=[nome db];" oppur e "Initial Catalog=[nome db];". Per
  • 334. esemplificar e questa nuova aggiunta alla str inga di connessione, utilizziamo anche un codice per cr ear e la tabella su cui lavor er emo: 01. Dim Conn As New MySqlConnection("Server=localhost; Database=appdata; Uid=root; Pwd=root;") 02. Dim Cmd As New MySqlCommand() 03. 04. Try 05. Conn.Open() 06. Cmd.Connection = Conn 07. 'Crea una nuova tabella di nome Customers nel database 08. 'appdata. I suoi attributi (colonne) sono: 09. ' - ID : identificativo numerico del record; non può essere 10. ' vuoto, viene autoincrementato quando si aggiunge una 11. ' nuova riga ed è la chiave primaria della 12. ' relazione Customers 13. ' - FirstName : nome del cliente (max 150 caratteri) 14. ' - LastName : cognome del cliente (max 150 caratteri) 15. ' - Address : indirizzo (max 255 caratteri) 16. ' - PhoneNumber : numero telefonico (max 30 caratteri) 17. Cmd.CommandText = "CREATE TABLE Customers (ID int NOT NULL AUTO_INCREMENT, FirstName char(150), LastName char(150), Address char(255), PhoneNumber char(30), PRIMARY KEY (ID));" 18. Cmd.ExecuteNonQuery() 19. Catch Ex As Exception 20. 21. Finally 22. Conn.Close() 23. End Try Con lo stesso pr ocedimento, è anche possibile inser ir e tuple nella tabella mediante il "comando" inser t into: INSERT INTO Customers VALUES(1, 'Mario', 'Rossi', 'Via Roma 89, Milano', '50 288 41 971'); Qui potete tr ovar e una cinquantina di quer ies cr eate a r andom da eseguir e sulla tabella per inser ir e qualche valor e, giusto per aver e un po' di dati su cui lavor ar e. Enumerazione di rec ord Come accennato, pr ecedentemente, quando si esegue Ex ecuteReader , viene r estituito un oggetto MySqlDataReader che per mette di scor r er e i r isultati di una quer y. Ecco una br eve descr izione dei suoi membr i: Close() : chiude il r eader . Dato che mentr e il r eader è aper to, nessuna oper azione può esser e eseguita sul database, è sempr e obbligator io chiuder e l'oggetto dopo l'uso; FieldCount : indica il numer o di attr ibuti della r iga cor r ente; Get...(i) : tutte le funzioni il cui nome inizia per "Get" ser vono per ottener e il valor e della i-esima colonna sottofor ma di un par ticolar e tipo; HasRow s : deter mina se il r eader contenga almeno un r ecor d da legger e; IsClosed : indica se l'oggetto è stato chiuso; IsDbNull(i) : r estituisce Tr ue se il campo i-esimo del r ecor d cor r ente non contiene un valor e (r appr esentato dalla costante DBNull.Value); Read() : legge una nuova r iga e r estituisce Tr ue se l'oper azione è r iuscita. False significa che non c'è più nulla da legger e; Ecco un esempio di come usar e il Reader , in un'applicazione Window s For m con una listview e un pulsante: 01. Imports MySql.Data.MySqlClient 02. 03. Class Form1 04. 05. Private Sub btnLoad_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
  • 335. Handles btnLoad.Click 06. Dim Conn As New MySqlConnection("Server=localhost; Database=appdata; Uid=root; Pwd=root;") 07. Dim Command As New MySqlCommand 08. 09. Try 10. 'Seleziona tutti i record della tabella Customers, 11. 'includendovi tutti gli attributi. Questa query è 12. 'equivalente a: 13. ' SELECT ID, FirstName, LastName, Address, PhoneNumber FROM Customers 14. Command.CommandText = "SELECT * FROM Customers;" 15. Command.Connection = Conn 16. Conn.Open() 17. 18. 'Esegue la query e restituisce il reader 19. Dim Reader As MySqlDataReader = Command.ExecuteReader() 20. 21. lstRecords.Columns.Clear() 22. 'Aggiunge tante colonne alla listview quanti sono 23. 'gli attributi dei record selezionati. È possibile 24. 'ottenere il nome di ogni colonna con la funzione 25. 'GetName di MySqlDataReader 26. For I As Int32 = 0 To Reader.FieldCount - 1 27. lstRecords.Columns.Add(Reader.GetName(I)) 28. Next 29. 30. Dim L As ListViewItem 31. Dim S(Reader.FieldCount - 1) As String 32. 33. 'Fintanto che c'è qualche record da leggere, 34. 'lo aggiunge alla listview 35. Do While Reader.Read() 36. 'Riempie l'array S con i valori degli attributi 37. 'della tupla corrente. Se una cella non contiene 38. 'valori, mette una stringa vuota al suo posto 39. For I As Int32 = 0 To S.Length - 1 40. If Not Reader.IsDBNull(I) Then 41. 'GetString(I) ottiene il valore della cella 42. 'I sottoforma di stringa 43. S(I) = Reader.GetString(I) 44. Else 45. S(I) = "" 46. End If 47. Next 48. L = New ListViewItem(S) 49. lstRecords.Items.Add(L) 50. Loop 51. 52. 'Chiude il Reader 53. Reader.Close() 54. Catch ex As Exception 55. MessageBox.Show(ex.Message, Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Exclamation) 56. Finally 57. 'Chiude la connessione 58. Conn.Clone() 59. End Try 60. End Sub 61. 62. End Class
  • 337. C3. Un esempio pratico Applicando i concetti del capitolo scor so, ho scr itto un piccolo esempio pr atico di applicazione basata su database, ossia un semplicissimo gestionale per or ganizzar e la tabella Customer s. L'inter faccia è questa: Il contr ollo vuoto è una ListView con View =Details e HideSelection=False. Le tr e tex tbox hanno un tag associato: la pr ima ha tag "Fir stName", la seconda "LastName", la ter za "Addr ess" e la quar ta "PhoneNumber ". Ecco il codice: 001. Imports MySql.Data.MySqlClient 002. 003. Class Form1 004. Private Conn As MySqlConnection 005. 006. 'Esegue una query sul database, quindi carica i 007. 'risultati nella listview 008. Private Sub LoadData(ByVal Query As String) 009. Dim Command As New MySqlCommand 010. 011. Command.CommandText = Query 012. Command.Connection = Conn 013. 014. Dim Reader As MySqlDataReader = Command.ExecuteReader() 015. 016. If lstRecords.Columns.Count = 0 Then 017. For I As Int32 = 0 To Reader.FieldCount - 1 018. lstRecords.Columns.Add(Reader.GetName(I)) 019. Next 020. End If 021. 022. Dim L As ListViewItem 023. Dim S(Reader.FieldCount - 1) As String 024. 025. lstRecords.Items.Clear() 026. Do While Reader.Read() 027. For I As Int32 = 0 To S.Length - 1 028. If Not Reader.IsDBNull(I) Then 029.
  • 338. S(I) = Reader.GetString(I) 030. Else 031. S(I) = "" 032. End If 033. Next 034. L = New ListViewItem(S) 035. lstRecords.Items.Add(L) 036. Loop 037. 038. Reader.Close() 039. Command.Dispose() 040. Command = Nothing 041. End Sub 042. 043. Private Sub LoadData() 044. Me.LoadData("SELECT * FROM Customers") 045. End Sub 046. 047. 'Scorciatoia per eseguire una query velocemente 048. Private Function ExecuteQuery(ByVal Query As String) As Int32 049. Dim Command As New MySqlCommand(Query, Conn) 050. Dim Result As Int32 = Command.ExecuteNonQuery() 051. 052. Command.Dispose() 053. Command = Nothing 054. 055. Return Result 056. End Function 057. 058. Private Sub Form_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load 059. Conn = New MySqlConnection("Server=localhost; Database=appdata; Uid=root; Pwd=root;") 060. 061. Try 062. Conn.Open() 063. Catch ex As Exception 064. Conn.Close() 065. MessageBox.Show(ex.Message, Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Exclamation) 066. Me.Close() 067. End Try 068. 069. LoadData() 070. End Sub 071. 072. Private Sub btnAdd_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAdd.Click 073. If ExecuteQuery(String.Format("INSERT INTO Customers VALUES(null, '{0}', '{1}', '{2}', '{3}');", txtFirstName.Text, txtLastName.Text, txtAddress.Text, txtPhoneNumber.Text)) Then 074. MessageBox.Show("Cliente aggiunto!", Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Information) 075. LoadData() 076. End If 077. End Sub 078. 079. Private Sub btnEdit_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEdit.Click 080. If lstRecords.SelectedIndices.Count = 0 Then 081. MessageBox.Show("Nessun record selezionato!", Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Exclamation) 082. Exit Sub 083. End If 084. 085. Dim ID As Int32 = CType(lstRecords.SelectedItems(0).SubItems(0).Text, Int32) 086. Dim Query As New System.Text.StringBuilder() 087. 088. 'L'istruzione UPDATE aggiorna i campi della tabella 089. 'specificata usando i valori posti dopo la clausola 090. 'SET. Solo i record che rispettano i vincoli imposti 091. 'dalla clausola WHERE vengono modificati 092. Query.Append("UPDATE Customers SET") 093.
  • 339. For Each T As TextBox In New TextBox() {txtFirstName, txtLastName, txtAddress, txtPhoneNumber} 094. Query.AppendFormat(" {0} = '{1}',", T.Tag.ToString(), T.Text) 095. Next 096. 'Rimuove l'ultima virgola... 097. Query.Remove(Query.Length - 1, 1) 098. 099. Query.AppendFormat(" WHERE ID = {0};", ID) 100. 101. ExecuteQuery(Query.ToString()) 102. Query = Nothing 103. LoadData() 104. End Sub 105. 106. Private Sub btnFilter_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnFilter.Click 107. Dim Query As New System.Text.StringBuilder() 108. Dim Conditions As New List(Of String) 109. 110. Query.Append("SELECT * FROM Customers") 111. 112. 'La scrittura: 113. ' Field LIKE '%Something%' 114. 'equivarrebbe teoricamente a: 115. ' Field.Contains(Something) 116. For Each T As TextBox In New TextBox() {txtFirstName, txtLastName, txtAddress, txtPhoneNumber} 117. If Not String.IsNullOrEmpty(T.Text) Then 118. Conditions.Add(String.Format("WHERE {0} LIKE '%{1}%'", T.Tag.ToString(), T.Text)) 119. End If 120. Next 121. 122. If Conditions.Count >= 1 Then 123. Query.AppendFormat(" {0}", Conditions(0)) 124. If Conditions.Count > 1 Then 125. For I As Int32 = 1 To Conditions.Count - 1 126. Query.AppendFormat(" AND {0}", Conditions(I)) 127. Next 128. End If 129. End If 130. Query.Append(";") 131. 132. LoadData(Query.ToString()) 133. Query = Nothing 134. End Sub 135. 136. Private Sub Form1_FormClosing(ByVal sender As System.Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles MyBase.FormClosing 137. If Conn.State <> ConnectionState.Closed Then 138. Conn.Close() 139. End If 140. End Sub 141. 142. Private Sub btnReload_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnReload.Click 143. LoadData() 144. End Sub 145. 146. Private Sub lstRecords_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lstRecords.SelectedIndexChanged 147. If lstRecords.SelectedItems.Count = 0 Then 148. Exit Sub 149. End If 150. 151. Dim Selected As ListViewItem = lstRecords.SelectedItems(0) 152. txtFirstName.Text = Selected.SubItems(1).Text 153. txtLastName.Text = Selected.SubItems(2).Text 154. txtAddress.Text = Selected.SubItems(3).Text 155. txtPhoneNumber.Text = Selected.SubItems(4).Text 156. End Sub 157. End Class
  • 341. C4. Dalle relazioni agli oggetti - Parte I Usar e quer ies per manipolar e il database è un mezzo molto efficacie, anche se il pr ocesso per cr ear e una quer y sottofor ma di str inga può r isultar e alquanto macchinoso in alcuni casi. A questo pr oposito, vor r ei invitar vi a legger e le pr ime lezioni del tutor ial che ho scr itto r iguar do a LINQ, il linguaggio di quer ying integr ato disponibile dalla ver sione 2008 del linguaggio (fr amew or k v3.5). In questo capitlo, invece, inizier emo a passar e dalle r elazioni, ossia dalle tabelle del database nel lor o ambiente, agli oggetti, tr asponendo, quindi, tutte le oper azioni a costr utti che già conosciamo. Possiamo r appr esentar e un database e le sue tabelle in due modi: Mediante l'appr occio standar d, con le classi DataSet e DataTable, che r appr esentano esattamente il database, con tutte le sue pr opr ietà e car atter istiche. Queste classi astr aggono tutta la str uttur a r elazione e la tr aspor tano nel linguaggio ad oggetti; Mediante l'appr occio LINQ, con nor mali classi scr itte dal pr ogr ammator e, ar tificalmente collegate tr amite attr ibuti e metadati, alle r elazioni pr esenti nel database; Vedr emo or a solo il pr imo appr occio, poiché il secondo è tr attato già nel tutor ial menzionato pr ima. DataSet La classe DataSet ha lo scopo di r appr esentar e un database. Mediante un apposito oggetto, detto Adapter (che analizzer emo in seguito), è possibile tr avasar e tutti i dati del database in un oggetto DataSet e quindi oper ar e su questo senza bisogno di quer y. Una volta ter minate le elabor azioni, si esegue il pr ocesso inver so aggior nando il database con le nuove modifiche appor tate al DataSet. Questo è il pr incipio di base con cui si affr onta il passaggio dalle r elazioni agli oggetti. Questa classe espone una gr an quantità di membr i, tr a cui menzioniamo i più impor tanti: AcceptChanges() : confer ma tutte le modifiche appor tate al DataSet; questo metodo deve esser e r ichiamato obbligator iamente pr ima di iniziar e la pr ocedur a di aggior namento del database a cui è collegato; CaseSensitive : indica se la compar azione di campi di tipo str ing avviene in modalità case-sensitive; Clear () : elimina tutti i dati pr esenti nel DataSet; Clone() : esegue una clonazione deep dell'oggetto DataSet e r estituisce la nuova istanza; DataSetName : nome del DataSet; GetChanges() : r estituisce una copia del DataSet in cui sono pr esenti tutti i dati modificati (come se si fosse r ichiamato AcceptChanges()); GetXml() : r estituisce una str inga contenente la r appr esentazione x ml di tutti i dati pr esenti nel DataSet; HasChanges : deter mina se il DataSet sia stato modificato dall'ultimo salvataggio o car icamento; IsInitialized : indica se è inizializzato; Mer ge(D As DataSet) : unisce D al DataSet cor r ente. Le tabelle e i dati di D vengono aggiunti all'oggetto cor r ente; RejectChanges() : annulla tutte le modifiche appor tate dall'ultimo salvataggio o car icamento; ReadXml(R) : legge un file XML mediante l'oggetto R (di tipo XmlReader ) e tr asfer isce i dati ivi contenuti nel DataSet; Reset() : annulla ogni modifica appor tata e r ipor ta il DataSet allo stato iniziale (ossia come er a dopo il car icamento);
  • 342. Tables : r estituisce una collezione di oggetti DataTable, che r appr esentano le tabelle pr esenti nel DataSet (e quindi nel database); DataTable Se un DataSet r appr esenta un database, allor a un oggetto di tipo DataTable r appr esenta un singola tabella, composta di r ighe e colonne. Nessun nuovo concetto da intr odur r e, quindi: si tr atta solo di una classe che r appr esenta ciò che abbiano visto fin or a per una r elazione. I suoi membr i sono pr essoché simili a quelli di DataSet, con l'aggiunta delle pr opr ietà Columns, Row s, Pr imar yKey e del metodo AddRow (ho menzionato solo quelli di uso più fr equente). Caric amento e binding Or a possiamo car icar e i dati in un DataSet ed eseguir e un binding ver so un contr ollo. Abbiamo già il concetto di binding nei capitoli sulla r eflection, in r ifer imento alla possibilità di legar e un identificator e a un valor e. In questo caso, pur essendo fondamentalmente la stessa cosa, il concetto è legger mente differ ente. Noi vogliamo legar e un cer to insieme di valor i ad un contr ollo, in modo che esso li visualizzi senza dover scr iver e un codice par ticolar e per inser ir li. Il contr ollo che, per eccellenza, r ende gr aficamente nel modo miglior e le tabelle è DataGr idView . Assumendo, quindi, di aver e nel for m solo un contr ollo DataGr idView 1, possiamo scr iver e questo codice: 01. Imports MySql.Data.MySqlClient 02. Class Form1 03. Private Data As New DataSet 04. 05. Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load 06. Dim Conn As New MySqlConnection("Server=localhost; Database=appdata; Uid=root; Pwd=root;") 07. Dim Adapter As New MySqlDataAdapter 08. 09. Conn.Open() 10. 11. Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Customers;", Conn) 12. Adapter.Fill(Data) 13. 14. Conn.Clone() 15. 16. DataGridView1.DataSource = Data.Tables(0) 17. End Sub 18. 19. End Class DataGr idView 1 ver r à r iempito con tutti i dati pr esenti nella pr ima (e unica) tabella del dataset. DataSet tipizzati Nel pr ossimo esempio vedr emo di accennar e alla costr uzione di una semplice applicazione per gestir e br ani musicali con un database, ed eventualmente intr odur r ò un po' di codice per la r ipr oduzione audio. In questo esempio, per ò, non user emo un gener ico database, ma uno specifico database di cui conosciamo le pr opr ietà e della cui esistenza siamo cer ti. Quindi far emo a meno di usar e un gener ico DataSet, ma ne cr eer emo uno specifico per quel database, che sia più semplice da manipolar e. User emo, quindi, un DataSet tipizzato. Non dovr emo scr iver e alcuna r iga di codice per far questo, poiché baster à "disegnar e" la str uttur a del database con uno specifico str umento che il nostr o IDE mette a disposizioni, e che si chiama Table Designer . Mediante quest'ultimo, possiamo delinar e lo schema di un database e delle sue tabelle, e l'ambiente di sviluppo pr ovveder à a scr iver e un codice adeguato per cr ear e un dataset tipizzato specifico per quel database. A livello pr atico, un dataset tipizzato non è altr o che una classe der ivata da DataSet che definisce alcune pr opr ietà e metodi atti a semplificar e la scr ittur a di codice. Ad esempio, invece di r efer enziar e la pr ima cella
  • 343. della pr ima r iga di una tabella, ottener ne il valor e e conver tir lo in inter o, la ver sione tipizzata del dataset espone dir ettamente una pr opr ietà ID (ad esempio) che fa tutto questo. C'è solo un difetto nel codice autogener ato, ma lo illustr er ò in seguito. Pr ima di iniziar e, bisogna cr ear e effettivamente le tabelle che user emo nel database AppData. Per questo pr ogr amma, ho ideato tr e tabelle: author s, songs e albums, costr uite come segue: CREATE TABLE `albums` ( `ID` int(11) NOT NULL AUTO_INCREMENT, `Name` char(255) NOT NULL, `Year` int(11) DEFAULT NULL, `Description` text, `Image` text, PRIMARY KEY (`ID`) ); CREATE TABLE `authors` ( `ID` int(11) NOT NULL AUTO_INCREMENT, `Name` char(255) NOT NULL, `Nickname` char(255) DEFAULT NULL, `Description` text, `Image` text, PRIMARY KEY (`ID`) ); CREATE TABLE `songs` ( `ID` int(11) NOT NULL AUTO_INCREMENT, `Path` char(255) NOT NULL, `Title` char(255) DEFAULT NULL, `Author` int(11) DEFAULT NULL, `Album` int(11) DEFAULT NULL, PRIMARY KEY (`ID`) ); [Gli accenti tonici sono stati aggiunti da SQLyog, e sono obbligator i solo se il nome della colonna o della tabella contiene degli spazi.] Pr ima di pr oceder e, potr ebbe esser e utile mostr ar e la toolbar di gestione delle basi di dati: per far questo, cliccate con il pulsante destr o su uno spazio vuoto della toolbar e spuntate Data Design per far appar ir e le r elative icone:
  • 344. Per aggiunger e un nuovo dataset tipizzato, invece, cliccate sempr e col destr o sul nome del pr ogetto nel solution ex plor er , scegliete Add Item e quindi DataSet. Dovr ebbe appar ir vi un nuovo spazio vuoto simile a questo: Spostando il mouse sulla toolbox a fianco, potr ete notar e che è possibile aggiunger e tabelle, r elazioni, quer ies e alcune altr e cose. Dato che dobbiamo r icr ear e la stessa str uttur a del database AppData, è necessar io cr ear e tr e tabelle: Albums, Author s e Songs, ciascuna con le stesse colonne di quelle sopr a menzionate. Tr ascinate un componente DataTable all'inter no della scher mata e r inominatelo in Songs, quindi fate click col destr o sull'header della tabella e scegliete Add > Column: Aggiungete quindi tante colonne quante sono quelle del codice SQL. Or a selezionate la pr ima colonna (ID) e por tate in
  • 345. pr imo piano la finestr a della pr opr ietà (la stessa usata per i contr olli). Essa visualizzer à alcune infor mazioni sulla colonna ID. Per r ispettar e il vincolo con il database r eale, essa deve esser e dichiar ata di tipo inter o, deve suppor tar e l'autoincr emento e non può esser e NULL: Or a fate lo stesso con tutte le altr e colonne, tenendo conto che char (255) e tex t sono entr ambi contenibili dallo stesso tipo (Str ing). Pr ima di passar e alla compilazione delle altr e tabelle, r icor datevi di impostar e ID come chiave pr imar ia: cliccate ancor a sull'header della tabella, scegliendo Add > Key: Bene. Come avr ete sicur amente notate, i campi Author e Album di Songs non sono str inghe, bensì inter i. Infatti, come abbiamo visto qualche capitolo fa, è possibile collegar e logicamente due tabelle tr amite una r elazione (uno-a-uno, uno-a-molti o molti-a-molti). In questo caso, vogliamo collegar e al campo Author di una canzone, la tupla che r appr esenta quell'autor e nella tabella Author s. Questa è una r elazione uno-a-molti (in questo pr ogr amma semplificato, assumiamo che tutti color o che hanno par tecipato alla r ealizzazione siano consider ati "autor i", senza le var ie
  • 346. distinzioni tr a autor e dei testi, ar tist, compositor i ecceter a...). Mediante l'editor integr ato nell'ambiente di sviluppo possiamo anche aggiunger e questa r ealzione (che andr à a popolar e la pr opr ietà Relations del DataSet). Basta aggiunger e un oggetto Relation e compilar e i campi r elativi: Una volta completati tutti i passaggi, è possibile iniziar e a scr iver e qualche r iga di codice (non dimenticatevi di r iempir e il database con qualche tupla di esempio pr ima di iniziar e il debug). Music a, maestro! Ecco l'inter faccia del pr ogr amma:
  • 347. Oltr e ai contr olli che si vedono nell'immagine, ho aggiunto anche il dataset tipizzato cr eato pr ima dall'editor , AppDataSet. Dato che nella listbox sulla sinistr a visualizzer emo i titoli delle canzoni, possiamo eseguir e un binging di tali dati sulla listbox . Dopo aver la selezionata, andate nella pr opr ietà DataSour ce e scegliete la tabella Songs: Dopodiché, impostate il campo DisplayMember su Title e ValueMember su ID: come avevo spiegato nel capitolo sulla listbox , queste pr opr ietà ci per mettono di modificar e cosa viene visualizzato coer entemente con gli oggetti immagazzinati nella lista. Se avete fatto tutto questo, l'IDE cr eer à automaticamente un nuovo oggetto di tipo BindingSour ce (il SongsBindingSour ce dell'immagine pr ecedente). Esso ha il compito di mediar e tr a la sor gente dati e l'inter faccia utente, e ci sar à utile in seguito per la r icer ca. Ecco il codice: 001. Public Class Form1 002. 003. Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load 004. Dim Conn As New MySqlConnection("Server=localhost; Database=appdata; Uid=root; Pwd=root;") 005. Dim Adapter As New MySqlDataAdapter 006. 007. Conn.Open() 008. 009. 'Carica le tabelle nel dataset. Le tabelle sono ora 010. 'accessibili mediante omonime proprietà dal 011. 'dataset tipizzato 012. 013. Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Songs;", Conn) 014. Adapter.Fill(AppDataSet.Songs) 015. 016. Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Authors;", Conn) 017. Adapter.Fill(AppDataSet.Authors) 018. 019.
  • 348. Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Albums;", Conn) 020. Adapter.Fill(AppDataSet.Albums) 021. 022. Conn.Clone() 023. End Sub 024. 025. Private Sub lstSongs_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lstSongs.SelectedIndexChanged 026. If lstSongs.SelectedIndex < 0 Then 027. Exit Sub 028. End If 029. 030. 'Trova la riga con ID specificato. Il tipo SongsRow è stato 031. 'definito dall'IDE durante la creazione del dataset tipizzato. 032. Dim S As AppDataSet.SongsRow = AppDataSet.Songs.FindByID(lstSongs.SelectedValue) 033. 034. lblName.Text = S.Title 035. 036. tabAuthor.Tag = Nothing 037. 'Anche il metodo IsAuthorNull è stato definito dall'IDE. 038. 'In generale, per ogni proprietà per cui non è stata 039. 'specificata la clausola NOT NULL, l'IDE crea un metodo per 040. 'verificare se quel dato attributo contiene un valore 041. 'nullo. Equivale a chiamare S.IsNull(3). 042. If Not S.IsAuthorNull() Then 043. 'Ottiene un array che contiene tutte le righe della 044. 'tabella Authors che soddisfano la relazione definita 045. 'tra Songs e Authors. Dato che la chiave esterna della 046. 'tabella figlio era un ID, la realzione, pur essendo 047. 'teoricamente uno-a-molti, è in realtà 048. 'uno-a-uno. Perciò, se questo array ha almeno 049. 'un elemento, ne avrà solo uno. 050. Dim Authors() As AppDataSet.AuthorsRow = S.GetAuthorsRows() 051. 'Come IsNull, questo metodo equivale a chiamare 052. 'S.GetChildRows("Songs_Authors") 053. 054. If Authors.Length > 0 Then 055. Dim Author As AppDataSet.AuthorsRow = Authors(0) 056. 057. lblAuthorName.Text = Author.Name 058. If Not Author.IsNicknameNull() Then 059. lblAuthorName.Text &= vbCrLf & Chr(34) & Author.Nickname & Chr(34) 060. End If 061. If Not Author.IsImageNull() Then 062. imgAuthor.Image = Image.FromFile(Author.Image) 063. Else 064. imgAuthor.Image = Nothing 065. End If 066. 067. If Not Author.IsDescriptionNull() Then 068. txtAuthorDescription.Text = Author.Description 069. Else 070. txtAuthorDescription.Text = "" 071. End If 072. tabAuthor.Tag = Author.ID 073. End If 074. End If 075. 076. tabAlbum.Tag = Nothing 077. If Not S.IsAlbumNull() Then 078. Dim Albums() As AppDataSet.AlbumsRow = S.GetAlbumsRows() 079. 080. If Albums.Length > 0 Then 081. Dim Album As AppDataSet.AlbumsRow = Albums(0) 082. 083. lblAlbumName.Text = Album.Name 084. If Not Album.IsYearNull() Then 085. lblAlbumYear.Text = Album.Year 086. Else 087. lblAlbumYear.Text = "" 088. End If 089. If Not Album.IsImageNull() Then 090.
  • 349. imgAlbum.Image = Image.FromFile(Album.Image) 091. Else 092. imgAlbum.Image = Nothing 093. End If 094. If Not Album.IsDescriptionNull() Then 095. txtAlbumDescription.Text = Album.Description 096. Else 097. txtAlbumDescription.Text = "" 098. End If 099. tabAlbum.Tag = Album.ID 100. End If 101. End If 102. End Sub 103. 104. Private Sub btnSearch_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSearch.Click 105. If Not String.IsNullOrEmpty(txtSearch.Text) Then 106. 'La proprietà Filter di un binding source è come 107. 'una condizione SQL. In questo caso cerchiamo tutte le 108. 'canzoni il cui titolo contenga la stringa specificata. 109. SongsBindingSource.Filter = String.Format("title like '%{0}%'", txtSearch.Text) 110. Else 111. SongsBindingSource.Filter = "" 112. End If 113. End Sub 114. 115. End Class
  • 350. C5. Dalle relazioni agli oggetti - Parte II Aggiungere, eliminare, modific are L'ultimo esempio di codice per metteva solo di scor r er e elementi già pr esenti nel database, ma questo è davver o poco utile all'utente. Vediamo, allor a, di aggiunger e un po' di dinamismo all'applicazione. Volendo gestir e tutto in manier a or dinata, sar ebbe bello che ci fosse un contr ollo dedicato a visualizzar e, modificar e e salvar e le infor mazioni sull'autor e e uno identico per l'album. A questo scopo, possiamo scr iver e dei nuovi contr olli utente. Io ho scr itto solo il pr imo, poiché il codice per il secondo è pr essoché identico: 01. Public Class AuthorViewer 02. Private _Author As AppDataSet.AuthorsRow 03. 04. 'Evento generato quando un autore viene aggiunto. Questo 05. 'evento si verifica se l'utente salva dei cambiamenti 06. 'quando la proprietà Author è Nothing. 07. 'Non potendo modificare una riga esistente, quindi, ne 08. 'viene creata una nuova. Poich´, tuttavia, questo 09. 'autore deve essere associato alla canzone, bisogna che 10. 'qualcuno ponga l'ID della nuova riga nel campo 11. 'Author della canzone opportuna e, dato che questo 12. 'controllo non può n´ logicamente né 13. 'praticamente arrivare a fare ciò, bisogna che 14. 'qualcun altro se ne occupi. 15. Public Event AuthorAdded As EventHandler 16. 17. Public Property Author() As AppDataSet.AuthorsRow 18. Get 19. Return _Author 20. End Get 21. Set(ByVal value As AppDataSet.AuthorsRow) 22. If value IsNot Nothing Then 23. _Author = value 24. With value 25. lblName.Text = .Name 26. If Not .IsImageNull() Then 27. imgAuthor.ImageLocation = .Image 28. Else 29. imgAuthor.ImageLocation = Nothing 30. End If 31. If Not .IsDescriptionNull() Then 32. txtDescription.Text = .Description 33. Else 34. txtDescription.Text = "" 35. End If 36. End With 37. Else 38. lblName.Text = "Nessun nome" 39. imgAuthor.ImageLocation = Nothing 40. txtDescription.Text = "" 41. End If 42. imgSave.Visible = False 43. End Set 44. End Property 45. 46. Private Sub imgAuthor_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles imgAuthor.Click 47. 'FOpen è un OpenFileDialog dichiarato nel designer. 48. 'Questo codice serve per caricare un'immagine da disco 49. 'fisso 50. If FOpen.ShowDialog = DialogResult.OK Then 51. imgAuthor.ImageLocation = FOpen.FileName 52. imgSave.Visible = True 53. End If 54.
  • 351. End Sub 55. 56. 'L'immagine del floppy diventa visibile solo quando c'è stata 57. 'una modifica, ossia è stato cambiato uno di questi 58. 'parametri: nome, immagine, descrizione. 59. Private Sub txtDescription_TextChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles txtDescription.TextChanged 60. imgSave.Visible = True 61. End Sub 62. 63. Private Sub lblName_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lblName.Click 64. Dim NewName As String = InputBox("Inserire nome:") 65. 66. If Not String.IsNullOrEmpty(NewName) Then 67. lblName.Text = NewName 68. imgSave.Visible = True 69. End If 70. End Sub 71. 72. Private Sub imgSave_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles imgSave.Click 73. If _Author Is Nothing Then 74. 'Crea la nuova riga e la inserisce nel dataset 75. 'principale. Notare che questo approccio non è 76. 'il migliore possibile, poich´ è sempre 77. 'consigliabile rendere il codice il più generale 78. 'possibile, e limitare i riferimenti agli altri form. 79. 'Sarebbe stato più utile rendere AppDataSet 80. 'visibile all'intero progetto mediante un 81. 'modulo pubblico. 82. _Author = My.Forms.Form1.AppDataSet.Authors.AddAuthorsRow(lblName.Text, "", txtDescription.Text, imgAuthor.ImageLocation) 83. 'Genera l'evento AuthorAdded 84. RaiseEvent AuthorAdded(Me, EventArgs.Empty) 85. Else 86. _Author.Name = lblName.Text 87. _Author.Description = txtDescription.Text 88. _Author.Image = imgAuthor.ImageLocation 89. End If 90. imgSave.Visible = False 91. End Sub 92. End Class E questa è l'inter faccia:
  • 352. È pr esente uno split container , in cui nella par te sinistr a c'è la pictur ebox (con dock=fill) e nella par te destr a la tex tbox . L'immagine del floppy ser ve per avviar e il salvataggio dei dati nell'oggetto Author sRow sotteso (ma non nel database). E questo è il codice dell'applicazione, modificato in modo da suppor tar e il nuovo contr ollo (solo per l'autor e): 001. Imports MySql.Data.MySqlClient 002. 003. Public Class Form1 004. 005. Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load 006. Dim Conn As New MySqlConnection("Server=localhost; Database=appdata; Uid=root; Pwd=root;") 007. Dim Adapter As New MySqlDataAdapter 008. 009. Conn.Open() 010. 011. Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Songs;", Conn) 012. Adapter.Fill(AppDataSet.Songs) 013. 014. Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Authors;", Conn) 015. Adapter.Fill(AppDataSet.Authors) 016. 017. Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Albums;", Conn) 018. Adapter.Fill(AppDataSet.Albums) 019. 020. Conn.Clone() 021. End Sub 022. 023. Private Sub lstSongs_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lstSongs.SelectedIndexChanged 024. If lstSongs.SelectedIndex < 0 Then 025. Exit Sub 026. End If 027. 028. Dim S As AppDataSet.SongsRow = AppDataSet.Songs.FindByID(lstSongs.SelectedValue) 029. 030. lblName.Text = S.Title 031. 032. tabAuthor.Tag = Nothing 033. If Not S.IsAuthorNull() Then 034. Dim Authors() As AppDataSet.AuthorsRow = S.GetAuthorsRows() 035. 036. 'Imposta la proprietà Author del controllo avAuthor, 037. 'che non è altro che un'istanza di AuthorViewer, 038. 'il controllo utente creato poco fa 039. If Authors.Length > 0 Then 040. Dim Author As AppDataSet.AuthorsRow = Authors(0) 041. avAuthor.Author = Author 042. Else 043. avAuthor.Author = Nothing 044. End If 045. End If 046. 047. tabAlbum.Tag = Nothing 048. If Not S.IsAlbumNull() Then 049. Dim Albums() As AppDataSet.AlbumsRow = S.GetAlbumsRows() 050. 051. If Albums.Length > 0 Then 052. Dim Album As AppDataSet.AlbumsRow = Albums(0) 053. 054. lblAlbumName.Text = Album.Name 055. If Not Album.IsYearNull() Then 056. lblAlbumYear.Text = Album.Year 057. Else 058. lblAlbumYear.Text = "" 059.
  • 353. End If 060. If Not Album.IsImageNull() Then 061. imgAlbum.Image = Image.FromFile(Album.Image) 062. Else 063. imgAlbum.Image = Nothing 064. End If 065. If Not Album.IsDescriptionNull() Then 066. txtAlbumDescription.Text = Album.Description 067. Else 068. txtAlbumDescription.Text = "" 069. End If 070. tabAlbum.Tag = Album.ID 071. End If 072. End If 073. End Sub 074. 075. Private Sub btnSearch_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSearch.Click 076. If Not String.IsNullOrEmpty(txtSearch.Text) Then 077. SongsBindingSource.Filter = String.Format("title like '%{0}%'", txtSearch.Text) 078. Else 079. SongsBindingSource.Filter = "" 080. End If 081. End Sub 082. 083. Private Sub avAuthor_AuthorAdded(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles avAuthor.AuthorAdded 084. If lstSongs.SelectedIndex < 0 Then 085. Exit Sub 086. End If 087. 088. Dim S As AppDataSet.SongsRow = AppDataSet.Songs.FindByID(lstSongs.SelectedValue) 089. 'Imposta il campo Author della canzone corrente 090. S.Author = avAuthor.Author.ID 091. End Sub 092. 093. Private Sub Form1_FormClosing(ByVal sender As System.Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles MyBase.FormClosing 094. Dim Conn As New MySqlConnection("Server=localhost; Database=appdata; Uid=root; Pwd=root;") 095. Dim Adapter As New MySqlDataAdapter 096. 097. 'Un oggetto di tipo CommandBuilder genera automaticamente 098. 'tutti le istruzioni update, insert e delete che servono 099. 'a un adapter per funzionare. Tali istruzioni vengono 100. 'generate relativamente alla tabella dalla quale si stanno 101. 'caricando i dati 102. Dim Builder As MySqlCommandBuilder 103. 104. Conn.Open() 105. 106. Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Songs;", Conn) 107. Builder = New MySqlCommandBuilder(Adapter) 108. Adapter.Update(AppDataSet.Songs) 109. 110. Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Authors;", Conn) 111. Builder = New MySqlCommandBuilder(Adapter) 112. Adapter.Update(AppDataSet.Authors) 113. 114. Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Albums;", Conn) 115. Builder = New MySqlCommandBuilder(Adapter) 116. Adapter.Update(AppDataSet.Albums) 117. 118. Conn.Clone() 119. End Sub 120. End Class
  • 354. C6. Il controllo BindingNavigator Funzionamento Questo contr ollo per mette di navigar e attr aver so insiemi di dati, siano essi tabelle di database o semplici liste di oggetti non fa differ enza, per mettendo di visualizzar e o modificar e una qualsiasi delle lor o pr opr ietà e di aggiunger e od eliminar e uno qualsiasi dei suoi elementi. La par ticolar ità che lo distingue da qualsiasi altr o contr ollo del gener e (come potr ebber o esser e ListView o DataGr idView ) consiste nel fatto che la sua inter faccia non è una tabella: anzi, è a pr ior i indefinita. Se si consider a poi il fatto che aggiunger lo semplicemente al for m non por ter à alcun r isultato, si potr ebbe pensar e che BindingNavigator è pr opr io una fr egatur a XD In effetti, per veder lo funzionar e cor r ettamente bisogna aggiunger e un po' di altr i contr olli e scr iver e qualche r iga di codice. Infatti, ho appena detto che esso per mette di navigar e attr aver so un insieme di dati e visualizzar e tali dati su una cer ta inter faccia gr afica che per or a non conosciamo: le incognite, quindi, sono due, ossia da do v e attinger e i dati e co m e visualizzar li. Per questo motivo, sono necessar i almeno altr i due componenti. Il pr imo di questi è un contr ollo BindingSour ce, il quale, come già visto nel capitolo pr ecedente, si pr eoccupa di gestir e e mediar e l'inter azione con una cer ta r isor sa di infor mazioni. Il secondo (e gli altr i eventuali) è ar bitr ar io e dipende dalla natur a dei dati da visualizzar e: per delle str inghe, ad esempio, avr emo bisogno di una Tex tBox . Autori illustri... Per esemplificar e il compor tamento di BindingNavigator , ecco una semplice applicazione che per mette di visualizzar e una ser ie di gr andi nomi e le lor o oper e pr incipali. La nostr a fonte di dati sar à una lista di oggetti di tipo Author , classe così definita: 01. Public Class Form1 02. 03. Public Class Author 04. Private _Name As String 05. Private _Works As List(Of String) 06. 07. Public Property Name() As String 08. Get 09. Return _Name 10. End Get 11. Set(ByVal value As String) 12. _Name = value 13. End Set 14. End Property 15. 16. Public ReadOnly Property Works() As List(Of String) 17. Get 18. Return _Works 19. End Get 20. End Property 21. 22. Public Sub New() 23. _Works = New List(Of String) 24. End Sub 25. 26. Public Sub New(ByVal Name As String, ByVal ParamArray WorksNames() As String) 27. Me.New() 28. Me.Name = Name 29. Me.Works.AddRange(WorksNames) 30. End Sub 31. End Class 32. 33.
  • 355. Public Authors As New List(Of Author) 34. 35. End Class Or a aggiungiamo al for m un BindingNavigator di nome bnMain: All'aspetto sembr a solo una toolstr ip con qualche button, due label e una tex tbox . È questo, e anche di più. Aggiungiamo or a un BindingSour ce di nome bsData e impostiamo la pr opr ietà bsMain.BindingSour ce su bsData. Aggiungete altr i contr olli in modo che l'inter faccia sia la seguente: Vor r emo usar e il pulsanti fr eccia del binding navigator per spostar ci avanti e indietr o nella lista, e i r ispettivi pulsanti per aggiunger e o eliminar e un elemento. Il codice: 01. Public Class Form1 02. 03. Public Class Author 04. Private _Name As String 05. Private _Works As List(Of String) 06. 07. Public Property Name() As String 08. Get 09. Return _Name 10. End Get 11. Set(ByVal value As String) 12. _Name = value 13. End Set 14. End Property 15. 16. Public ReadOnly Property Works() As List(Of String) 17. Get 18. Return _Works 19. End Get 20. End Property 21. 22. Public Sub New() 23. _Works = New List(Of String) 24. End Sub 25. 26. Public Sub New(ByVal Name As String, ByVal ParamArray WorksNames() As String) 27. Me.New() 28. Me.Name = Name 29. Me.Works.AddRange(WorksNames) 30. End Sub 31. End Class 32. 33. Public Authors As New List(Of Author) 34. 35. Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load 36. 'Aggiungie alcuni elementi iniziali alla lista 37. Authors.Add(New Author("Dante Alighieri", "Comedìa", "Vita Nova", "De vulgari eloquentia", "De Monarchia")) 38. Authors.Add(New Author("Luigi Pirandello", "Il fu Mattia Pascal", "Uno, nessuno, centomila", "Il gioco delle parti")) 39. 'Imposta la sorgente di dati del bindingsource 40. bsAuthors.DataSource = Authors 41. 42. 'Aggiunge un binding alla textbox. Ciò significa 43. 'che la proprietà Text di txtName sarà da 44. 'ora in poi sempre legata alla proprietà Name 45. 'dell'elemento corrente della sorgente di dati di 46. 'bsAuthors. Dato che quest'ultima è una lista di 47. 'Author, ogni suo elemento espone la proprietà 48. 'Name. 49. txtName.DataBindings.Add("Text", bsAuthors, "Name") 50.
  • 356. 51. 'Non possiamo fare la stessa cosa con lstWorks.Items, 52. 'poiché Items è una proprietà readonly. 53. 'Questo capita abbastanza spesso: si ha bisogno di 54. 'visualizzare una lista per ogni elemento dell'insieme. 55. 'La soluzione consiste nel caricare gli items della 56. 'lista quando viene caricato un nuovo elemento 57. End Sub 58. 59. Private Sub bsAuthors_CurrentChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles bsAuthors.CurrentChanged 60. 'L'evento CurrentChanged si verifica quando la proprietà 61. 'Current del binding source viene modificata. Ciò significa 62. 'che l'utente si è spostato tramite il binding 63. 'navigator. Ottenendo l'oggetto Current, possiamo risalire alla 64. 'lista di stringhe che esso contiene 65. Dim Author As Author = bsAuthors.Current 66. lstWorks.Items.Clear() 67. lstWorks.Items.AddRange(Author.Works.ToArray()) 68. End Sub 69. 70. Private Sub btnAdd_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAdd.Click 71. 'Aggiunge alla listbox e alla lista Works un nuovo 72. 'titolo aggiunto dall'utente 73. If Not String.IsNullOrEmpty(txtAdd.Text) Then 74. lstWorks.Items.Add(txtAdd.Text) 75. DirectCast(bsAuthors.Current, Author).Works.Add(txtAdd.Text) 76. txtAdd.Text = "" 77. End If 78. End Sub 79. 80. Private Sub btnDelete_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnDelete.Click 81. 'Elimina una delle opere visualizzate 82. If lstWorks.SelectedIndex >= 0 Then 83. DirectCast(bsAuthors.Current, Author).Works.RemoveAt(lstWorks.SelectedIndex) 84. lstWorks.Items.RemoveAt(lstWorks.SelectedIndex) 85. End If 86. End Sub 87. End Class Come vedete, il codice è molto r idotto anche se l'applicazione suppor ta un numer o più elevato di funzionalità: tutto ciò che non abbiamo scr itto viene automaticamente gestito dal BindingNavigator . La pr opr ietà DataBindings, per inciso, non appar tiene solo a Tex tBox , ma a tutti i contr olli e non è necessar io specificar e come sor gente di dati un binding sour ce, ma un qualsiasi oggetto, poiché tutto viene gestito tr amite r eflection. È possibile associar e una qualsiasi pr opr ietà di un contr ollo ad un campo di un qualsiasi altr o oggetto. Allo stesso modo, è possibile associar e alla pr opr ietà DataSour ce di BindingSour ce una tabella di un database, o un dataset (e associate un dataset, dovr ebe usar e la pr opr ietà DataMember per specificar e quale tabella).
  • 357. C7. DataGridView - Parte I Introduzione DataGr idView è uno dei contr olli più potenti e gr andi del Fr amew or k .NET. Consente la visualizzazione di dati in una tabella ed è per questo motivo for temente cor r elato all'uso dei database, anche se natur almente suppor ta, tr amite la pr opr ietà DataSour ce, il binding di qualsiasi oggetto: Ecco un elenco delle pr opr ietà inter essanti: Ar eAllCellsSelected(includeInvisible As Boolean) : r estituisce Tr ue se tutte le celle sono selezionate. Se includeInvisible è Tr ue, include nel contr ollo anche quelle colonne che nor malmente sono nascoste (è possibile nasconder e colonne indesider ate, come ad esempio l'ID); Allow User To AddRow s, DeleteRow s, Or der Columns, ResizeColumns, ResizeRow s: una ser ie di pr opr ietà booleane che deter minano se l'utente sia o meno in gr ado di aggiunger e o r imuover e r ighe, or dinar e le colonne o r idimensionar e sia le r ighe che le colonne; Alter natingRow sDefaultCellStyle : specifica il CellStyle (un insieme di pr opr ietà che definiscono l'aspetto estetico di una cella) per le r ighe di posto dispar i. Se questo valor e è diver so da DefaultCellStyle, avr emo le r ighe di stile alter nato (ad esempio una di un color e e una di un altr o), da cui il nome di questo membr o; AutoResize... : tutti i metodi che iniziano con "AutoResize" ser vono per r idimensionar e r ighe o colonne; AutoSizeColumnsMode : pr opr ietà enumer ata che deter mina in che modo le colonne vengano r idimensionate quando del testo va oltr e i confini visibili. I valor i che può assumer e sono: None : le colonne r imangono sempr e della stessa lar ghezza, a meno che l'utente non le r idimensioni manualmente; AllCells : la colonna viene allar gata affinché il testo di tutte le celle sottostanti e della stessa intestazione sia visibile; AllCellsEx ceptHeader : la colonna viene allar gata in modo che solo il testo di tutte le celle sottostanti sia visibile; ColumnHeader : la colonna viene allar gata in modo che il testo dell'header sia inter amente visibile; DisplayedCells : come AllCells, ma solo per le celle visibili nei mar gini del contr ollo; DisplayedCellsEx ceptHeader : come AllCellsEx ceptHeader , ma solo per le celle visibili nei mar gini del contr ollo; Fill : le colonne vengono r idimensionate affinché la lor o lar ghezza totale sia quanto più possibile vicina all'ar ea effettivamente visibile a scher mo, nei limiti imposti dalla pr opr ietà MinimumWidth di ciascuna colonna. AutoSizeRow sMode : come sopr a, ma per le r ighe; CancelEdit() : ter mina l'editing di una cella e annulla tutte le modifiche ad essa appor tate; CellBor der Style : pr opr ietà enumer ata che definisce come sia visualizzato il bor do delle celle. Inutile descr iver ne tutti i valor i: basta pr ovar li tutti per veder e come appaiono; Clear Selection: deseleziona tutte le celle selezionate ColumnCount / Row Count : deter mina il numer o iniziale di colonne o r ighe visualizzate sul contr ollo ColumnHeader s / Row Header s : imposta lo stile di visualizzazione, i bor di, le dimensioni e la visibilità delle intestazioni Columns : insieme di tutte le colonne del contr ollo. Tr amite questa pr opr ietà è possibile deter minar e quali siano
  • 358. i tipi di valor i che si possono immetter e in una cella. Nella finestr a di dialogo dur ante la scr ittur a del pr ogr amma, infatti, quando si aggiunge una colonna non a r untime, viene anche chiesto quale debba esser e il suo tipo, pr oponendo una gamma abbastanza ampia di possibilità (tex tbox , combobox , checkbox , image, button, linklabel) Cur r entCell : imposta o r estituisce la cella selezionata (un oggetto di tipo DataGr idView Cell) Cur r entCellAddr ess : r estituisce due coor dinate sotto for ma di Point che indicano la colonna e la r iga della cella selezionata Cur r entRow : indica la r iga contenente la cella selezionata (o la r iga selezionata se tutte le sue celle sono selezionate); DefaultCellStyle : specifica lo stile pr edefinito per una cella; ogni cella, poi, può modificar e il pr opr io aspetto mediante la pr opr ietà Style; DisplayedPar tialColumns/Row s(includePar tial As Boolean) : r estituisce il numer o di colonne / r ighe visibili nel contr ollo. Se includePar tial è Tr ue, include nel conteggio anche quelle che si vedono solo par zialmente; EditMode : pr opr ietà enumer ata che indica in che modo sia possibile iniziar e a modificar e il contenuto di una cella. I valor i che può assumer e sono: EditOnEnter : inizia la modifica quando la cella viene selezionata, quando r iceve il focus oppur e quando viene pr emuto invio su di essa; EditOnF2 : inizia la modifica quando l'utente pr eme F2 sulla cella selezionata; EditOnKeystr oke : inizia la modifica quando viene pr emuto un qualsiasi tasto (alfanumer ico) mentr e la cella ha il focus; EditOnKeystr okeOr F2 : è palese... EditPr ogr ammatically : inizia la modifica so lo quando viene esplicitamente r ichiamato da codice il metodo BeginEdit; Fir stDisplayedCell : ottiene o imposta un r ifer imento alla pr ima cella visualizzata; GetCellCount(Filter ) : r estituisce il numer o di celle che soddisfano il filtr o impostato. Filter non à altr o che un valor e enumer ato codificato a bit che contempla questi valor i: Displayed (celle visualizzate), Fr ozen (celle che è impossibile scr ollar e), None (celle che sono in stato di default), ReadOnly (celle a sola lettur a), Resizable and ResizableSet (se specificati entr ambi, indicano le celle r idimensionabili), Selected (celle selezionate), Visible (celle visibili, nel senso che è possibile veder le, non che sono effettivamente visualizzate); HitTest(x , y) : r estituisce un valor e str uttur ato contenente r iga e colonna della cella che si tr ova alle coor dinate r elative x e y (in pix el); IsCur r entCellDir ty : indica se la cella cor r ente contiene modifiche non salvate; IsCur r entCellInEditMode : indica se la cella cor r ente è in modalità edit (modifica); IsCur r entRow Dir ty : indica se la r iga cor r ente contiene modifiche non salvate; Item(x ,y): r estituisce la cella alle coor dinate x e y (colonna e r iga). La sua pr opr ietà più impor tante è Value, che r estituisce o imposta il valor e contenuto nella cella, che può esser e un testo, un valor e booleano, una combobox ecceter a MultiSelect: deter mina qualor a sia possibile selezionar e più celle, colonne o r ighe insieme; Row ... : tutte le pr opr ietà che iniziano con "Row " sono analoghe a quelle spiegate per Column; ReadOnly : deter mina se l'utente possa o meno modificar e i contenuto delle celle SelectedCells/Columns/Row s : r estituisce un insieme delle celle/colonne/r ighe cor r entemente selezionate; SelectionMode : pr opr ietà enumer ata che indica come debba avvenir e la selezione. Può assumer e 5 valor i: CellSelect (solo la cella), FullRow Select (tutta la r iga), FullColumnSelect (tutta la colonna), Row Header Select (solo l'intestazione della r iga) o ColumnHeader Select (solo l'intestazione della colonna); Show CellToolTip : indica se visualizzar e il tooltip della cella; Show EditingIcon : indica se visualizzar e l'icona di modifica; Selected Cells, Columns, Row : tr e collezioni che r estituiscono un insieme di tutte le celle, colonne o r ighe selezionate;
  • 359. Sor t(a, b): utilissima pr ocedur a che or dina la colonna a della datagr idview secondo un or dine ascendente o discendente definito da un enumer ator e di tipo ComponentModel.ListSor tDir ection; Standar dTab : indica se il pulsante Tab viene usato per ciclar e i contr olli (tr ue) o le celle all'inter no del datagr idview (false); Come avete visto c'è una mar ea di membr i, e un numer o consistente di questi sono dedicati ad impostar e l'"estetica" del contr ollo. Ma tutto questo non è nulla se confr ontato alla quantità di eventi che DataGr idView espone (e al numer o di distur bi mentali che è solita causar e nei pr ogr ammator i sani). Un c lassic o esempio di gestionale La DataGr idView è un contr ollo usatissimo sopr attutto in quei noiosissimi pr ogr ammi che qualcuno chiama gestionali, e che un gr an numer o di pover i pr ogr ammator i è costr etto a scr iver e per guadagnar si il pane quotidiano. Per questo motivo, il pr ossimo esempio sar à par ticolar mente r ealistico. Andr emo a scr iver e un'applicazione per gestir e clienti e or dini. Pr ima di iniziar e, cr eiamo le tabelle che ci ser vir anno per il pr ogr amma (sì, user emo un database): CREATE TABLE `customers` ( `ID` int(11) NOT NULL AUTO_INCREMENT, `FirstName` char(150) DEFAULT NULL, `LastName` char(150) DEFAULT NULL, `Address` char(255) DEFAULT NULL, `PhoneNumber` char(30) DEFAULT NULL, `RegistrationDate` date NOT NULL, `AccountType` int(5) DEFAULT '0', PRIMARY KEY (`ID`) ); CREATE TABLE `orders` ( `ID` int(11) NOT NULL AUTO_INCREMENT, `CustomerID` int(11) NOT NULL, `ItemName` char(255) NOT NULL, `ItemPrice` float unsigned NOT NULL, `ItemCount` int(10) unsigned DEFAULT '1', `CreationDate` date NOT NULL, `Settled` tinyint(1) NOT NULL, PRIMARY KEY (`ID`) ); E, per completezza, bisogna aggiunger e anche le r ispettive r appr esentazioni tabular i in un nuovo dataset (si tr atta solo di r icopiar e). Lo scopo del pr ogr amma consiste nel gestir e una ser ie di clienti e di or dini associati, indicando lo stato di ciascuno e le sue condizioni di pagamento. Avr emo, quindi, una datagr idview per contener e i clienti, una per conter e gli or dini e una listview per visualizzar e un r iepilogo delle infor mzioni. Inoltr e, iniziamo subito con una casistica un po' complicata: pr ima di tutto vogliamo che il tipo dell'account di ciascun cliente sia visualizzato sottofor ma di icona; poi vogliamo anche che la seconda datagr idview visualizzi so lo gli or dini associati al cliente cor r entemente selezionato. La pr ima r ichiesta ver r à gestita completamente da codice, ma è oppor tuno che si aggiunga un'ImageList contenente almeno tr e piccole immagini (16x 16 vanno bene), mentr e per la seconda avr emo bisogno un po' di aiuto da par te di BindingSour ce. Nei capitoli pr ecedenti, infatti, si è visto che questo componente espone una pr opr ietà Filter mediante la quale possiamo r estr inger e l'insieme di dati visualizzati sotto cer ti par ametr i (aggiungete quindi anche un BindingSour ce di nome bsOr der s, ed impostate il suo DataSour ce sul dataset pr incipale, e il suo DataMember su Or der s). Ecco il codice: 001. Imports MySql.Data.MySqlClient 002.
  • 360. 003. 'Prima di iniziare: 004. ' - MainDatabase è un'istanza di AppDataSet, ossia il 005. ' dataset tipizzato creato mediante l'editor che contiene le 006. ' due tabelle. 007. ' - bsOrders è il BindingSource che useremo come sorgente 008. ' dati per dgvOrders. Ricordatevi che: 009. ' bsOrders.DataSource = MainDatabase 010. ' bsOrders.DataMember = "Orders" o MainDatabase.Orders 011. ' - AllowUserToAddRows = False per entrambi i datagridview 012. 013. Public Class Form1 014. 015. Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load 016. Dim Connection As New MySqlConnection("Server=localhost; Database=appdata; Uid=root; Pwd=root;") 017. Dim Adapter As New MySqlDataAdapter() 018. 019. Try 020. Connection.Open() 021. Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Customers;", Connection) 022. Adapter.Fill(MainDatabase.Customers) 023. Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Orders;", Connection) 024. Adapter.Fill(MainDatabase.Orders) 025. Catch Ex As Exception 026. MessageBox.Show("Impossibile connettersi al database!", Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Error) 027. Finally 028. Connection.Close() 029. End Try 030. 031. 'Imposta la sorgente dati di dgvCustomers. Quando questa 032. 'proprietà viene impostata, crea automaticamente tutte 033. 'le colonne necessarie, ne imposta il tipo e 034. 'l'intestazione. Per questo motivo, tutte le colonne che 035. 'andremo ad usare iniziano ad esistere solo dopo questa 036. 'riga di codice. Tutte quelle che erano state definite 037. 'prima vengono eliminate. 038. dgvCustomers.DataSource = MainDatabase.Customers 039. 'Nasconde la prima e la settima colonna, ossia l'ID e 040. 'l'AccountType. La prima non deve essere visibile poiché 041. 'contiene dati che non riguardano l'utente, mentre 042. 'l'ultima la nascondiamo perchè avevamo detto di 043. 'voler visualizzare il tipo di account con un'icona 044. dgvCustomers.Columns(0).Visible = False 045. dgvCustomers.Columns(6).Visible = False 046. 047. 'Crea una nuova colonna di datagridview adatta a contenere 048. 'immagini. Esistono molti tipi di colonna (button, combobox, 049. 'linklabel, image, eccetera...), di cui la più comune 050. 'è una che contiene solo testo. Il tipo di una 051. 'colonna indica il tipo di dati che tutte le celle ad essa 052. 'sottostanti devono contenere. In questo caso vogliamo 053. 'che l'ultima colonna contenga una piccola immagine 054. 'indicante il livello dell'account (ci saranno tre livelli) 055. Dim Img As New DataGridViewImageColumn 056. 'Imposta l'immagine di default 057. Img.Image = imgAccountTypes.Images(0) 058. 'E l'intestazione della colonna 059. Img.HeaderText = "AccountLevel" 060. 'Quindi la aggiunge a quelle esistenti 061. dgvCustomers.Columns.Add(Img) 062. 063. 'Poi cicla attraverso tutte le righe, controllando il 064. 'contenuto della settima cella di ogni riga (ossia il 065. 'valore corrdispondente ad AccountType, un numero intero), 066. 'e imposta il contenuto dell'ottava prelevando la 067. 'rispettiva immagine dall'imagelist. 068. 'Ricordate che la colonna di indice 6, pur essendo 069. 'nascosta, esiste comunque 070. For Each Row As DataGridViewRow In dgvCustomers.Rows 071.
  • 361. Row.Cells(7).Value = imgAccountTypes.Images(CInt(Row.Cells(6).Value)) 072. Next 073. 074. 'Imposta come sorgente di dati di dgvOrders il binding 075. 'source bsOrders, specificato in precedenza 076. dgvOrders.DataSource = bsOrders 077. 'E nasconde le prime due colonne, ossia CustomerID e ID 078. dgvOrders.Columns(0).Visible = False 079. dgvOrders.Columns(1).Visible = False 080. 'Impone di visualizzare solo le tuple di ID pari a -1, 081. 'ossia nessuna 082. bsOrders.Filter = "ID=-1" 083. End Sub 084. 085. Private Sub dgvCustomers_KeyDown(ByVal sender As System.Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles dgvCustomers.KeyDown 086. 'Intercettiamo la pressione di un pulsante quando l'utente 087. 'si trova nell'ultima colonna (quella dell'icona) 088. If dgvCustomers.CurrentCell.ColumnIndex = 7 Then 089. 'Ottiene il valore di AccountType 090. Dim CurValue As Int32 = CInt(dgvCustomers.CurrentRow.Cells(6).Value) 091. 092. 'Se è stato premuto +, aumenta il valore di 1 093. 'Se è stato premuto -, lo decrementa di 1 094. If e.KeyCode = Keys.Oemplus Then 095. CurValue += 1 096. ElseIf e.KeyCode = Keys.OemMinus Then 097. CurValue -= 1 098. End If 099. 100. 'Fa in modo di non andare oltre i limit imposti 101. If CurValue < 0 Then 102. CurValue = 0 103. ElseIf CurValue > 2 Then 104. CurValue = 2 105. End If 106. 107. 'Quindi imposta il nuovo valore di AccountType 108. dgvCustomers.CurrentRow.Cells(6).Value = CurValue 109. 'E la corrispondente nuova immagine 110. dgvCustomers.CurrentCell.Value = imgAccountTypes.Images(CurValue) 111. End If 112. End Sub 113. 114. 'L'evento DataError viene generato ogniqualvolta l'utente 115. 'non rispetti i vincoli imposti dal database. Ad esempio, 116. 'viene generato se un campo marcato come NOT NULL viene 117. 'lasciato vuoto, o se una data non è valida, o 118. 'se un numero è troppo grande o troppo piccolo, 119. 'oppure ancora se non viene soddisfatto il vincolo di 120. 'unicità, eccetera... 121. 'In questi casi, se il programmatore non gestisce l'evento, 122. 'appare una finestra di default che riporta tutto il testo 123. 'dell'eccezione e vi assicuro che non è una bella cosa 124. Private Sub dgvCustomers_DataError(ByVal sender As System.Object, ByVal e As System.Windows.Forms.DataGridViewDataErrorEventArgs) Handles dgvCustomers.DataError 125. Dim Result As DialogResult 126. 127. 'Riporta l'errore all'utente, e lascia scegliere se 128. 'modificare i dati incompatibili oppure annullare 129. 'le modifiche e cancellare la riga 130. Result = MessageBox.Show("Si è verificato un errore di compatibilità dei dati immessi. Messaggio:" & _ 131. Environment.NewLine & e.Exception.Message & Environment.NewLine & _ 132. "E' possibile che dei dati mancanti compromettano il database. Premere Sì per modificare opportunamente " & _ 133. "tali valori, o No per cancellare la riga.", Me.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation) 134. 135. If Result = Windows.Forms.DialogResult.Yes Then 136. 'Annulla questo evento: non viene generata la 137. 'finestra di errore 138.
  • 362. e.Cancel = True 139. 'Pone il cursore sulla casella corrente e obbliga 140. 'ad iniziare l'edit mode. Il valore booleano tra 141. 'parentesi indica di selezionare l'intero contenuto 142. 'della cella corrente 143. dgvCustomers.BeginEdit(True) 144. Else 145. 'Annulla l'eccezione e l'evento, quindi cancella 146. 'la riga corrente 147. e.ThrowException = False 148. e.Cancel = True 149. 'Le righe "nuove", ossia quelle in cui non è 150. 'stato salvato ancora nessun dato, non possono essere 151. 'eliminate (così dice il datagridview...) 152. If Not dgvCustomers.CurrentRow.IsNewRow Then 153. dgvCustomers.Rows.Remove(dgvCustomers.CurrentRow) 154. End If 155. End If 156. End Sub 157. 158. 'Questa procedura aggiorna la ListView con alcuni dettagli 159. 'sullo stati dei pagamenti 160. Private Sub RefreshPreview(ByVal CustomerID As Int32) 161. lstPreview.Items.Clear() 162. 163. 'Cerca il cliente 164. Dim Customer As AppDataSet.CustomersRow = _ 165. MainDatabase.Customers.FindByID(CustomerID) 166. 167. 'Se non esiste, esce 168. If Customer Is Nothing Then 169. Exit Sub 170. End If 171. 172. Dim TotalPaid, TotalUnpaid As Single 173. Dim Delay As TimeSpan 174. 175. 'Notate che qui agiamo direttamente sul dataset, 176. 'perchè contiene campi tipizzati, e ci consente di 177. 'utilizzare meno operatori di cast 178. For Each Order As AppDataSet.OrdersRow In MainDatabase.Orders 179. 'Conta solo gli ordini associati a un cliente 180. If Order.CustomerID <> CustomerID Then 181. Continue For 182. End If 183. 184. 'Se l'ordine è stato pagato, aggiunge il 185. 'totale alla variabile TotalPaid, altrimenti a 186. 'TotalUnpaid. 187. 'Se l'ordine non è stato pagato, inoltre, 188. 'calcola il ritardo maggiore nel pagamento 189. If Order.Settled Then 190. TotalPaid += Order.ItemPrice * Order.ItemCount 191. Else 192. TotalUnpaid += Order.ItemPrice * Order.ItemCount 193. If Date.Now - Order.CreationDate > Delay Then 194. Delay = Date.Now - Order.CreationDate 195. End If 196. End If 197. Next 198. 199. Dim Item1 As New ListViewItem 200. Item1.Text = "Ammontare pagato" 201. Item1.SubItems.Add(String.Format("{0:N2}€", TotalPaid)) 202. 203. Dim Item2 As New ListViewItem 204. Item2.Text = "Oneri futuri" 205. Item2.SubItems.Add(String.Format("{0:N2}€", TotalUnpaid)) 206. 207. Dim Item3 As New ListViewItem 208. Item3.Text = "Ritardo pagamento" 209. Item3.SubItems.Add(CInt(Delay.TotalDays) & " giorni") 210.
  • 363. 211. Dim DaysLimit As Int32 212. 'Un diverso tipo di account permette un maggior ritardo 213. 'nei pagamenti... 214. Select Case Customer.AccountType 215. Case 0 216. DaysLimit = 60 217. Case 1 218. DaysLimit = 90 219. Case 2 220. DaysLimit = 120 221. Case Else 222. DaysLimit = 60 223. End Select 224. 225. 'Se il cliente ha superato il limite con almeno uno 226. 'dei suoi ordini, la riga viene colorata in rosso 227. If Delay.TotalDays > DaysLimit Then 228. Item3.ForeColor = Color.Red 229. End If 230. 231. lstPreview.Items.Add(Item1) 232. lstPreview.Items.Add(Item2) 233. lstPreview.Items.Add(Item3) 234. End Sub 235. 236. 'Evento generato quando l'utente si posizione su una riga 237. Private Sub dgvCustomers_RowEnter(ByVal sender As System.Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles dgvCustomers.RowEnter 238. 'Se si tratta di una riga valida... 239. If e.RowIndex < dgvCustomers.Rows.Count And e.RowIndex >= 0 Then 240. Dim CurID As Int32 = CInt(dgvCustomers.Rows(e.RowIndex).Cells(0).Value) 241. 'Aggiorna il filtro di bsOrders, per visualizzare solo gli 242. 'ordini di quel dato cliente 243. bsOrders.Filter = "CustomerID=" & CurID 244. 'E aggiorna la listview 245. RefreshPreview(CurID) 246. End If 247. End Sub 248. 249. 'Quando una cella di dgvOrders viene modificata, aggiorna 250. 'la listview... 251. Private Sub dgvOrders_CellEndEdit(ByVal sender As System.Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles dgvOrders.CellEndEdit 252. Try 253. RefreshPreview(CInt(dgvCustomers.CurrentRow.Cells(0).Value)) 254. Catch Ex As Exception 255. 256. End Try 257. End Sub 258. End Class Ed ecco come potr ebbe pr esentar si: Come avr ete cer tamente notato, fatta eccezione per l'unica pr ocedur a Refr eshPr eview , abbiamo agito solo sul datagr idview e non sul dataset. Questo accade per chè l'applicazione cr eata è "str atificata": può esser e consider ata come un par ticolar e caso di applicazione 3-tier . L'ar chitettur a thr ee-tier indica una par ticolar e str uttur azione del softw ar e in cui ci sono tr e layer (str ati) pr incipali: data layer , buisness layer e gui layer . Il pr imo si dedica alla gestione dei dati per sistenti (nel nostr o caso, il database), il secondo si occupa di for nir e delle logiche funzionali, ossia metodi che gestiscono le infor mazioni in manier a da r ispecchiar e il funzionamento dell'applicazione e che per mettono di inter facciar si più facilmente con il data layer (il nostr o buisness layer è il dataset, r appr esentazione oggettiva e funzionale dei dati per sistenti) mentr e il ter zo ha il compito di mediar e l'inter azione con l'utente attr aver so l'inter faccia gr afica. Sentir ete par lar e molto spesso di questo tipo di ar chitettur a nel campo dei gestionali.
  • 364. C8. DataGridView - Parte II Manipolazione dei dati Nell'esempio pr ecedente, l'utente poteva modificar e ed eventualmente cancellar e dati esistenti, ma, ancor a una volta, ho tr alasciato di implementar e l'aggiunta. In questo caso, per ò, aver lasciato la possibilità di agir e liber amente sui dati aggiunti avr ebbe causato non pochi danni, poiché gli ID sono tutti nascosti e il contr ollo datagr idview no n implementa nessun tipo di autoincr emento per i valor i numer ici: aggiungendo nuove r ighe, l'utente non avr ebbe potuto influir e sulle celle ID, che sar ebber o r imaste vuote e avr ebber o causato sempr e lo stesso er r or e (avendolo noi gestito nel modo che sapete, l'unica scelta possibile sar ebbe stata quella di cancellar e l'ultima r iga e per ciò non si sar ebbe potuto aggiunger e nulla in ogni caso). In poche par ole, bisogna inter venir e a livello di codice. Possiamo cor r egger e in modo elegante aggiungendo due Contex tMenu con un solo elemento "Aggiungi cliente" o "Aggiungi or dine" ed associar e ciascuno dei due a uno dei datagr idview . Per aggiunger e un nuovo cliente basta agir e dir ettamente sulla tabella customer , r ichiamando AddCustomer sRow e lasciando tutti i par ametr i vuoti (con la data di default), poiché nessuno di essi è specificato come NOT NULL nella str uttur a della tabella. Per l'or dine, invece, non è possibile seguir e la stessa str ada, poiché quasi tutti gli attr ibuti non possono esser e null. Per questo cr eer emo una nuova finestr a di dialogo di nome Cr eateOr der Dialog con quest'aspetto: e con questo semplice codice: 01. Public Class CreateOrderDialog 02. 03. Private CustomerID As Int32 04. Private _NewOrder As AppDataSet.OrdersRow 05. 06. 'Restituisce una nuova riga con gli attributi impostati 07. 'nel dialog 08. Public ReadOnly Property NewOrder() As AppDataSet.OrdersRow 09. Get 10. Return _NewOrder 11. End Get 12. End Property 13. 14. 'Per creare un nuovo ordine ci serve l'ID del cliente ad 15. 'esso associato, perciò dobbiamo costringere il chiamante 16. '(ossia noi stessi XD) a passarci questo dato in qualche 17. 'modo. In questo caso, sovrascriviamo il metodo ShowDialog 18. 'mediante shadowing: 19. Public Shadows Function ShowDialog(ByVal CustomerID As Int32) As DialogResult 20. Me.CustomerID = CustomerID 21. Return MyBase.ShowDialog() 22. End Function 23. 24. Private Sub OK_Button_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles OK_Button.Click 25. If String.IsNullOrEmpty(txtItemName.Text) Then 26. MessageBox.Show("Specificare una descrizione valida del prodotto!", Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Exclamation) 27. Exit Sub 28. End If 29. 30. If nudItemPrice.Value = 0.0F Then 31. MessageBox.Show("Prezzo non valido!", Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Exclamation) 32. Exit Sub 33. End If 34. 35.
  • 365. _NewOrder = My.Forms.Form1.MainDatabase.Orders.NewOrdersRow() 36. With _NewOrder 37. .CustomerID = Me.CustomerID 38. .ItemName = txtItemName.Text 39. .ItemPrice = nudItemPrice.Value 40. .ItemCount = nudItemCount.Value 41. .CreationDate = dtpCreationDate.Value 42. .Settled = chbSettled.Checked 43. End With 44. 45. Me.DialogResult = System.Windows.Forms.DialogResult.OK 46. Me.Close() 47. End Sub 48. 49. Private Sub Cancel_Button_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Cancel_Button.Click 50. Me.DialogResult = System.Windows.Forms.DialogResult.Cancel 51. Me.Close() 52. End Sub 53. 54. End Class Tenendo conto del nuovo dialog appena scr itto, il codice del for m diventer ebbe: 01. Imports MySql.Data.MySqlClient 02. Public Class Form1 03. 04. '... 05. 06. Private Sub strAddCustomer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles strAddCustomer.Click 07. MainDatabase.Customers.AddCustomersRow("", "", "", "", Date.Now, 0) 08. End Sub 09. 10. Private Sub strAddOrder_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles strAddOrder.Click 11. If dgvCustomers.CurrentRow Is Nothing Then 12. Exit Sub 13. End If 14. 15. Dim CID As Int32 = CInt(dgvCustomers.CurrentRow.Cells(0).Value) 16. Dim OrderDialog As New CreateOrderDialog() 17. 18. If OrderDialog.ShowDialog(CID) = Windows.Forms.DialogResult.OK Then 19. MainDatabase.Orders.AddOrdersRow(OrderDialog.NewOrder) 20. RefreshPreview(CID) 21. End If 22. End Sub 23. 24. End Class Manca ancor a un'ultima cosa per ter minar e il pr ogr amma, ossia il salvataggio. Possiamo, ad esempio, salvar e tutto alla chiusur a del for m: 01. Public Class Form1 02. '... 03. 04. Private Sub Form1_FormClosing(ByVal sender As System.Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles MyBase.FormClosing 05. Dim Connection As New MySqlConnection("Server=localhost; Database=appdata; Uid=root; Pwd=root;") 06. Dim Adapter As New MySqlDataAdapter() 07. Dim Builder As MySqlCommandBuilder 08. 09. Try 10. Connection.Open() 11. Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Customers;", Connection) 12. Builder = New MySqlCommandBuilder(Adapter) 13. Adapter.Update(MainDatabase.Customers) 14. 15. Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Orders;", Connection) 16.
  • 366. Builder = New MySqlCommandBuilder(Adapter) 17. Adapter.Update(MainDatabase.Orders) 18. 19. Catch Ex As Exception 20. If MessageBox.Show("Impossibile connettersi al database per il salvataggio. Proseguire nella chiusura? Tutti i dati non salvati andranno persi.", Me.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Error) = Windows.Forms.DialogResult.No Then 21. e.Cancel = True 22. End If 23. Finally 24. Connection.Close() 25. End Try 26. End Sub 27. 28. End Class P.S.: r icor datevi di r iassegnar e le immagini all'ultima cella dopo che le r ighe sono state r ior dinate (è possibile or dinar e automaticamente i dati cliccando sull'intestazione di una colonna). Stampa Il passo successivo di un gestionale consiste, di nor ma, nel voler stampar e i dati che si gestiscono. Esistono par ecchi componenti già pr onti per stampar e un datagr idview , nonché molti str umenti integr ati nell'IDE (r epor ts), acquistabili e non, e anche un discr eto numer o di "scor ciatoie", che non fanno altr o che disegnar e il contr ollo su un qualche suppor to e delegar ne la stampa ad un'altr a applicazione. Potete sceglier e liber amente di usar e uno dei metodi sopr acitati senza spr ecar vi più di tanto nel codice. In ogni caso, la soluzione che pr opongo potr ebbe esser e utile per r ipassar e l'uso di Gr aphics: 001. Public Class Form1 002. 003. '... 004. 005. 'Indici dei campi da stampare 006. Private PrintingFields() As Int32 = {1, 2, 3, 4, 5, 7} 007. 008. Private Sub PrintDoc_PrintPage(ByVal sender As System.Object, ByVal e As System.Drawing.Printing.PrintPageEventArgs) Handles PrintDoc.PrintPage 009. 'Indice della prima riga della prima pagina 010. Static RowIndex As Int32 = 0 011. 'Indica se si tratta della prima pagina 012. Static FirstPage As Boolean = True 013. 014. 'Offset orizzontale 015. Dim CellOffset As Int32 = e.MarginBounds.X 016. 'Offset verticale 017. Dim Y As Int32 = e.MarginBounds.Y 018. 'Larghezza totale colonne 019. Dim TotalWidth As Int32 = 0 020. 021. 'Se è la prima pagina, stampa le intestazioni 022. If FirstPage Then 023. For Each Column As DataGridViewColumn In dgvCustomers.Columns 024. 'Stampa solo gli header dei campi contemplati 025. If Array.IndexOf(PrintingFields, Column.Index) < 0 Then 026. Continue For 027. End If 028. 029. e.Graphics.DrawString(Column.HeaderText, dgvCustomers.ColumnHeadersDefaultCellStyle.Font, New SolidBrush(dgvCustomers.ColumnHeadersDefaultCellStyle.ForeColor), CellOffset, Y) 030. CellOffset += Column.Width 031. TotalWidth += Column.Width 032. Next 033. Y += dgvCustomers.ColumnHeadersHeight 034.
  • 367. End If 035. 036. 'Stampa le righe 037. For I As Int32 = RowIndex To dgvCustomers.RowCount - 1 038. Dim Row As DataGridViewRow = dgvCustomers.Rows(I) 039. 040. CellOffset = e.MarginBounds.X 041. 'Ottiene lo stile delle celle 042. Dim Style As DataGridViewCellStyle 043. 'Distingue fra quelle pari e dispari 044. If Row.Index Mod 2 = 0 Then 045. Style = dgvCustomers.DefaultCellStyle 046. Else 047. Style = dgvCustomers.AlternatingRowsDefaultCellStyle 048. End If 049. 050. 'Se nessun font particolare è specificato, prende 051. 'quello del controllo 052. If Style.Font Is Nothing Then 053. Style.Font = dgvCustomers.Font 054. End If 055. 'Se nessun colore particolare è specificato (di 056. 'default valore argb=(0,0,0,0)) prende quello del 057. 'controllo 058. If Style.ForeColor.A = 0 Then 059. Style.ForeColor = dgvCustomers.ForeColor 060. End If 061. 062. 'Disegna una striscia del colore di sfondo della riga 063. e.Graphics.FillRectangle(New SolidBrush(Style.BackColor), CellOffset, Y, TotalWidth, dgvCustomers.RowTemplate.Height) 064. 'E la contorna con un tratto nero 065. e.Graphics.DrawRectangle(Pens.Black, CellOffset, Y, TotalWidth, dgvCustomers.RowTemplate.Height) 066. 067. For Each Cell As DataGridViewCell In Row.Cells 068. 'Stampa solo gli attributi contemplati 069. If Array.IndexOf(PrintingFields, Cell.ColumnIndex) < 0 Then 070. Continue For 071. End If 072. 073. 'Se la cella contiene un'immagime, la stampa 074. If Cell.ValueType Is GetType(Image) Then 075. Dim Img As Image = Cell.Value 076. e.Graphics.DrawImage(Img, CellOffset + dgvCustomers.Columns(Cell.ColumnIndex).Width 2 - Cell.Value.Width 2, Y + dgvCustomers.CurrentRow.Height 2 - Img.Height 2) 077. Else 078. Dim Height As Int32 079. Dim StrVal As String 080. 081. 'Formatta la data in forma compatta 082. If Cell.ValueType Is GetType(Date) Then 083. StrVal = CType(Cell.Value, Date).ToShortDateString() 084. Else 085. StrVal = Cell.Value.ToString() 086. End If 087. 'Calcola l'altezza del testo per centrarlo 088. Height = Cell.MeasureTextHeight(e.Graphics, StrVal, Style.Font, 500, TextFormatFlags.Default) 089. 090. 'Stampa il testo 091. e.Graphics.DrawString(StrVal, Style.Font, New SolidBrush(Style.ForeColor), CellOffset, Y + dgvCustomers.RowTemplate.Height 2 - Height 2) 092. End If 093. 094. 'Si sposta alla prossima ascissa 095. CellOffset += dgvCustomers.Columns(Cell.ColumnIndex).Width 096. 097. 'Per tutte le celle tranne l'ultima, disegna una linea 098. 'verticale di separazione dalla cella successiva 099. If Array.IndexOf(PrintingFields, Cell.ColumnIndex) < PrintingFields.Length - 1
  • 368. Then 100. e.Graphics.DrawLine(Pens.Black, CellOffset - 4, Y, CellOffset - 4, Y + dgvCustomers.RowTemplate.Height) 101. End If 102. Next 103. 104. 'Aumenta l'ordinata 105. Y += dgvCustomers.RowTemplate.Height 106. Next 107. 108. If e.HasMorePages Then 109. FirstPage = False 110. Else 111. FirstPage = True 112. RowIndex = 0 113. End If 114. End Sub 115. 116. 'strPrint è un sottoelemento del context menu 117. Private Sub strPrint_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles strPrint.Click 118. Dim PDialog As New PrintDialog 119. 120. PDialog.Document = PrintDoc 121. If PDialog.ShowDialog = Windows.Forms.DialogResult.OK Then 122. PrintDoc.PrinterSettings = PDialog.PrinterSettings 123. 'Ridimensiona le colonne per far stare i testi in modo 124. 'corretto. N.B.: ho impostato la larghezza minima della 125. 'colonna Address a 190 pixel 126. dgvCustomers.AutoResizeColumns() 127. PrintDoc.Print() 128. End If 129. End Sub 130. End Class Risultato: V alidazione dell'input E' possibile convalidar e l'input dell'utente o indicar e che alcuni dati sono er r onei utilizzando l'evento CellValidating o Row Validating: 01. Private Sub dgvCustomers_CellValidating(ByVal sender As System.Object, ByVal e As System.Windows.Forms.DataGridViewCellValidatingEventArgs) Handles dgvCustomers.CellValidating 02. If e.ColumnIndex > 0 And e.ColumnIndex < 5 Then 03. With dgvCustomers.Item(e.ColumnIndex, e.RowIndex) 04. If String.IsNullOrEmpty(e.FormattedValue.ToString()) Then 05. .ErrorText = "Il campo non dovrebbe essere lasciato vuoto" 06. Else 07. .ErrorText = Nothing 08. End If 09. End With 10. End If 11. End Sub
  • 369. D1. Il controllo WebBrowser WebBr ow ser è uno dei contr olli standar d for niti dal Fr amew or k .NET fin dalla ver sione 1.0, e le sue potenzialità sono abbastanza elevate da per metter ci di "cr ear e" (o quanto meno, simular e) un nostr o per sonale w eb br ow ser , come Mozilla Fir eFox , Oper a o Google Chr ome. Non a caso ho messo tr a vir golette il ver bo creare, poiché il contr ollo che andr emo ad analizzar e tr a poco assolve un'unica funzione, che costituisce, per ò, il fulcr o di tutta la navigazione. WebBr ow ser per mette di car icar e al pr opr io inter no una pagina w eb e di visualizzar la senza pr aticamente scr iver e alcun codice. Nei pr ossimi par agr afi illustr er ò come scr iver e un semplicissimo pr ogr amma di navigazione basato su questa classe. La fisionomia di W ebBrow ser Dopo aver cr eato un nuovo pr ogetto Window s For ms, tr ascinate sulla super ficie del designer un nuovo contr ollo WebBr ow ser . Una volta posizionato dovr ebbe mostr ar si come un'ar ea totalmente bianca: per or a, infatti, non contiene ancor a nessuna pagina. Pr ima di pr oceder e, ecco uno sguar do alla lista dei suoi membr i più impor tanti: CanGoBack : deter mina se sia possibile tor nar e indietr o nella cr onologia CanGoFor w ar d : deter mina se sia possibile andar e avanti nella cr onologia Document : un oggetto di tipo HtmlDocument contenente tutte le infor mazioni sulla pagina. Tr a le sue pr opr ietà, inoltr e, ci sono molti modi per ottener e una vasta gamma di tag, ma illustr er ò in dettaglio questi meccanismi nel pr ossimo capitolo DocumentStr eam : per mette di legger e la pagina w eb come da un file. Restituisce un oggetto System.IO.Str eam DocumentTex t : r estituisce o imposta il codice della pagina. Dopo aver car icato una pagina, contiene il suo codice HTML. Modificando questa pr opr ietà, anche la pagina visualizzata ver r à r ielabor ata (e r icar icata) di conseguenza DocumentTitle : il titolo del documento DocumentType : il tipo del documento GoBack : tor na indietr o alla pagina pr ecedente GoFor w ar d : pr ocede alla pagina successiva GoHome : r itor na all'Home Page. Per ottener e l'indir izzo di quest'ultima, r icer ca nel r egistr o di sistema le pr efer enze che l'utente ha impostato per il br ow ser Inter net Ex plor er GoSear ch : si r eca alla pagina di r icer ca pr edefinita. Esegue lo stesso pr ocedimento di GoHome IsBusy : indica se il contr ollo sta car icando un nuovo documento IsOffline : indica se il contr ollo è in modalità offline (sta pr ocessando pagine w eb su disco fisso) IsWebBr ow ser Contex tMenuEnabled : deter mina se sia attivo il menù contestuale pr edefinito per il Web Br ow ser Naviagate(S) : apr e la pagina r efer enziata dall'indir izzo ur l S Pr int : stampa il documento aper to con i settaggi impostati della stampante cor r ente ReadyState : r estituisce lo stato del contr ollo. L'enumer ator e può assumer e quattr o valor i: Complete (pagina completa), Inter active (le par ti della pagina car icate sono sufficienti a gar antir e un minimo di inter azione con l'utente, ad esempio con dei click sui link pr esenti), Loaded (il documento è car icato e inizializzato, ma non tutti i dati sono ancor a stati r icevuti), Loading (il documento è in car icamento) e Uninitialized (nessun documento è stato aper to) Show PageSetupDialog : visualizza le impostazioni pagina con una finestr a di dialogo Inter net Ex plor er Show Pr intDialog : visualizza la finestr a di stampa di Inter net Ex plor er
  • 370. Show Pr intPr eview Dialog : visualizza l'antepr ima di stampa in una finestr a Inter net Ex plor er Show Pr oper tiesDialog : visualizza la finestr a delle pr opr ietà pagina come Inter net Ex plor er Show SaveAsDialog : visualizza la finestr a di dialogo di salvataggio di Inter net Ex plor er Ur l : r estituisce un oggetto Ur i r appr esentante l'indir izzo della pagina car icata Ver sion : la ver sione di Inter net Ex plor er installata Alcune delle funzionalità esposte da questi membr i si r eggono pesantemente su Inter net Ex plor er , come ad esempio la visualizzazione dell'antepr ima o la r icer ca della home page (che potete cambiar e solo dal menù opzioni di IE). Nonostante tali pesanti impedimenti, è possibile usar e il contr ollo con semplicità. Nel nostr o pr ogetto possiamo quindi aggiunger e qualche altr o contr ollo: btnBack per andar e indietr o; btnFor w ar d per andar e avanti; btnRefr esh per aggior nar e la pagina; tx tUr l per contener e l'indir izzo a cui r ecar si; Come vedete ho inser ito tutti i contr olli sopr a menzionati in un ToolStr ip, e tutti i pulsanti sono di default disattivati (Enabled = False), poiché all'inizio non è car icata nessuna pagina e di conseguenza non si può effettuar e alcuna oper azione. Con questo semplice codice potr emo iniziar e a navigar e un po': 01. Public Class Form1 02. 03. Private Sub txtUrl_KeyDown(ByVal sender As System.Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles txtUrl.KeyDown 04. 'Quando si preme invio durante la digitazione, naviga 05. 'alla pagina indicata 06. If e.KeyCode = Keys.Enter Then 07. wbBrowser.Navigate(txtUrl.Text) 08. 'Poiché si inizia a navigare, è lecito fermare 09. 'il caricamento, quindi attiva btnCancel 10. btnCancel.Enabled = True 11.
  • 371. End If 12. End Sub 13. 14. Private Sub btnCancel_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCancel.Click 15. 'Ferma l'attività del WebBrowser 16. wbBrowser.Stop() 17. btnCancel.Enabled = False 18. btnRefresh.Enabled = True 19. End Sub 20. 21. Private Sub wbBrowser_Navigating(ByVal sender As System.Object, ByVal e As System.Windows.Forms.WebBrowserNavigatingEventArgs) Handles wbBrowser.Navigating 22. 'L'evento Navigating si genera prima della navigazione 23. btnCancel.Enabled = True 24. End Sub 25. 26. Private Sub wbBrowser_DocumentCompleted(ByVal sender As System.Object, ByVal e As System.Windows.Forms.WebBrowserDocumentCompletedEventArgs) Handles wbBrowser.DocumentCompleted 27. 'L'evento DocumentCompleted si verifica quando una pagina 28. 'è stata completamente caricata. Se anche una sola 29. 'delle parti della pagina non è completa, l'evento 30. 'non viene generato. Per evitare brutte soprese, potete 31. 'utilizzare l'evento Navigated, che si verifica dopo la 32. 'navigazione (indipendentemente dal successo o meno 33. 'dell'operazione) 34. btnCancel.Enabled = False 35. btnBack.Enabled = wbBrowser.CanGoBack 36. btnForward.Enabled = wbBrowser.CanGoForward 37. btnRefresh.Enabled = True 38. End Sub 39. 40. Private Sub btnRefresh_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnRefresh.Click 41. wbBrowser.Refresh() 42. End Sub 43. 44. Private Sub btnBack_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnBack.Click 45. wbBrowser.GoBack() 46. End Sub 47. 48. Private Sub btnForward_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnForward.Click 49. wbBrowser.GoForward() 50. End Sub 51. 52. End Class Come alter nativa a DocumentCompleted, si può utilizzar e Navigated: 1. Private Sub wbBrowser_Navigated(ByVal sender As System.Object, ByVal e As System.Windows.Forms.WebBrowserNavigatedEventArgs) Handles wbBrowser.Navigated 2. btnCancel.Enabled = False 3. btnBack.Enabled = wbBrowser.CanGoBack 4. btnForward.Enabled = wbBrowser.CanGoForward 5. btnRefresh.Enabled = True 6. End Sub Possiamo or a aggiunger e una bar r a di stato in basso per comunicar e lo stato della navigazione: 01. Public Class Form1 02. 03. Private Sub txtUrl_KeyDown(ByVal sender As System.Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles txtUrl.KeyDown 04. If e.KeyCode = Keys.Enter Then 05. wbBrowser.Navigate(txtUrl.Text) 06. btnCancel.Enabled = True 07. End If 08. End Sub 09.
  • 372. 10. Private Sub btnCancel_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCancel.Click 11. wbBrowser.Stop() 12. btnCancel.Enabled = False 13. btnRefresh.Enabled = True 14. End Sub 15. 16. Private Sub wbBrowser_Navigating(ByVal sender As System.Object, ByVal e As System.Windows.Forms.WebBrowserNavigatingEventArgs) Handles wbBrowser.Navigating 17. btnCancel.Enabled = True 18. 'La proprietà StatusText contiene in forma leggibile 19. 'un resoconto dell'operazione che il controllo sta svolgendo 20. lblStatus.Text = wbBrowser.StatusText 21. End Sub 22. 23. Private Sub wbBrowser_Navigated(ByVal sender As System.Object, ByVal e As System.Windows.Forms.WebBrowserNavigatedEventArgs) Handles wbBrowser.Navigated 24. btnCancel.Enabled = False 25. btnBack.Enabled = wbBrowser.CanGoBack 26. btnForward.Enabled = wbBrowser.CanGoForward 27. btnRefresh.Enabled = True 28. lblStatus.Text = "Pagina caricata" 29. End Sub 30. 31. Private Sub btnRefresh_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnRefresh.Click 32. wbBrowser.Refresh() 33. End Sub 34. 35. Private Sub btnBack_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnBack.Click 36. wbBrowser.GoBack() 37. End Sub 38. 39. Private Sub btnForward_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnForward.Click 40. wbBrowser.GoForward() 41. End Sub 42. 43. Private Sub wbBrowser_ProgressChanged(ByVal sender As System.Object, ByVal e As System.Windows.Forms.WebBrowserProgressChangedEventArgs) Handles wbBrowser.ProgressChanged 44. prgProgress.Value = e.CurrentProgress / e.MaximumProgress * 100 45. lblStatus.Text = wbBrowser.StatusText 46. End Sub 47. 48. End Class
  • 373. Dato che questo non vuole esser e un tutor ial su come cr ear e un br ow ser , ma solo un abstr act per mostr ar e le funzionalità del contr ollo, non mi dilungher ò oltr e nella modifica e nella r affinazione dell'applicazione pr oposta in esempio, anche per chè sono sicur o che qualche lettor e lo star à già facendo e non vor r ei toglier gli il diver timento XD
  • 374. D2. Parsing di codice HTML È possibile scar icar e pagine w eb in molti modi diver si, di cui WebBr ow ser è solo il pr imo che ho intr odotto. Nel capitolo pr ecedente non ci siamo posti alcun pr oblema sull'analsi del codice di una pagina, poiché l'impor tante er a r iuscir e a visualizzar la ed a navigar e da essa ad alr e pagine pr esenti in r ete. Tuttavia, pr esto o tar di incor r er ete nel bisogno di ottener e infor mazioni sui tag html pr esenti in un data pagina, ad esempio per effettuar e un login automatico senza esser e per sonalmente al computer , o per aggiunger e alcune funzionalità al br ow ser che state scr ivendo di nascosto. Per nostr a for tuna esistono un paio di classi, HtmlDocument e HtmlElement, che eseguono autonomamente il par sing del sor gente html e ci per mettono di agir e su di esso mediante oggetti di alto livello. Uno sguardo alle c lassi HtmlDocument è la classe di par tenza, che ci per mette di iniziar e ad ispezionar e il codice. Essa non espone costr uttor i, né metodi statici, e quindi non esiste alcun modo di inizializzar la o di applicar la ad un file html. L'unico modo in cui possiamo ottener ne un'istanza è attr aver so la pr opr ietà Document del contr ollo WebBr ow ser . HtmlDocument espone alcuni membr i inter essanti: ActiveElement : r estituisce un oggetto HtmlElement che r appr esenta l'elemento che possiede il focus al momento. Può indicar e, ad esempio, un tag tex tar ea se l'utente sta digitando del testo, od un div se è stato selezionata una par te di par agr afo; All : r estituisce una collezione di tutti i tag pr esenti nel documento, sempr e sottofor ma di HtmlElement; Body : r estituisce l'elemento body della pagina; Cr eateElement(tagName) : cr ea un nuovo HtmlElement con tagName dato. Questo è l'unico modo in cui possiamo cr ear e nuovi oggetti da aggiunger e alla pagina (tr anne ovviamente r icopiar e il codice, modificar lo, e poi impostar e di nuovo la pr opr ietà DocumentTex t); For ms : r estituisce una collezione di tutti i tag form pr esenti nel documento; GetElementById(id As Str ing) : r estituisce un r ifer imento all'elemento con specifico id; GetElementFr omPoint(p As Point) : r estituisce un r ifer imento all'elemento che contiene il punto p; le coor dinate del punto sono r elative all'estr emo super ior e sinistr o della pagina; GetElementsByTagName(tagName As Str ing) : r estituisce una collezione di tutti i tag con dato tagName. GetElementsByTagName("div"), ad esempio, r estituisce l'insieme di tutti i div della pagina; Images : r estituisce una collezione di tutti i tag image; InvokeScr ipt(scr iptName As Str ing, ar gs() As Object) : esegue il metodo di nome scr iptName passandogli gli ar gomenti specificati in ar gs. Il metodo deve esser e definito all'inter no di un tag s cript nella pagina (non è impor tante il linguaggio, ma per or a ho ver ificato che funzioni solo con javascr ipt e actionscr ipt); Links : r estituisce una collezione di tutti i tag a; Title : indica il titolo della pagina; Ur l : l'indir izzo della pagina car icata; Window : r estituisce un oggetto HtmlWindow associato alla finestr a che visualizza la pagina. Questo oggetto espone alcuni membr i molto inter essanti, tr a cui: Aler t(S) : visualizza il messaggio S in una finestr a di dialogo; Confir m(S) : visualizza il messaggio S in una finestr a di dialogo e per mette di sceglier e tr a OK e Annulla; r estituisce Tr ue se è stato pr emuto OK, altr imenti False;
  • 375. Pr ompt(S, D) : visualizza il messaggio S in una finestr a di dialogo e chiede di inser ir e un valor e in una casella di testo (il valor e pr edefinito è D). Restituisce il valor e che l'utente ha immesso. Gli oggetti HtmlElement contengono più o meno gli stessi membr i, con l'aggiunta di GetAttr ibute e SetAttr ibute per modificar e gli attr ibuti di un tag. Nei pr ossimi par agr afi far ò alcuni esempi di come utilizzar e tali classi. Login automatic o Ecco un modo con cui potr este automatizzar e il login in una pagina salvando le infor mazioni e compilando i campi con un solo pulsante. Ipotizziamo di aver e un dizionar io LoginInfo in cui sono contenute delle coppie indir izzo-dizionar io. I valor i sono a lor o volta altr i dizionar i che contengono le infor mazioni per il login. Potr emmo utilizzar e il codice seguente per automatizzar e il tutto: 01. Class Form1 02. 03. 'Qui c'è il codice del capitolo precedente 04. 05. 'Ecco il dizionario che contiene tutto 06. Dim LoginInfo As New Dictionary(Of String, Dictionary(Of String, String)) 07. 08. Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load 09. 'Per semplicità, in questo esempio carichiamo dei 10. 'dati di prova al caricamento del form 11. 12. Dim TInfo As New Dictionary(Of String, String) 13. With TInfo 14. 'ID del form di login 15. .Add("form-id", "totemlogin") 16. 'ID della textbox per l'username 17. .Add("username-field", "lname") 18. 'ID della textbox per la password 19. .Add("password-field", "lpassw") 20. 'Username e password 21. .Add("username", "prova") 22. .Add("password", "prova") 23. End With 24. 25. 'Associa alla pagina il suo login 26. LoginInfo.Add("http://totem.altervista.org/guida/versione3/login.php", TInfo) 27. End Sub 28. 29. 30. Private Sub btnAction_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAction.Click 31. 'Quando viene premuto il pulsante, ricava il dizionario 32. 'dei dati dall'url della pagina 33. Dim Info As Dictionary(Of String, String) = LoginInfo(wbBrowser.Url.ToString()) 34. 'Quindi compila i campi e invia la richiesta di login 35. With wbBrowser.Document 36. .GetElementById(Info("username-field")).SetAttribute("value", Info("username")) 37. .GetElementById(Info("password-field")).SetAttribute("value", Info("password")) 38. 'InvokeMember invoca un metodo usabile da un 39. 'certo elemento. I metodi sono gli stessi che si 40. 'usano in javascript 41. .GetElementById(Info("form-id")).InvokeMember("submit") 42. End With 43. End Sub 44. 45. End Class Nonostante possa sempr ar e inutile, questo appr occio potr ebbe diventar e molto più intr igante, ad esempio, se l'utente immettesse semplicemente una tesser a o una chiavetta in un dispositivo collegato al computer e, usando il vostr o
  • 376. br ow ser , potesse inter facciar si con tale dispositivo per automatizzar e e per sonalizzar e tutti i login a seconda dell'utente. Oppur e potr este sfr uttar e il r iconoscimento vocale offer to dalle libr er ie del fr amew or k 3.5 per confer mar e l'accesso mediante una par ola detta a voce. Trasformazioni Nel par agr afo pr ecedente ho mostr ato come modificar e degli elementi. In questo mostr er ò come aggiunger e nuovi elementi alla pagina dinamicamente e come gestir ne gli eventi. Sempr e tenendo come guida il codice pr oposto nel par agr afo pr ecedente, cambiamo la funzione del pulsante btnAction con la seguente: dopo il click sul pulsante, cliccando su qualsiasi immagine nella pagina, questa viene tr asfor mata in un link all'immagine. Ecco il codice: 01. Class Form1 02. 03. '... 04. 05. Private Sub btnAction_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAction.Click 06. With wbBrowser.Document 07. 'Scorre tutte le immagini nella pagina e ad ognuna 08. 'aggiunge un nuovo gestore d'evento per l'evento OnClick. 09. 'Il metodo AttachEventHandler può essere usato 10. 'da qualsiasi HtmlElement, ed accetta come primo 11. 'parametro il nome dell'evento da gestire (vedere la 12. 'documentazione ufficiale W3C) e come secondo un 13. 'delegate che punta al sottoscrittore. 14. For Each img As HtmlElement In .Images 15. .AttachEventHandler("onclick", AddressOf ImageToLink) 16. Next 17. End With 18. End Sub 19. 20. 'Questo è il nuovo gestore d'evento. Nonostante i 21. 'parametri, sender è sempre Nothing 22. Private Sub ImageToLink(ByVal sender As Object, ByVal e As EventArgs) 23. 'Ottiene un riferimento all'immagine con il metodo 24. 'GetElementFromPoint, sfruttando il fatto che questo 25. 'codice viene eseguito subito dopo un click. 26. 'MousePosition indica la posizione del mouse sullo schermo, 27. 'Me.Location determina la posizione del form sullo schermo 28. 'e wbBrowser.Location la posizione del browser sul form. 29. 'La differenza tra questi punti è la posizione 30. 'del mouse rispetto al browser. Anche se un po' grezzo, 31. 'questo metodo dovrebbe funzionare abbastanza 32. Dim Img As HtmlElement = wbBrowser.Document.GetElementFromPoint(MousePosition - Me.Location - wbBrowser.Location) 33. 'Crea un nuovo link mediante il metodo CreateElement 34. 'di HtmlDocument 35. Dim Link As HtmlElement = wbBrowser.Document.CreateElement("a") 36. 37. 'Imposta l'attributo href dell'immagine 38. Link.SetAttribute("href", Img.GetAttribute("src")) 39. 'Imposta il testo del link 40. If Not String.IsNullOrEmpty(Img.GetAttribute("longdesc")) Then 41. Link.InnerText = Img.GetAttribute("longdesc") 42. ElseIf Not String.IsNullOrEmpty(Img.GetAttribute("alt")) Then 43. Link.InnerText = Img.GetAttribute("alt") 44. Else 45. Link.InnerText = "Immagine" 46. End If 47. 48. 'Aggiunge il link prima dell'immagine 49. Img.InsertAdjacentElement(HtmlElementInsertionOrientation.BeforeBegin, Link) 50. 'Dato che non è possibile eliminare elementi, 51. 'impone all'immagine larghezza 0 52. Img.SetAttribute("width", "0") 53.
  • 378. D3. Scaricare file dalla rete Oltr e al WebBr ow ser , ci sono altr i tr e modi di scar icar e file da inter net. In questo par agr afo li analizzer ò uno per uno. Dow nload sinc rono gestito Il pr imo e più semplice dei suddetti modi consiste nell'utilizzar e una classe messa a disposizione dal Fr amew or k, ossia WebClient (del namespace System.Net). Una volta istanziato un oggetto di questo tipo, è possibile r ichiamar e da esso molti metodi diver si per scar icar e pr aticamente qualsiasi cosa. Qui espongo i metodi sincr oni: Dow nloadData(ur i) : scar ica il file con dato ur i (Unifor m Resour ce Identifier , una for ma più gener ale dell'ur l) e r estituisce tutti i dati scar icati sottofor ma di un ar r ay di bytes. Par ticolar mente indicato per scar icar e piccoli file binar i ad uso tempor aneo; Dow nloadFile(ur l, path) : scar ica il file indicato dall'indir izzo ur l e lo salva nel per cor so path su disco fisso; Dow nloadStr ing(ur i) : molto simile a Dow nloadData, ma anziché r estituir e un ar r ay di bytes, r estituisce una str inga. Ci sono, poi, altr i membr i che è inter essante conoscer e: Cr edentials : indica le cr edenziali usate per acceder e alla data r isor sa. È utile impostar e questa pr opr ietà quando si accede a ser ver che r ichiedono un'autenticazione tr amite nome utente e passw or d, come ad esempio si usa far e quando si utilizza il pr otocollo ftp per il tr asfer imento di file. Ad esempio: 1. Dim W As New Net.WebClient 2. W.Credentials = New Net.NetworkCredential("username", "password") Header s : espone una collezione degli header posti all'inizio della r ichiesta per il file. Quando un metodo di dow nload viene invocato, la classe WebClient si pr eoccupa di inviar e una r ichiesta oppor tuna al ser ver . Ad essa può aggiunger e alcune metainfor mazioni note come header s, definite dallo standar d del pr otocollo HTTP (di cui potete tr ovar e una descr izione appr ofondita qui). Nei pr ossimi esempi user ò un solo tipo di header , Range, che per mette di ottener e solo una data par te del file; Pr ox y : imposta il pr o xy che la classe attr aver sa per inoltr ar e la r ichiesta; Quer yStr ing : indica un insieme di chiavi e valor i che costituiscono la quer y applicata alla pagina r ichiesta. Una quer y str ing può esser e accodata alla fine dell'ur l intr oducendola con un "?", definendo una coppia come nome=valor e e separ ando tutte le copie da un car atter e "&". Ser ve per ottener e r isultati diver si da una stessa pagina, specificando cosa si sta cer cando. Alcuni semplici esempi: 01. Dim W As New Net.WebClient 02. 'Scarica l'home page del sito e la salva in C: 03. W.DownloadFile("http://totem.altervista.org/index.php", "C:index.php") 04. 05. Dim S As String 06. 'Scarica il contenuto del file Capitoli.txt e lo salva 07. 'nella stringa S 08. S = W.DownloadString("http://totem.altervista.org/guida/versione3/Capitoli.txt") 09. 10. 'Aggiunge una coppia nome-valore alla query 11. W.QueryString.Add("name", "twaveeditor") 12. 'La prossima richiesta sarà quindi equivalente a: 13. ' http://totem.altervista.org/download/details.php?name=twaveeditor 14.
  • 379. 'Ossia scaricherà la pagina di download di TWave Editor. 15. 'Il contenuto del file verrà salvato in B 16. Dim B() As Byte = W.DownloadData("http://totem.altervista.org/download/details.php") La pecca di questi metodi è che sono sincr oni, ossia bloccano il funzionamento dell'applicazione fino a quando il dow nload non è ter minato. Questo compor tamento può r ivelar si utile in cer ti casi e r ender e più maneggevole il codice per scar icar e file di piccole dimensioni, ma è tutt'altr o che accettabile per gr andi quantità di dati. Dow nload asinc rono gestito Per file molto gr andi, invece, ci vengono in aiuto le ver sioni asincr one dei metodi sopr a esposti: sono r iconoscibili dal suffisso "Async" dopo il nome del metodo. Questi eseguono il dow nload in un thr ead separ ato, per ciò non inter fer iscono con le nor mali oper azioni del pr ogr amma. In compenso, sono un po' più difficili da gestir e, ma nulla di par ticolar mente complicato. I metodi asincr oni si r ichiamano usando esattamente gli stessi par ametr i delle ver sioni sincr one, ma per saper e come stanno andando le cose, dobbiamo far e uso di due eventi della classe WebClient: Dow nloadPr ogr essChanged, che notifica il pr ogr esso del dow nload, e Dow nloadFileCompleted (o Dow nloadDataCompleted o Dow nloadStr ingCompleted, a seconda dei casi). Ecco un semplice esempio: 01. Class Form1 02. 'WithEvents permette di gestire gli eventi di W 03. Private WithEvents W As New Net.WebClient() 04. 05. Private Sub btnDownload_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnDownload.Click 06. 'Inizia il download asincrono 07. W.DownloadFileAsync(New Uri(txtUrl.Text), txtFile.Text) 08. btnCancel.Enabled = True 09. btnDownload.Enabled = False 10. End Sub 11. 12. Private Sub W_DownloadProgressChanged(ByVal sender As Object, ByVal e As Net.DownloadProgressChangedEventArgs) Handles W.DownloadProgressChanged 13. 'Il parametro e contiene alcune informazioni 14. 'sul progresso del download 15. lblStatus.Text = _ 16. String.Format("Bytes ricevuti: {0} B{3}Dimensione file: {1} B{3}Progresso: {2:N0}%", _ 17. e.BytesReceived, e.TotalBytesToReceive, _ 18. e.ProgressPercentage, Environment.NewLine) 19. End Sub 20. 21. Private Sub W_DownloadFileCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.AsyncCompletedEventArgs) Handles W.DownloadFileCompleted 22. 'e.Cancelled vale True se il download è stato annullato. 23. 'e.Error è di tipo Exception e contiene l'eccezione 24. ' generata nel caso si sia verificato un errore. 25. If e.Cancelled Then 26. MessageBox.Show("Il download è stato cancellato!", Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Exclamation) 27. ElseIf e.Error IsNot Nothing Then 28. MessageBox.Show("Si è verificato un errore: " & e.Error.Message, Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Exclamation) 29. Else 30. MessageBox.Show("Download completato con successo!", Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Information) 31. End If 32. btnDownload.Enabled = True 33. btnCancel.Enabled = False 34. End Sub 35. 36. Private Sub btnCancel_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCancel.Click 37. 'Il metodo CancelAsync cancella il download asincrono 38. W.CancelAsync() 39.
  • 380. btnDownload.Enabled = True 40. btnCancel.Enabled = False 41. End Sub 42. 43. End Class Dow nload sinc rono/asinc rono non gestito Come ho illustr ato nei par agr afi pr ecedenti, WebClient si occupa di eseguir e molte istr uzioni r iguar do al dow nload: connetter si al ser ver indicato, cr ear e una r ichiesta valida secondo il pr otocollo usato (HTTP o FTP o altr i), inoltr ar e la r ichiesta, aspettar e una r isposta, legger e dallo str eam di r ete i dati for niti dal ser ver e copiar li nel file indicato, quindi chiuder e la connessione. Insomma, non lascia nulla al contr ollo del pr ogr ammator e. Con il pr ossimo metodo che andr ò ad intr odur r e, potr emmo manipolar e alcuni di questi passaggi a nostr o piacimento. Le classi che ci inter essano or a sono WebRequest e WebResponse, del namespace System.Net. Esse sono classi astr atte, poiché ogni pr otocollo implementa le pr opr ie r ichieste e r isposte secondo deter minati standar d. Nel nostr o esempio, user emo HttpWebRequest per cr ear e ed inviar e una r ichiesta http ad un ser ver e HttpWebResponse per inter pr etar ne la r isposta. Sappiate, per ò, che esistono anche le r ispettive ver sioni per il pr otocollo FTP, ossia FtpWebRequest e FtpWebResponse. Ecco una pr ima semplice ver sione del codice: 01. Public Sub DownloadFile(ByVal Address As String, ByVal Path As String) 02. 'Crea una richiesta http per l'indirizzo Address. 03. 'Address può anche contenere una query string 04. Dim Request As Net.HttpWebRequest = Net.HttpWebRequest.Create(Address) 05. 'Invia la richieste a ottiene la risposta 06. Dim Response As Net.HttpWebResponse = Request.GetResponse() 07. 'Ottiene da Response uno stream di rete dal quale si 08. 'potrà leggere il file richiesto. 09. Dim Reader As IO.Stream = Response.GetResponseStream() 10. 'Crea un nuovo file in locale 11. Dim Writer As New IO.FileStream(Path, IO.FileMode.Create) 12. 'Un buffer di byte che contiene i blocchi letti 13. 'dallo stream. La lettura a blocchi è più 14. 'conveniente che trasferire in massa tutto il contenuto, 15. 'poiché altrimenti si dovrebbe usare un buffer 16. 'gigantesco (almeno le dimensioni del file) 17. Dim Buffer(8127) As Byte 18. Dim BytesRead As Int32 19. 20. 'La funzione Read, vi ricordo, restituisce come risultato 21. 'il numero di bytes effettivamente letti dallo stream 22. BytesRead = Reader.Read(Buffer, 0, Buffer.Length) 23. Do While BytesRead > 0 24. Writer.Write(Buffer, 0, BytesRead) 25. BytesRead = Reader.Read(Buffer, 0, Buffer.Length) 26. Loop 27. 28. Reader.Close() 29. Writer.Close() 30. Response = Nothing 31. Request = Nothing 32. End Sub Pr ima di pr oceder e, vor r ei far e alcuni chiar imenti sullo str eam di r ete. Esso r appr esenta un flusso di dati che pr oviene dal ser ver a cui si è inviata la r ichiesta, ed è un flusso a senso unico, per ciò non suppor ta oper azioni di r icer ca (invocando il metodo Seek o modificando la pr opr ietà Position otter r ete degli er r or i). Non è neppur e possibile saper ne la dimensione complessiva, poiché anche la pr opr ietà Length gener a eccezioni. E, infine, non è possibile scr iver vi sopr a. Esiste un modo per saper e le dimensioni dei dati, ossia r ichiamar e la pr opr ietà Reponse.ContentLength, che, tuttavia, potr ebbe contener e valor i pr ivi di senso (ad esempio -1). Questo succede per chè essa si limita ad espor r e il valor e di un header posto nella r isposta http: tuttavia, il ser ver non è obbligato ad inser ir e questo header , e se non lo fa, non c'è modo di legger lo.
  • 381. Osser viamo or a che tutte le oper azioni svolte sono sincr one, ma, come il titolo sugger isce, è possibile r ender e tutto il metodo asincr ono, facendo uso dei thr ead. Infatti, è sufficiente eseguir e la pr ocedur a in un thr ead differ ente: per ulter ior i infor mazioni sul multithr eading, veder e capitolo r elativo. In ultimo, è possibile ottener e solo una par te del file aggiungendo l'header Range alla r ichiesta, come anticipato nei par agr afi pr ecedenti. Dato che la pr opr ietà Header s di WebClient vieta l'uso di questo header (non è ben chiar a la r agione), l'unico modo per usar lo consiste nell'impiegar e quest'ultimo metodo di dow nload. Basta r ichiamar e AddRange pr ima dell'invio della r ichiesta: 01. '... 02. 'Indica al server che vogliamo iniziare la lettura 03. 'dall'offset n 04. Request.AddRange(n) 05. 06. 'oppure 07. 08. 'Indica al server che vogliamo iniziare la lettura dalla 09. 'posizione n, ma solo fino alla posizione q 10. Request.AddRange(n, q) Non ser ve eseguir e altr e oper azioni par ticolar i per la lettur a. Lo str eam ottenuto consentir à di legger e esattamente ciò che si è r ichiesto come se fosse un unico flusso di dati.
  • 382. D4. I Socket - Parte I I socket sono uno str umento che per mette di inviar e e r icever e dati tr a due applicazioni che cor r ono su macchine collegate da una r ete, la quale, nel caso più fr equente, coincide con Inter net. Le classi che espongono i metodi necessar i sono contenute nel namespace System.Net.Sockets, di cui la classe Socket costituisce il membr o più eminente. In questo capitolo e nel successivo, tuttavia, non user emo dir ettamente tale classe, poiché è poco pr atica da gestir e e fa massiccio uso di tecniche di pr ogr ammazione un po' complesse, quali il multithr eading, che non ho ancor a spiegato. Intr odur r ò, invece, al suo posto, due classi più semplici che fanno da w r apper ad alcune funzioni basilar i del socket: TcpListener e TcpClient. Server Il ser ver , nel nostr o caso, è il computer sul quale r isiede l'applicazione pr incipale deputata alla gestione delle connessioni e dei ser vizi dei client ester ni. Più in gener ale è una componente infor matica che for nisce ser vizi ad altr e componenti attr aver so una r ete. Per implementar e un'applicazione Ser ver da codice dobbiamo far sì che essa possa accettar e connessioni da par te di altr i. Per far questo è necessar io usar e la classe TcpListener , che si mette in ascolto su di una por ta, e r ifer isce quando ci sono r ichieste di connessioni in attesa su di essa. I suoi membr i di spicco sono: AcceptTcpClient() : accetta una connessione in attesa e r estituisce un oggetto TcpClient collegato al client che ha inviato la r ichiesta. Usando tale oggetto sar à possibile inviar e o r icever e dati, poiché il Listener , di per sé, non fa altr o che attender e e accettar e connessioni, ma l'azione ver a viene intr apr esa da oggetti TcpClient; Pending() : r estituisce Tr ue se si cono connessioni in attesa; Ser ver : r estituisce l'oggetto socket che TcpListener sfr utta. Come avevo detto nel par agr afo intr oduttivo, queste due classi fanno uso inter namente di Socket, ma espongono metodi di più semplice gestione; Star t() : inizia l'oper azione di listening su una por ta data; Stop() : inter r ompe il listening; Il costr uttor e di TcpListener che user emo r ichiede come unico par ametr o la por ta su cui metter si in ascolto. Client La classe fondamentalmente usata per un client è TcpClient. I suoi membr i più significativi sono: Available: r estituisce il numer o di bytes r icevuti e pr onti per la lettur a Close(): chiude la connessione Connect(IP, P): tenta una connessione ver so il ser ver identificato da IP sulla por ta P. IP può esser e sia un indir izzo IP che DNS Connected: r estituisce Tr ue se è connesso, altr imento False GetStr eam(): funzione impor tantissima che r estituisce un oggetto di tipo Sockets.Netw or kStr eam su cui e da cui si scr ivono e leggono tutti i dati scambiati tr a client e ser ver ReceiveBuffer Size: imposta la gr andezza del buffer di bytes r icevuti SendBuffer Size: imposta la gr andezza del buffer di bytes inviati Il client tenta la connessione al ser ver e, se accettato, può dialogar e con esso scambiando messaggi. I dati vengono inviati e r icevuti attr aver so uno str eam di r ete bidir ezionale, che è possibile ottener e r ichiamando GetStr eam().
  • 383. Quando il client scr ive su questo str eam, il ser ver r iceve i dati e li può legger e, e vicever sa. Un semplic e sc ambio di messaggi Per iniziar e scr iver emo un semplice pr ogr amma per scambiar e messaggi (chiamar lo "chat" sar ebbe a dir poco inoppor tuno). Per semplicità d'uso, la stessa applicazione potr à far e sia da ser ver che da client, così un utente potr à sia collegar si ad un altr o che attender e connessioni (ma non far e le due cose contempor aneamente). L'inter faccia che ho pr epar ato è questa: Ci sono anche due timer , tmr Connections e tmr Data. Ecco il codice: 001. Imports System.Net.Sockets 002. Imports System.Text.UTF8Encoding 003. 004. Public Class Form1 005. 006. Private Listener As TcpListener 007. Private Client As TcpClient 008. Private NetStream As NetworkStream 009. 010. 'Questa procedura serve per attivare o disattivare i 011. 'controlli a seconda che si sia connessi oppure no. Serve 012. 'per impedire che si tenti di inviare un messaggio quando 013. 'non si è connessi, ad esempio 014. Private Sub EnableControls(ByVal Connected As Boolean) 015. btnConnect.Enabled = Not Connected 016. btnListen.Enabled = Not Connected 017. txtIP.Enabled = Not Connected 018. 019. btnSend.Enabled = Connected 020. txtMessage.Enabled = Connected 021. btnDisconnect.Enabled = Connected 022.
  • 384. 023. If Connected Then 024. tmrData.Start() 025. Else 026. tmrData.Stop() 027. End If 028. End Sub 029. 030. Private Sub btnListen_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnListen.Click 031. 'Inizializza il listener e inizia l'ascolto sulla porta 032. '5000. Inoltre, attiva il timer per controllare se ci 033. 'sono connessioni in arrivo. Il timer scatta ogni 100ms 034. Listener = New TcpListener(5000) 035. Listener.Start() 036. tmrConnections.Start() 037. btnListen.Enabled = False 038. btnConnect.Enabled = False 039. txtLog.AppendText("Server - in ascolto..." & Environment.NewLine) 040. End Sub 041. 042. Private Sub tmrConnections_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles tmrConnections.Tick 043. 'Se ci sono connessioni... 044. If Listener.Pending() Then 045. 'Ferma un attimo il timer 046. tmrConnections.Stop() 047. 048. 'Chiede all'utente se confermare la connessione 049. If MessageBox.Show("Rilevato un tentativo di connessione. Accettare?", Me.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Question) = Windows.Forms.DialogResult.Yes Then 050. 'Ottiene l'oggetto TcpClient collegato al client 051. Client = Listener.AcceptTcpClient() 052. 'Ferma il listener 053. Listener.Stop() 054. 'Ottiene il network stream 055. NetStream = Client.GetStream() 056. 'E attiva/disattiva i controlli per quando si è connessi 057. EnableControls(True) 058. Else 059. Listener.Stop() 060. Listener.Start() 061. tmrConnections.Start() 062. End If 063. End If 064. End Sub 065. 066. Private Sub btnConnect_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnConnect.Click 067. Dim IP As Net.IPAddress 068. 069. 'Prima esegue un controllo sull'indirizzo IP per 070. 'controllare che sia valido 071. If Not Net.IPAddress.TryParse(txtIP.Text, IP) Then 072. MessageBox.Show("IP non valido!", Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Exclamation) 073. Exit Sub 074. End If 075. 076. 'Quindi inizializza un client e tenta la connessione 077. 'al dato IP sulla porta 5000 078. Client = New TcpClient() 079. txtLog.AppendText("Client - tentativo di connessione..." & vbCrLf) 080. Try 081. Application.DoEvents() 082. Client.Connect(IP, 5000) 083. Catch Ex As Exception 084. 085. End Try 086. 087. 'Se la connessione ha avuto successo, ottiene il network 088.
  • 385. 'stream e agisce sui controlli come nel codice precedente 089. If Client.Connected Then 090. txtLog.AppendText("Tentativo di connessione riuscito!" & vbCrLf) 091. NetStream = Client.GetStream() 092. EnableControls(True) 093. Else 094. txtLog.AppendText("Tentativo di connessione fallito..." & vbCrLf) 095. End If 096. End Sub 097. 098. Private Sub tmrData_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles tmrData.Tick 099. 'Se ci sono dati disponibili 100. If Client.Available > 0 Then 101. 'Li legge dallo stream 102. Dim Buffer(Client.Available - 1) As Byte 103. NetStream.Read(Buffer, 0, Buffer.Length) 104. 105. 'Li trasforma in una stringa 106. Dim Msg As String = UTF8.GetString(Buffer) 107. 108. 'Se il messaggio inizia con questa stringa 109. 'particolare signifia che l'altro utente ha chiuso 110. 'la connessione, quindi disconnette anche questo 111. If Msg.StartsWith("close") Then 112. btnDisconnect_Click(Me, EventArgs.Empty) 113. Exit Sub 114. End If 115. 116. 'Altrimenti lo accoda alla textbox grande 117. txtLog.AppendText("Ricevuto: ") 118. txtLog.AppendText(Msg) 119. txtLog.AppendText(Environment.NewLine) 120. End If 121. End Sub 122. 123. Private Sub btnSend_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSend.Click 124. If Not String.IsNullOrEmpty(txtMessage.Text) Then 125. Dim Buffer() As Byte = UTF8.GetBytes(txtMessage.Text) 126. txtLog.AppendText("Inviato: " & txtMessage.Text & Environment.NewLine) 127. NetStream.Write(Buffer, 0, Buffer.Length) 128. txtMessage.Text = "" 129. End If 130. End Sub 131. 132. Private Sub btnDisconnect_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnDisconnect.Click 133. tmrData.Stop() 134. 135. Dim Buffer() As Byte = UTF8.GetBytes("close") 136. NetStream.Write(Buffer, 0, Buffer.Length) 137. 138. Client.Client.Close() 139. Client.Close() 140. Client = Nothing 141. 142. If Listener IsNot Nothing Then 143. Listener.Server.Close() 144. Listener = Nothing 145. End If 146. 147. EnableControls(False) 148. txtLog.AppendText("Disconnesso" & Environment.NewLine) 149. End Sub 150. End Class Come avete visto dal codice non c'è nulla di par ticolar mente complicato da capir e. Tuttavia, questo pr ogr amma è molto semplice e per mette di gestir e solo una connessione (in ar r ivo o in uscita). Il Listener , anche se r iavviabile, continuer à a dar e Pending = Tr ue almeno fino a che tutti i client r elativi alla connessione siano stati cor r ettamente chiusi, e
  • 386. questo non si ver ifica se non alla fine del pr ogr amma, ossia quando uno dei due si disconnette. Per far la br eve, è impossibile cr ear e un'applicazione di scambio messaggi multiutente con questi oggetti e queste modalità.
  • 387. D5. I Socket - Parte II Esempio: File Sender Fino ad or a si è par lato di inviar e semplici messaggi sotto for ma di str inghe, ma come ci si dovr ebbe compor tar e nel caso il contenuto da inviar e sia un file inter o o, per chè no?, molti files? Il pr ocedimento è lo stesso e con questo esempio for nir ò una pr ova di come sia altr ettanto semplice questo compito. L'applicazione File Sender si basa su un semplice scambio di inter r ogazioni tr a i due computer , al ter mine delle quali si inizia l'invio effettivo del file. Per pr ima cosa il client comunica al ser ver che sta per cominciar e il flusso di dati; il ser ver deve per ciò r isponder e in caso affer mativo se l'utente è disposto al tr asfer imento: in questo caso, r imanda indietr o un messaggio di confer ma, e apr e una nuova por ta per i dati in ar r ivo; par allelamente, il client si connette alla por ta aper ta e inizia il tr asfer imento. File Sender: server Ho str uttur ato l'inter faccia del ser ver in questo modo: Label1 : una label esplicativo con il testo "Pr ogr esso:" pr gPr ogr ess : la bar r a del pr ogr esso cmdListen : il pulsante "Ascolta" str Status : la status str ip sul lato basso del for m lblStatus : la label contenuta in str Status, con il compito di infor mar e l'utente sullo stato dell'applicazione tmr Contr olConnection : timer con Inter val = 100 che ha il compito di contr ollar e se ci sono r ichieste in attesa tmr Contr olFile : timer con Inter val = 100 con il compito di contr ollar e se ci sono r ichieste in attesa sulla por ta 1001, deputata in questo caso alla r icezione del file dal client tmr GetData : timer con Inter val = 100 con il compito di ottener e i messaggi inviati dal client e di r isponder vi bgReceiveFile : Backgr oundWr oker con Wr oker Repor tPr ogr ess = Tr ue che ha il compito di r icever e il file dal client E si pr esenta gr aficamente così: Ed ecco il codice: 001.
  • 388. Imports System.Net.Sockets 002. Imports System.Text.ASCIIEncoding 003. 004. Imports System.ComponentModel 005. Public Class Form1 006. 'Listener: attende una connessione sulla porta 25 007. 'FileListener: attende una connessione sulla porta 1001. Questa 008. ' ha il compito di trasferire i bytes del file 009. 010. Private Listener, FileListener As TcpListener 011. 'Client: l'oggetto che ha il compito di dialogare con 012. ' il client e confermarne le operazioni 013. 'FileReceiver: l'oggetto che ha il compito di ricevere le 014. 015. ' informazioni contenute nel file e scriverle sulla macchina 016. ' in forma di file concreto 017. Private Client, FileReceiver As TcpClient 018. 'NetStream: lo stream su cui si scrivono i dati di comunicazione 019. 020. 'NetFile: lo stream da cui si leggono i dati del file 021. Private NetStream, NetFile As NetworkStream 022. 'Percorso su cui salvare il file 023. Private FileName As String 024. 025. 'Dimensione del file 026. Private FileSize As Int64 027. 028. 'I seguenti metodi semplificano le operazioni di invio e 029. 'ricezione di stringhe 030. 031. 'Invia un messaggio su uno stream di rete 032. Private Sub Send(ByVal Msg As String, ByVal Stream As NetworkStream) 033. 'Se si può scrivere 034. 035. If Stream.CanWrite Then 036. 'Converte il messaggio in binario 037. Dim Bytes() As Byte = ASCII.GetBytes(Msg) 038. 'E lo scrive sul network stream 039. 040. Stream.Write(Bytes, 0, Bytes.Length) 041. End If 042. End Sub 043. 044. 'Ottiene un messaggio dallo stream di rete 045. Private Function GetMessage(ByVal Stream As NetworkStream) As String 046. 047. 'Se si può leggere 048. If Stream.CanRead Then 049. Dim Bytes(Client.ReceiveBufferSize) As Byte 050. 051. Dim Msg As String 052. 'Legge i bytes arrivati 053. Stream.Read(Bytes, 0, Bytes.Length) 054. 'Li converte in una stringa leggibile 055. Msg = ASCII.GetString(Bytes) 056. 'E restituisce la stringa 057. 058. Return Msg.Normalize 059. Else 060. Return Nothing 061. End If 062. End Function 063. 064. Private Sub cmdListen_Click(ByVal sender As Object, _ 065. ByVal e As EventArgs) Handles cmdListen.Click 066. If cmdListen.Text = "Ascolta" Then 067. 068. 'Inizia ad ascoltare sulla porta 25 069. Listener = New TcpListener(25) 070. Listener.Start() 071. 'Attiva il timer per controllare le richieste di connesione 072. tmrControlConnection.Start() 073.
  • 389. 'Cambia il testo e la funzione del pulsante 074. cmdListen.Text = "Stop" 075. Else 076. 077. 'Ferma l'operazione di ascolto 078. Listener.Stop() 079. 'Ripristina il testo 080. cmdListen.Text = "Ascolta" 081. End If 082. End Sub 083. 084. Private Sub tmrControlConnection_Tick(ByVal sender As Object, _ 085. ByVal e As EventArgs) Handles tmrControlConnection.Tick 086. 'Se ci sono connessioni in attesa... 087. 088. If Listener.Pending Then 089. 'Ferma il timer per eseguire le operazioni 090. tmrControlConnection.Stop() 091. lblStatus.Text = "È stata ricevuta una richiesta" 092. 'Richiede all'utente se accettare la connessione 093. If MessageBox.Show("È stata ricevuta una richiesta di connessione. Accettare?", _ 094. Me.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Question) = _ 095. Windows.Forms.DialogResult.Yes Then 096. 097. 'Acceta la connessione 098. Client = Listener.AcceptTcpClient 099. 'Apre lo stream di rete condiviso 100. NetStream = Client.GetStream 101. 'Termina l'ascolto 102. Listener.Stop() 103. 'Rende il pulsante cmdListen inutilizzabile, poiché 104. 'una connessione è già stata aperta 105. 106. cmdListen.Enabled = False 107. 'Inizia la ricezione di messaggi 108. tmrGetData.Start() 109. lblStatus.Text = "Connessione riuscita!" 110. Else 111. 'Altrimenti si rimette in attesa per altre connessioni 112. tmrControlConnection.Start() 113. lblStatus.Text = "In attesa di connessioni..." 114. End If 115. 116. End If 117. End Sub 118. 119. Private Sub tmrControlFile_Tick(ByVal sender As Object, _ 120. ByVal e As EventArgs) Handles tmrControlFile.Tick 121. 'Se c'è una richiesta, l'accetta subito 122. 123. If FileListener.Pending Then 124. tmrControlFile.Stop() 125. FileReceiver = FileListener.AcceptTcpClient 126. NetFile = FileReceiver.GetStream 127. 'Ferma il listener 128. FileListener.Stop() 129. lblStatus.Text = "Flusso di informazioni aperto" 130. 'Attiva la ricezione di dati attraverso un background worker 131. bgReceiveFile.RunWorkerAsync() 132. End If 133. 134. End Sub 135. 136. Private Sub tmrGetData_Tick(ByVal sender As Object, _ 137. ByVal e As EventArgs) Handles tmrGetData.Tick 138. If Client.Connected And Client.Available Then 139. 140. 'Ferma il timer mentre si eseguono le operazioni 141. tmrGetData.Stop() 142. 'Legge il messaggio 143. Dim Msg As String = GetMessage(NetStream) 144. 145.
  • 390. If Msg.StartsWith("ConfirmTransfer") Then 146. 147. 'Divide il messagio in parti in base al carattere pipe 148. Dim Parts() As String = Msg.Split("|") 149. 'La prima parte è "ConfirmTransfer" 150. 151. 'La seconda è il percorso del file sull'altro computer 152. Dim File As String = Parts(1) 153. 'La terza è la dimensione 154. 155. Dim Size As Int64 = CType(Parts(2), Int64) 156. 'Ottiene solo il nome del file, senza percorso 157. File = IO.Path.GetFileName(File) 158. 'Costruisce il percorso del file su questo computer, 159. 'salvandolo nella cartella del progetto (binDebug) 160. 161. FileName = Application.StartupPath & "" & File 162. 'Imposta Size come variabile globale 163. FileSize = Size 164. 'Richiede se accettare il trasferimento 165. If MessageBox.Show(String.Format( _ 166. "È stata ricevuta una richiesta di trasferimento di {0} ({1} bytes). Acettare?", _ 167. File, Size), Me.Text, MessageBoxButtons.YesNo, _ 168. MessageBoxIcon.Question) = Windows.Forms.DialogResult.Yes Then 169. 170. 'Manda OK al client 171. Send("OK", NetStream) 172. 'Intanto si mette in attesa sulla porta 1001 per 173. 'l'invio dei bytes del file 174. FileListener = New TcpListener(1001) 175. FileListener.Start() 176. 'E attiva il timer di controllo 177. 178. tmrControlFile.Start() 179. Else 180. 'Altrimenti, risponde di no 181. Send("NO", NetStream) 182. End If 183. End If 184. 185. 'Riprende il controllo 186. tmrGetData.Start() 187. End If 188. End Sub 189. 190. Private Sub bgReceiveFile_DoWork(ByVal sender As Object, _ 191. ByVal e As DoWorkEventArgs) Handles bgReceiveFile.DoWork 192. 'Apre un nuovo stream in base al percorso costruito 193. 194. 'nella procedura precedente 195. Dim Stream As New IO.FileStream(FileName, IO.FileMode.Create) 196. 'Crea un indice che indica il progresso 197. Dim Index As Int64 = 0 198. 199. lblStatus.Text = "In ricezione..." 200. Do 201. 202. If FileReceiver.Available Then 203. 'Riceve i bytes necessari 204. Dim Bytes(4096) As Byte 205. 206. Dim Msg As String = ASCII.GetString(Bytes) 207. 'Se i bytes sono un messaggio stringa e contengono 208. '"END", oppure la dimensione giusta è già stata 209. 210. 'raggiunta, allora si ferma 211. If Msg.Contains("END") Or Index >= FileSize Then 212. Exit Do 213. 214. End If 215. 'Preleva i bytes dallo stream di rete 216.
  • 391. NetFile.Read(Bytes, 0, 4096) 217. 'E li scrive sul file fisico 218. Stream.Write(Bytes, 0, 4096) 219. 'Incrementa l'indice di 4096 220. 221. Index += 4096 222. 'E notifica il progresso 223. bgReceiveFile.ReportProgress(Index * 100 / FileSize) 224. End If 225. Loop 226. 227. lblStatus.Text = "File ricevuto!" 228. Stream.Close() 229. MessageBox.Show("File ricevuto con successo!", Me.Text, _ 230. MessageBoxButtons.OK, MessageBoxIcon.Information) 231. End Sub 232. 233. Private Sub bgReceiveFile_ProgressChanged(ByVal sender As Object, _ 234. ByVal e As ProgressChangedEventArgs) _ 235. Handles bgReceiveFile.ProgressChanged 236. prgProgress.Value = e.ProgressPercentage 237. End Sub 238. 239. End Class File Sender: c lient Ho str uttur a l'inter faccia del client in questo modo: gr pTr asnfer : un Gr oupBox con Tex t = "Tr asfer imento" che contiene tutti i contr olli sul tr asfer imento del file tx tFile : una Tex tBox che contiene il per cor so del file da inviar e cmdBr ow se : un pulsante con Tex t = "Sfoglia" per per metter e all'utente di selezionar e un file in manier a semplice cmdSend : un pulsante con Tex t = "Invia" che ha il compito di inoltr ar e la r ichiesta al ser ver pr gPr ogr ess : una bar r a di pr ogr esso cmdConnect : un pulsante con Tex t = "Connetti" con il compito di connetter si al ser ver str Status : una StatusStr ip nel lato infer ior e del for m lblStatus : la label con il compito di tener e l'utente al cor r ente dello stato dell'applicazione tmr GetData : un timer con Inter val = 100 per r icever e e inviar e messaggi al ser ver bgSendFile : un Backgr oundWr oker con Wr oker Repor tPr ogr ess = Tr ue che ha il compito di inviar e il file L'inter faccia si pr esenta così:
  • 392. E questo è il codice: 001. Imports System.Net.Sockets 002. Imports System.Text.ASCIIEncoding 003. Imports System.ComponentModel 004. 005. Public Class Form1 006. 'Client: il client che si dovrà connettere al server 007. 'FileSender: il client che ha il compito di trasferire i 008. ' pacchetti di informazioni al server 009. 010. Private Client, FileSender As TcpClient 011. 'NetStream: lo stream su cui scrivere i dati di comunicazione 012. 'NetFile: lo stream per inviare i dati da scrivere sul file 013. Private NetStream, NetFile As NetworkStream 014. 'L'IP del server a cui connettersi 015. 016. Private IP As String 017. 018. 'I seguenti metodi semplificano le operazioni di invio e 019. 'ricezione di stringhe 020. 021. 'Invia un messaggio su uno stream di rete 022. Private Sub Send(ByVal Msg As String, ByVal Stream As NetworkStream) 023. 'Se si può scrivere 024. 025. If Stream.CanWrite Then 026. 'Converte il messaggio in binario 027. Dim Bytes() As Byte = ASCII.GetBytes(Msg) 028. 'E lo scrive sul network stream 029. 030. Stream.Write(Bytes, 0, Bytes.Length) 031. End If 032. End Sub 033. 034. 'Ottiene un messaggio dallo stream di rete 035. Private Function GetMessage(ByVal Stream As NetworkStream) As String 036. 037. 'Se si può leggere 038. If Stream.CanRead Then 039. Dim Bytes(Client.ReceiveBufferSize) As Byte 040. 041. Dim Msg As String 042. 'Legge i bytes arrivati 043. Stream.Read(Bytes, 0, Bytes.Length) 044. 'Li converte in una stringa leggibile 045. Msg = ASCII.GetString(Bytes) 046. 'E restituisce la stringa 047. 048. Return Msg.Normalize 049. Else 050. Return Nothing 051. End If 052. End Function 053. 054. Private Sub cmdConnect_Click(ByVal sender As Object, _ 055. ByVal e As EventArgs) Handles cmdConnect.Click 056. 'Ottiene l'IP del server 057. 058. IP = InputBox("Inserire l'IP del server:", Me.Text) 059. 060. 'Controlla che l'IP non sia nullo o vuoto 061. If String.IsNullOrEmpty(IP) Then 062. MessageBox.Show("Connessiona annullata!", Me.Text, _ 063. MessageBoxButtons.OK, MessageBoxIcon.Exclamation) 064. Exit Sub 065. 066. End If 067. 068. 'Inizializza un nuovo client 069.
  • 393. Client = New TcpClient 070. 'E tenta la connessione all'IP dato, sulla porta 25 071. 072. lblStatus.Text = "Connessione in corso..." 073. Try 074. Client.Connect(IP, 25) 075. Catch SE As SocketException 076. MessageBox.Show("Impossibile stabilire una connessione!", _ 077. Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Exclamation) 078. Exit Sub 079. 080. End Try 081. 'Se la connessione è riuscita, ottiene lo 082. 'stream condiviso di rete direttamente collegato con 083. 'il networkstream del server 084. 085. If Client.Connected Then 086. 'Ora si è sicuri di essere connessi: 087. 'sblocca i comandi per il trasferimento 088. NetStream = Client.GetStream 089. grpTransfer.Enabled = True 090. lblStatus.Text = "Connessione riuscita!" 091. End If 092. 093. End Sub 094. 095. Private Sub cmdBrowse_Click(ByVal sender As Object, _ 096. ByVal e As EventArgs) Handles cmdBrowse.Click 097. Dim Open As New OpenFileDialog 098. Open.Filter = "Tutti i file|*.*" 099. If Open.ShowDialog = Windows.Forms.DialogResult.OK Then 100. 101. txtFile.Text = Open.FileName 102. End If 103. End Sub 104. 105. Private Sub cmdSend_Click(ByVal sender As Object, _ 106. ByVal e As EventArgs) Handles cmdSend.Click 107. 'Controlla che il file esista 108. 109. If Not IO.File.Exists(txtFile.Text) Then 110. MessageBox.Show("Il file non esiste!", Me.Text, _ 111. MessageBoxButtons.OK, MessageBoxIcon.Exclamation) 112. Exit Sub 113. End If 114. 115. 'Se si è connessi e si può scrivere 116. 'sullo stream di rete... 117. If Client.Connected AndAlso NetStream.CanWrite Then 118. 119. 'Manda un messaggio al server, chiedendo 120. 'conferma del trasferimento. Nel messaggio immette anche 121. 'alcune informazioni riguardo il nome e la 122. 'dimensione del file 123. Dim Msg As String = _ 124. String.Format("ConfirmTransfer|{0}|{1}", txtFile.Text, _ 125. FileLen(txtFile.Text)) 126. 'Invia il messaggio con la procedura scritta sopra 127. 128. Send(Msg, NetStream) 129. 130. 'Attiva il timer per controllare i dati arrivati 131. tmrGetData.Start() 132. 'Disattiva il pulsante per evitare più azioni 133. 'contemporanee indesiderate 134. cmdSend.Enabled = False 135. lblStatus.Text = "In attesa di conferma dal server..." 136. End If 137. 138. End Sub 139. 140. Private Sub tmrGetData_Tick(ByVal sender As Object, _ 141.
  • 394. ByVal e As EventArgs) Handles tmrGetData.Tick 142. If Client.Connected AndAlso Client.Available Then 143. 144. 'Ferma il timer mentre si eseguono le operazioni 145. tmrGetData.Stop() 146. 'Legge il messaggio 147. Dim Msg As String = GetMessage(NetStream) 148. 149. 'Uso Contains per un semplice motivo. Quando si converte 150. 151. 'un array di bytes in una stringa, ci possono essere 152. 'caratteri speciali successivi a questa, come ad esempio 153. 'il NULL terminator (carattere 00), che ne compromettono 154. 'la struttura. 155. If Msg.Contains("OK") Then 156. 157. 'Termina questa connessione e si connette 158. 'alla porta deputata alla ricezione dei file 159. FileSender = New TcpClient 160. FileSender.Connect(IP, 1001) 161. If FileSender.Connected Then 162. 163. 'Ottiene lo stream associato a questa operaizone 164. NetFile = FileSender.GetStream 165. 'E inizia la trasmissione dei dati 166. bgSendFile.RunWorkerAsync(txtFile.Text) 167. End If 168. ElseIf Msg.Contains("NO") Then 169. 170. MessageBox.Show("Il server ha rifiutato il trasferimento!", _ 171. Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Exclamation) 172. cmdSend.Enabled = True 173. End If 174. 175. 'Riprende il controllo dei dati 176. tmrGetData.Start() 177. End If 178. 179. End Sub 180. 181. Private Sub bgSendFile_DoWork(ByVal sender As Object, _ 182. ByVal e As DoWorkEventArgs) Handles bgSendFile.DoWork 183. 'Ottiene il nome del file dall'argomento passato al metodo 184. 185. 'RunWorkerAsync nella procedura precedente 186. Dim FileName As String = e.Argument 187. 'Crea un nuovo lettore del file a basso livello, così 188. 'da poter ottenere bytes di informazione anziché caratteri 189. 190. 'come nello StreamReader 191. Dim Reader As New IO.FileStream(FileName, IO.FileMode.Open) 192. 'Calcola la grandezza del file, per poter poi tenere 193. 'l'utente al corrente della percentuale di completamento 194. 195. Dim Size As Int64 = FileLen(FileName) 196. 'Un blocco di bytes da 4096 posti. Il file viene spedito in 197. '"pacchettini" per evitare di sovraccaricare la connessione 198. Dim Bytes(4095) As Byte 199. 200. 'Se il file è più grande di 4KiB, lo divide 201. 'in blocchi di dati da 4096 bytes 202. If Size > 4096 Then 203. 204. For Block As Int64 = 0 To Size Step 4096 205. 'Se i bytes rimanenti sono più di 4096, 206. 207. 'ne legge un blocco intero 208. If Size - Block >= 4096 Then 209. Reader.Read(Bytes, 0, 4096) 210. Else 211. 'Altrimenti un blocco più piccolo 212. 213.
  • 395. Reader.Read(Bytes, 0, Size - Block) 214. End If 215. 'Scrive i dati prelevati sullo stream di rete, 216. 'inviandoli così al server 217. NetFile.Write(Bytes, 0, 4096) 218. 'Riporta la percentuale all'utente 219. 220. bgSendFile.ReportProgress(Block * 100 / Size) 221. 'Smette per 30ms, così da dare tempo dal 222. 'server di poter processare i pacchetti uno per 223. 'uno, evitando confusione 224. Threading.Thread.Sleep(30) 225. Next 226. 227. Else 228. 'Se il file è minore di 4KiB, lo invia tutto 229. 'direttamente dal server 230. Reader.Read(Bytes, 0, Size) 231. NetFile.Write(Bytes, 0, Size) 232. End If 233. 234. Reader.Close() 235. 236. 'Percentuale massima: lavoro terminato 237. bgSendFile.ReportProgress(100) 238. Threading.Thread.Sleep(100) 239. 'Comunica la fine delle operazioni 240. NetFile.Write(ASCII.GetBytes("END"), 0, 3) 241. MessageBox.Show("File inviato con successo!", Me.Text, _ 242.