Sei sulla pagina 1di 519

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. 02. 03. 04. 05. 06. 07. 08. 09. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. Namespace Triangoli Class Scaleno '... End Class Class Isoscele '... End Class Class Equilatero '... End Class End Namespace Namespace Quadrilateri Namespace Parallelogrammi Class Parallelogramma '... End Class Namespace Rombi Class Rombo '... End Class Class Quadrato '... End Class End Namespace End Namespace 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 Documenti\Visual Studio 2008\Pr ojects\[Nome pr ogetto]\bin\Release.

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. V += B 'Equivale a 05. 06. B -= V 'Equivale a 07. V *= B 'Equivale a 08. B /= V 'Equivale a 09. End Sub 10. End Module

V B V B

= = = =

V B V B

+ * /

B V B V

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) Console.ReadKey() 10. 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. Console.WriteLine("Inserire il lato di un quadrato:") 05. Lato = Console.ReadLine 06. 07. 08. If Lato < 0 Then 'Se Lato minore di 0... Console.WriteLine("Il lato non pu avere una misura negativa!") 09. Else 'Altrimenti, se non lo ... 10. Console.WriteLine("L'area del quadrato : " & Lato * Lato) 11. 12. End If 'Fine controllo 13. Console.ReadKey() 14. 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() Dim a, b As Double 03. 04. Console.WriteLine("Inserire i lati di un rettangolo:") 05. a = Console.ReadLine 06. 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 Console.WriteLine("Non esistono lati con misure negative!") 13. 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. Console.WriteLine("Inserisci il tuo voto:") 05. 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. Console.ReadKey() 20. 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 ... 'End Sub 04. Dim a, b, c As Single 05. 06. 07. 'Semplice esempio di risoluzione di equazione di 08. 'secondo grado Console.WriteLine("Equazione: ax2 + bx + c = 0") 09. Console.WriteLine("Inserisci, in ordine, a, b e c:") 10. a = Console.ReadLine 11. b = Console.ReadLine 12. 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 'coincide con il programma. Equivale a terminare 19. 'il programma stesso 20. Exit Sub 21. End If 22. 23. 'Anche delta fa parte del blocco delimitato da Sub ... 24. 'End Sub 25. Dim delta As Single = b ^ 2 - 4 * a * c 26. 27. 'Esistono due soluzioni distinte 28. If delta > 0 Then 29. 'Queste variabili fanno parte del blocco di If ... 30. 'ElseIf 31. Dim x1, x2 As Single 32. ' possibile accedere senza problemi alla variabile 33. 'delta, poich questo blocco a sua volta 34. 'all'interno del blocco in cui dichiarato delta 35. x1 = (-b + Math.Sqrt(delta)) / (2 * a) 36. x2 = (-b - Math.Sqrt(delta)) / (2 * a) 37. Console.WriteLine("Soluzioni: ") 38. Console.WriteLine("x1 = " & x1) 39. Console.WriteLine("x2 = " & x2) 40. 41. 'Esiste una soluzione doppia 42. ElseIf delta = 0 Then 43. 'Questa variabile fa parte del blocco ElseIf ... Else 44. Dim x As Single 45. x = -b / (2 * a) 46. Console.WriteLine("Soluzione doppia: ") 47. Console.WriteLine("x = " & x) 48. 49. 'Non esistono soluzioni in R 50. Else 51. Console.WriteLine("Non esistono soluzioni in R") 52. End If 53. 54. Console.ReadKey() 55. End Sub 56. 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. 03. 04. 05. 06. 07. 08. 09. 10. 11. '... If A = 1 Then 'istruzioni End If If A = 2 Then 'istruzioni End If If A = 3 Then 'istruzioni End If '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] Case [valore1] 03. 04. 'istruzioni Case [valore2] 05. 06. 'istruzioni 07. Case [valore3] 'istruzioni 08. 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 Console.WriteLine(a * b) 18. Case 4 19. 20. Console.WriteLine(a / b) End Select 21. 22. Console.ReadKey() 23. End Sub 24. 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. 02. 03. 04. 05. 06. 07. 08. 09. 10. Dim A As Int32 '... Select Case A Case 1, 2, 3 'Questo codice viene eseguito 'contiene un valore pari a 1, Case 4, 6, 9 'Questo codice viene eseguito 'contiene un valore pari a 4, End Select

solo se A 2 o 3 solo se A 6 o 9

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 4. 'contiene un valore compreso tra 67 e 5. Case 91 To 191 6. 'Questo codice viene eseguito solo se 7. 'contiene un valore compreso tra 91 e 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.

A 90 (estremi inclusi) A 191

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. Console.WriteLine("L'area del rettangolo : " & (a * b)) 27. 28. Console.ReadKey() Loop 29. End Sub 30. 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. Do 05. 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() Dim a As Int32 = 0 03. 04. Do While (a < 2) And (a > 0) 05. a += 1 06. 07. Loop Console.WriteLine(a) 08. 09. Console.ReadKey() 10. End Sub 11. 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 a = c 23. 24. Console.WriteLine(c) 25. Loop 26. Console.ReadKey() 27. 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] 'istruzioni 4. 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. 2. 3. 4. 5. 6. 7. For [variabile] As [tipo] = [valore] To [numero] 'istruzioni Next 'Che, se volessimo descrivere con un esempio, diverrebbe cos: For H As Int16 = 78 To 108 'istruzioni 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 Do While (Mark > 0) And (Index < 10) 19. 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 Marks(Index) = Mark 23. Index += 1 24. 25. Console.WriteLine("Inserisci un altro voto (0 per terminare):") 26. Mark = Console.ReadLine 27. Loop 28. 'Decrementa l'indice di 1, poich anche se l'utente 29. 'ha immesso 0, nel ciclo precedente, l'indice era stato 30. 'incrementato prevedendo un'ulteriore immissione, che, 31. 'invece, non c' stata 32. Index -= 1 33. 34. 'Totale dei voti 35. Dim Total As Single = 0 36. 'Usa un ciclo For per scorrere tutte le celle dell'array 37. 'e sommarne i valori 38. For I As Int32 = 0 To Index 39. Total += Marks(I) 40. Next 41. 42. 'Mostra la media 43. Console.WriteLine("La tua media : " & (Total / (Index + 1))) 44. Console.ReadKey() 45. End Sub 46. 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. 2. 3. 4. Dim Parole(2) As String Parole(0) = "ciao" Parole(1) = "mouse" 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. Console.ReadKey() 29. 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. 2. 3. 4. Dim A(,) As Int32 Dim B()() As Int32 '... 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 Array.Resize(A, n) 10. 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. 2. 3. 4. 5. 'Words.Length restituisce il numero di elementi 'presenti nell'array Words For I As Int32 = 0 To Words.Length - 1 Console.Write(Words(I) & " ") 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 'Ora cosa succede? 28. 29. Console.ReadKey() 30. End Sub 31. 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 'tipo value, contiene un valore predefinito, il primo 19. 'Gennaio dell'anno 1 d.C. a mezzogiorno in punto. 20. 21. If Date.TryParse(StrDate, D) Then 'D.DayOfWeek contiene il giorno della settimana di D 22. '(luned, marted, eccetera...), ma in un 23. 'formato speciale, l'Enumeratore, che vedremo nei 24. 'prossimi capitoli. 25. 'Il ".ToString()" converte questo valore in una 26. 'stringa, ossia in un testo leggibile: i giorni della 27. 'settimana, per, sono in inglese 28. Console.WriteLine(D.DayOfWeek.ToString()) 29. Else 30. Console.WriteLine(StrDate & " non una data valida!") 31. End If 32. End Sub 33. 34. 'Altra procedura, simile alla prima 35. Sub CalculateDaysDifference() 36. Dim StrDate1, StrDate2 As String 37. Console.Write("Inserisci il primo giorno (dd/mm/yyyy): ") 38. StrDate1 = Console.ReadLine 39. Console.Write("Inserisci il secondo giorno (dd/mm/yyyy): ") 40. StrDate2 = Console.ReadLine 41. 42. Dim Date1, Date2 As Date 43. 44. If Date.TryParse(StrDate1, Date1) And _ 45. Date.TryParse(StrDate2, Date2) Then 46. 'La differenza tra due date restituisce il tempo 47. 'trascorso tra l'una e l'altra. In questo caso noi 48. 'prendiamo solo i giorni 49. Console.WriteLine((Date2 - Date1).Days) 50. 51.

52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91. 92. 93. 94. 95. End

Console.WriteLine("Inserire due date valide!") End If End Sub Sub Main() 'Command una variabile di tipo char (carattere) che 'conterr una lettera indicante quale compito eseguire Dim Command As Char Do Console.Clear() Console.WriteLine("Qualche operazione con le date:") Console.WriteLine("- Premere F per sapere in che giorno " & _ "della settimana cade una certa data;") Console.WriteLine("- Premere D per calcolare la differenza tra due date;") Console.WriteLine("- Premere E per uscire.") 'Console.ReadKey() la funzione che abbiamo sempre 'usato fin'ora per fermare il programma in attesa della 'pressione di un pulsante. Come vedremo fra breve, non ' necessario usare il valore restituito da una 'funzione, ma in questo caso ci serve. Ci che 'ReadKey restituisce qualcosa che non ho ancora 'trattato. Per ora basti sapere che 'Console.ReadKey().KeyChar contiene l'ultimo carattere 'premuto sulla tastiera Command = Console.ReadKey().KeyChar 'Analizza il valore di Command Select Case Command Case "f" 'Invoca la procedura FindDay() FindDay() Case "d" 'Invoca la procedura CalculateDaysDifference() CalculateDaysDifference() Case "e" 'Esce dal ciclo Exit Do Case Else Console.WriteLine("Comando non riconosciuto!") End Select Console.ReadKey()

Else

Loop End Sub 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.

12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. End

Total += Number End Sub 'Legge un numero da tastiera e lo sottrae al totale Sub Subtract() Dim Number As Single = Console.ReadLine Total -= Number End Sub 'Legge un numero da tastiera e divide il totale per 'tale numero Sub Divide() Dim Number As Single = Console.ReadLine Total /= Number End Sub 'Legge un numero da tastiera e moltiplica il totale 'per tale numero Sub Multiply() Dim Number As Single = Console.ReadLine Total *= Number End Sub Sub Main() 'Questa variabile conterr il simbolo matematico 'dell'operazione da eseguire Dim Operation As Char Do Console.Clear() Console.WriteLine("Risultato attuale: " & Total) Operation = Console.ReadKey().KeyChar Select Case Operation Case "+" Sum() Case "-" Subtract() Case "*" Multiply() Case "/" Divide() Case "e" Exit Do Case Else Console.WriteLine("Operatore non riconosciuto") Console.ReadKey() End Select Loop End Sub 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. Sub Sum(ByVal Number As Single) 04. Total += Number 05. 06. End Sub 07. 08. Sub Subtract(ByVal Number As Single) Total -= Number 09. End Sub 10. 11. 12. Sub Divide(ByVal Number As Single) 13. Total /= Number End Sub 14. 15. 16. Sub Multiply(ByVal Number As Single) 17. Total *= Number 18. End Sub 19. Sub Main() 20. 21. 'Questa variabile conterr il simbolo matematico 'dell'operazione da eseguire 22. Dim Operation As Char 23. Do 24. Console.Clear() 25. Console.WriteLine("Risultato attuale: " & Total) 26. Operation = Console.ReadKey().KeyChar 27. Select Case Operation 28. 'Se si tratta di simboli accettabili 29. Case "+", "-", "*", "/" 30. 'Legge un numero da tastiera 31. Dim N As Single = Console.ReadLine 32. 'E a seconda dell'operazione, utilizza una 33. 'procedura piuttosto che un'altra 34. Select Case Operation 35. Case "+" 36. Sum(N) 37. Case "-" 38. Subtract(N) 39. Case "*" 40. Multiply(N) 41. Case "/" 42. Divide(N) 43. End Select 44. Case "e" 45. Exit Do 46. Case Else 47. Console.WriteLine("Operatore non riconosciuto") 48. Console.ReadKey() 49. End Select 50. Loop 51. End Sub 52. 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. Sub DoOperation(ByVal Number As Single, ByVal Op As Char) 04. Select Case Op 05. 06. Case "+" 07. Total += Number 08. Case "-" Total -= Number 09. Case "*" 10. 11. Total *= Number 12. Case "/" 13. Total /= Number End Select 14. 15. End Sub 16. 17. Sub Main() 18. Dim Operation As Char Do 19. Console.Clear() 20. Console.WriteLine("Risultato attuale: " & Total) 21. Operation = Console.ReadKey().KeyChar 22. Select Case Operation 23. Case "+", "-", "*", "/" 24. Dim N As Single = Console.ReadLine 25. 'A questa procedura vengono passati due 26. 'parametri: il primo il numero da 27. 'aggiungere/sottrarre/moltiplicare/dividere 28. 'a Total; il secondo il simbolo 29. 'matematico che rappresenta l'operazione 30. 'da eseguire 31. DoOperation(N, Operation) 32. Case "e" 33. Exit Do 34. Case Else 35. Console.WriteLine("Operatore non riconosciuto") 36. Console.ReadKey() 37. End Select 38. Loop 39. End Sub 40. 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:\Users\Totem\Documents\Visual Studio 2008\Projects\ConsoleApplication2\bin\Debug" 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 End Sub 04. 05. 06. Sub Main() 07. Dim A As Int32 = 56 Change(A) 08. 09. Console.WriteLine(A) Console.ReadKey() 10. End Sub 11. 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) Console.ReadKey() 10. 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 Console.WriteLine("Media: " & Average(Values)) 23. Console.ReadKey() 24. 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.

10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. End

For i As Byte = 1 To Esponente X *= Base Next Return X End If End If End Function Sub Main() Dim F As Double Dim b As Single Dim e As Byte Console.WriteLine("Inserire base ed esponente:") b = Console.ReadLine e = Console.ReadLine F = Potenza(b, e) Console.WriteLine(b & " elevato a " & e & " vale " & F) Console.ReadKey() End Sub Module

Else

Return Base

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 Test() 10. 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 'caratterei debbano essere stampati a schermo. AnimationSpeed 04. 05. ' la velocit dell'animazione, di default 1 06. Sub DrawBar(ByVal Length As Int32, _ 07. Optional ByVal AnimationSpeed As Single = 1) 'La variabile static tiene conto del punto a cui si 08. 'arrivati al caricamento 09. Static Index As Int32 = 1 10. 11. 12. 'Disegna la barra 13. For I As Int32 = 1 To Length If I > Index Then 14. 15. Console.Write("-") Else 16. 17. Console.Write("|") 18. End If Next 19. 20. 'Aumenta l'indice di uno. Notare il particolare 21. 'assegnamento che utilizza l'operatore Mod. Finch 22. 'Index minore di Length, questa espressione equivale 23. 'banalmente a Index + 1, poich a Mod b = a se a < b. 24. 'Quando Index supera il valore di Length, allora l'operatore 25. 'Mod cambia le cose: infatti, se Index = Length + 1, 26. 'l'espressione restituisce 0, che, sommato a 1, d 1. 27. 'Il risultato che otteniamo che Index reinizia 28. 'da capo, da 1 fino a Length. 29. Index = (Index Mod (Length + 1)) + 1 30. 'Il metodo Sleep, che vedremo approfonditamente solo nella 31. 'sezione B, fa attendere al programma un certo numero di 32. 'millisecondi. 33. '1000 / AnimationSpeed provoca una diminuzione del tempo 34. 'di attesa all'aumentare della velocit 35. Threading.Thread.CurrentThread.Sleep(1000 / AnimationSpeed) 36. End Sub 37. 38. Sub Main() 39. 'Disegna la barra con un ciclo infinito. Potete invocare 40. 'DrawBar(20) tralasciando l'ultimo argomento e l'animazione 41. 'sar lenta poich la velocit di default 1 42. Do 43. Console.Clear() 44. DrawBar(20, 5) 45. Loop 46. End Sub 47. 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 Dim Result As Single = 0 04. 05. 06. For I As Int32 = 0 To Values.Length - 1 07. Result += Values(I) 08. Next 09. Return Result 10. 11. End Function 12. 13. Sub Main() Dim S As Single 14. 15. 16. 'Somma due valori 17. S = Sum(1, 2) 18. 'Somma quattro valori S = Sum(1.1, 5.6, 98.2, 23) 19. 'Somma un array di valori 20. Dim V() As Single = {1, 8, 3.4} 21. S = Sum(V) 22. End Sub 23. 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 'il pagamento di alcune tasse (non sono un esperto di 03. 04. 'economia, perci mi mantengono piuttosto sul vago XD). 'Il primo parametro rappresenta il prezzo originale, mentre 05. 06. 'il secondo un parametro indefinito che 07. 'raggruppa tutte le varie tasse vigenti sul prodotto 'da acquistare che devono essere aggiunte all'importo 08. 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() Dim Price As Single = 120 20. 21. 22. 'Aggiunge una tassa del 5% a Price Dim Price2 As Single = _ 23. ApplyTaxes(Price, 5) 24. 25. 'Aggiunge una tassa del 5%, una del 12.5% e una 26. 27. 'dell'1% a Price Dim Price3 As Single = _ 28. ApplyTaxes(Price, 5, 12.5, 1) 29. 30. Console.WriteLine("Prezzo originale: " & Price) 31. Console.WriteLine("Presso con tassa 1: " & Price2) 32. Console.WriteLine("Prezzo con tassa 1, 2 e 3: " & Price3) 33. 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 'fattoriale cresce in modo abnorme e gi a 170! Double non 03. 04. 'basta pi a contenere il risultato Function Factorial(ByVal N As Byte) As Double 05. If N <= 1 Then 06. 07. Return 1 Else 08. Return N * Factorial(N - 1) 09. 10. End If End Function 11. 12. Sub Main() 13. 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. Console.ReadKey() 20. End Sub 21. 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 'Case una keyword, ma noi la vogliamo usare come 25. 26. 'identificatore. Function ToCase(ByVal Str As String, ByVal [Case] As StringCase) As String 27. 'Le funzioni per convertire in Lower e Upper 28. 'case sono gi definite. E' sufficiente 29. 'indicare un punto dopo il nome della variabile 30. 31. 'stringa, seguito a ToLower e ToUpper Select Case [Case] 32. Case StringCase.Lower 33. Return Str.ToLower() 34. Case StringCase.Upper 35. Return Str.ToUpper() 36. Case StringCase.Proper 37. 'Consideriamo la stringa come array di 38. 'caratteri: 39. Dim Chars() As Char = Str.ToLower() 40. 'Iteriamo lungo tutta la lunghezza della 41. 'stringa, dove Str.Length restituisce appunto 42. 'tale lunghezza 43. For I As Int32 = 0 To Str.Length - 1 44. 'Se questo carattere uno spazio oppure 45. ' il primo di tutta la stringa, il 46. 'prossimo indicher l'inizio di una nuova 47. 48.

49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. End

Next 'Restituisce l'array modificato (un array di caratteri 'e una stringa sono equivalenti) Return Chars Case StringCase.Sentence 'Riduce tutta la stringa a Lower Case Str = Str.ToLower() 'Imposta il primo carattere come maiuscolo Dim Chars() As Char = Str Chars(0) = Char.ToUpper(Chars(0)) Str = Chars 'La chiude con un punto Str = Str & "." Return Str End Select End Function Sub Main() Dim Str As String = "QuEstA una stRingA DI prova" 'Per usare i valori di un enumeratore bisogna sempre scrivere 'il nome dell'enumeratore seguito dal punto Console.WriteLine(ToCase(Str, StringCase.Lower)) Console.WriteLine(ToCase(Str, StringCase.Upper)) Console.WriteLine(ToCase(Str, StringCase.Proper)) Console.WriteLine(ToCase(Str, StringCase.Sentence)) Console.ReadKey() End Sub Module

'parola e dovr essere maiuscolo. If I = 0 Then Chars(I) = Char.ToUpper(Chars(I)) End If If Chars(I) = " " And I < Str.Length - 1 Then 'Char.ToUpper rende maiuscolo un carattere 'passato come parametro e lo restituisce Chars(I + 1) = Char.ToUpper(Chars(I + 1)) End If

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. 2. 3. 4. Console.WriteLine(StringCase.Lower) Console.WriteLine(StringCase.Upper) Console.WriteLine(StringCase.Sentence) Console.WriteLine(StringCase.Proper)

A scher mo appar ir 1. 2. 3. 4. 0 1 2 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 'sotto il La centrale 04. Enum Note 05. C = -9 06. CSharp 07. D 08. DSharp 09. E 10. F 11. FSharp 12. G 13. GSharp 14. A 15. ASharp 16. B 17. End Enum 18. 19. 'Restituisce la frequenza di una nota. N, in concreto, 20. 'rappresenta la differenza, in semitoni, di quella nota 21. 'dal La centrale. Ecco l'utilitt degli enumeratori, 22. 'che danno un nome reale a ci che un dato indica 23. 'indirettamente 24. Function GetFrequency(ByVal N As Note) As Single 25. Return 440 * 2 ^ (N / 12) 26. End Function 27. 28. 'Per ora prendete per buona questa funzione che restituisce 29. 'il nome della costante di un enumeratore a partire dal 30. 'suo valore. Avremo modo di approfondire nei capitoli 31. 'sulla Reflection 32. Function GetName(ByVal N As Note) As String 33. Return [Enum].GetName(GetType(Note), N) 34. End Function 35. 36. Sub Main() 37. 'Possiamo anche iterare usando gli enumeratori, poich 38. 'si tratta pur sempre di semplici numeri 39. For I As Int32 = Note.C To Note.B 40. Console.WriteLine("La nota " & GetName(I) & _ 41. " risuona a una frequenza di " & GetFrequency(I) & "Hz") 42. Next 43. 44. Console.ReadKey() 45. End Sub 46. 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 'abbiano un nome al plurale 03. 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. Sub Main() 24. Dim F As FileAttributes 25. 26. 'F all'inizio 0, non contiene niente: '00000000 27. 28. F = FileAttributes.Normal 29. 'Ora F 1, ossia Normal 30. 31. '00000001 32. F = FileAttributes.Hidden Or FileAttributes.System 33. 'La situazione diventa complessa: 34. 'Il primo valore 2: 000000010 35. 'Il secondo valore 4: 000000100 36. 'Abbiamo gi visto l'operatore Or: restituisce True se 37. 'almeno una delle condizioni vera: qui True 38. '1 e False 0: 39. 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. 'Ora F 00001110 e bisogna eseguire un'operazione di And 08. 'sui bit, confrontando questo valore con Archive, che 8. 09. 'And restituisce Vero solo quando entrambe le condizioni 10. 'sono vere: 11. 12. '00001110 And 13. '00001000 = '00001000, ossia Archive! 14. 15. If F And FileAttributes.Archive = FileAttributes.Archive Then Console.WriteLine("Il file marcato come 'Archive'") 16. 17. End If 18. Console.ReadKey() End Sub 19. 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. 2. 3. 4. 5. 6. 7. 8. Structure Contact Dim Name, Surname, PhoneNumber As String End Structure '... 'Un array di conttati, ognuno rappresentato dalla struttura Contact 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] Dim [Campo2] As [Tipo] 3. 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 End Structure 04. 05. 06. Sub Main() 07. 'Contacts(-1) inizializza un array vuoto, 08. 'ossia con 0 elementi Dim Contacts(-1) As Contact 09. Dim Command As Char 10. 11. 12. Do 13. Console.Clear() Console.WriteLine("Rubrica -----") 14. Console.WriteLine("Selezionare l'azione desiderata:") 15. Console.WriteLine("N - Nuovo contatto;") 16. Console.WriteLine("T - Trova contatto;") 17. 18. Console.WriteLine("E - Esci.") Command = Char.ToUpper(Console.ReadKey().KeyChar) 19. Console.Clear() 20. 21. Select Case Command 22. Case "N" 23. 'Usa ReDim Preserve per aumentare le dimensioni 24. 'dell'array mentenendo i dati gi presenti. 25. 'L'uso di array e di redim, in questo caso, 26. 'sconsigliato, a favore delle pi versatili 27. 'Liste, che per non ho ancora introdotto. 28. 'Ricordate che il valore specificato tra 29. 'parentesi indica l'indice massimo e non 30. 'il numero di elementi. 31. 'Se, all'inizio, Contacts.Length 0, 32. 'richiamando ReDim Contacts(0), si aumenta 33. 'la lunghezza dell'array a uno, poich 34. 'in questo caso l'indice massimo 0, 35. 'ossia quello che indica il primo e 36. 'l'unico elemento 37. ReDim Preserve Contacts(Contacts.Length) 38. 39. Dim N As Contact 40. Console.Write("Nome: ") 41. N.Name = Console.ReadLine 42. Console.Write("Cognome: ") 43. N.Surname = Console.ReadLine 44. Console.Write("Numero di telefono: ") 45. N.PhoneNumber = Console.ReadLine 46. 47. 'Inserisce nell'ultima cella dell'array 48. 'l'elemento appena creato 49. Contacts(Contacts.Length - 1) = N 50. 51. Case "T" 52. Dim Part As String 53. 54. Console.WriteLine("Inserire nome o cognome del " & _ 55. "contatto da trovare:") 56. Part = Console.ReadLine 57. 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 Dim Command As Char 21. 22. Do 23. Console.Clear() 24. 25. Console.WriteLine("Rubrica -----") 26. Console.WriteLine("Selezionare l'azione desiderata:") 27. Console.WriteLine("N - Nuovo contatto;") Console.WriteLine("T - Trova contatto;") 28. Console.WriteLine("E - Esci.") 29. 30. Command = Char.ToUpper(Console.ReadKey().KeyChar) Console.Clear() 31. 32. Select Case Command 33. Case "N" 34. ReDim Preserve Contacts(Contacts.Length) 35. 36. Dim N As Contact 37. Console.Write("Nome: ") 38. N.Name = Console.ReadLine 39. Console.Write("Cognome: ") 40. N.Surname = Console.ReadLine 41. 42. 'Ricordate che le dimensioni dell'array non 43. 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 Console.WriteLine("Nome: " & C.Name) 69. Console.WriteLine("Cognome: " & C.Surname) 70. Console.WriteLine("Numeri di telefono: ") 71. For Each N As String In C.PhoneNumbers 72. Console.WriteLine(" - " & N) 73. Next 74. Console.WriteLine() 75. End If 76. Next 77. 78. Case "E" 79. Exit Do 80. 81. Case Else 82. Console.WriteLine("Comando sconosciuto!") 83. End Select 84. Console.ReadKey() 85. Loop 86. End Sub 87. 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. 02. 03. 04. 05. 06. 07. 08. 09. 10. 11. Structure Address Dim State, Town As String Dim Street, CivicNumber As String Dim Cap As String End Structure Structure Contact Dim Name, Surname As String Dim PhoneNumbers() As String Dim Home As Address 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.

04. 05. 06. 07. 08. 09. 10. 11.

A.Name = "Mario" A.Surname = "Rossi" ReDim A.PhoneNumbers(0) A.PhoneNumbers(0) = "124 90 87 111" A.Home.State = "Italy" A.Home.Town = "Pavia" A.Home.Street = "Corso Napoleone" A.Home.CivicNumber = "96/B" 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 Return (SideLength ^ 2) 24. 25. End Function 26. 'Questa funzione restituisce il volume del cubo 27. 28. Function GetVolume() As Single Return (SideLength ^ 3) 29. End Function 30. 31. 'Questa funzione restituisce la massa del cubo 32. Function GetMass() As Single 33. Return (Density * GetVolume()) 34. End Function 35. End Class 36. 37. Sub Main() 38. 'Variabile di tipo Cube, che rappresenta uno specifico cubo 39. 'La riga di codice che segue contiene la dichiarazione 40. 'della variabile A. La dichiarazione di una variabile 41. 'fa sapere al compilatore, ad esempio, di che tipo 42. 'sar, in quale blocco di codice sar 43. 'visibile, ma nulla di pi. 44. 'Non esiste ancora un oggetto Cube collegato ad A, ma 45. 'potrebbe essere creato in un immediato futuro. 46. 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 'Costruttore, di cui parler approfonditamente 66. 67. 'in seguito 68. 69. 'Come per le strutture, i membri di classe sono accessibili 'tramite l'operatore punto ".". Ora imposto le variabili 70. 'contenute in A per rappresentare un cubo di alluminio 71. '(densit 2700 Kg/m<sup>3</sup>) di 1.5m di lato 72. A.SetData(1.5, 2700) 73. 74. Console.WriteLine("Superficie faccia: " & A.GetSurfaceArea() & " m2") 75. Console.WriteLine("Volume: " & A.GetVolume() & " m3") 76. Console.WriteLine("Massa: " & A.GetMass() & " Kg") 77. 'It's Over 9000!!!! 78. 79. Console.ReadKey() 80. End Sub 81. 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 Dim SideLength As Single 03. Dim Density As Single 04. 05. 'Quasi uguale a SetData 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. 18. End Function 19. Function GetMass() As Single 20. Return (Density * GetVolume()) 21. End Function 22. End Class 23. 24. Sub Main() 25. 'Questa una sintassi pi concisa che equivale a: 26. 'Dim A As Cube 27. 'A = New Cube(2700, 1.5) 28. 'Tra parentesi vanno passati i parametri richiesti dal 29. 'costruttore 30. Dim A As New Cube(2700, 1.5) 31. 32. Console.WriteLine("Superficie faccia: " & A.GetSurfaceArea() & " m<sup>2</sup>") 33. Console.WriteLine("Volume: " & A.GetVolume() & " m<sup>3</sup>") 34. Console.WriteLine("Massa: " & A.GetMass() & " Kg") 35. 36. Console.ReadKey() 37. End Sub 38. 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 Dim Density As Single 04. 05. 06. Sub New(ByVal SideLengthValue As Single, ByVal DensityValue As Single) 07. SideLength = SideLengthValue 08. Density = DensityValue End Sub 09. 10. 11. Function GetSurfaceArea() As Single 12. Return (SideLength ^ 2) 13. End Function 14. 15. Function GetVolume() As Single Return (SideLength ^ 3) 16. 17. End Function 18. Function GetMass() As Single 19. Return (Density * GetVolume()) 20. End Function 21. End Structure 22. 23. Sub Main() 24. 'Questo codice 25. Dim A As New Cube(2700, 1.5) 26. 27. 'Equivale a questo 28. Dim B As Cube 29. B.SideLength = 1.5 30. B.Density = 2700 31. 32. 'A e B sono uguali 33. 34. Console.ReadKey() 35. End Sub 36. 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.

010. 011. 012. 013. 014. 015. 016. 017. 018. 019. 020. 021. 022. 023. 024. 025. 026. 027. 028. 029. 030. 031. 032. 033. 034. 035. 036. 037. 038. 039. 040. 041. 042. 043. 044. 045. 046. 047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081.

'variabile di tipo SortOrder e se questo fosse private, 'non si potrebbe usare al di fuori della classe, cosa 'che invece viene richiesta. Public Enum SortOrder Ascending 'Crescente Descending 'Decrescente None 'Nessun ordinamento End Enum 'Mantiene in memoria il senso di ordinamento della lista, 'per evitare di riordinarla nel caso fosse richiesto due 'volte lo stesso Private CurrentOrder As SortOrder = SortOrder.None 'Mantiene in memoria una copia dell'array, che 'accessibile ai soli membri della classe. In 'questo modo, possibile eseguire tutte 'le operazioni di ordinamento usando un solo metodo 'per l'inserimento dell'array Private Buffer() As Double 'Memorizza in Buffer l'array passato come parametro Public Sub PushArray(ByVal Array() As Double) 'Se Buffer diverso da Nothing, lo imposta 'esplicitamente a Nothing (equivale a distruggere 'l'oggetto) If Buffer IsNot Nothing Then Buffer = Nothing End If 'Copia l'array: ricordate come si comportano i tipi 'reference e pensate a quali ripercussioni tale 'comportamento potr avere sul codice Buffer = Array 'Annulla CurrentOrder CurrentOrder = SortOrder.None End Sub 'Procedura che ordina l'array secondo il senso specificato Public Sub Sort(ByVal Order As SortOrder) 'Se il senso None, oppure uguale a quello corrente, ' inutile proseguire, quindi si ferma ed esce If (Order = SortOrder.None) Or (Order = CurrentOrder) Then Exit Sub End If 'Questa variabile tiene conto di tutti gli scambi 'effettuati Dim Occurrences As Int32 = 0 'Il ciclo seguente ordina l'array in senso crescente: 'se l'elemento i maggiore dell'elemento i+1, 'ne inverte il posto, e aumenta il contatore di 1. 'Quando il contatore rimane 0 anche dopo il For, 'significa che non c' stato nessuno scambio 'e quindi l'array ordinato. Do Occurrences = 0 For I As Int32 = 0 To Buffer.Length - 2 If Buffer(I) > Buffer(I + 1) Then Dim Temp As Double = Buffer(I) Buffer(I) = Buffer(I + 1) Buffer(I + 1) = Temp Occurrences += 1 End If Next Loop Until Occurrences = 0 'Se l'ordine era discendente, inverte l'array If Order = SortOrder.Descending Then Array.Reverse(Buffer) End If 'Memorizza l'ordine

082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. End

CurrentOrder = Order End Sub 'Restituisce l'array ordinato Public Function PopArray() As Double() Return Buffer End Function End Class Sub Main() 'Crea un Dim a As 'Crea un Dim b As array temporaneo Double() = {1, 6, 2, 9, 3, 4, 8} nuovo oggetto BubbleSorter New BubbleSorter()

'Vi inserisce l'array b.PushArray(a) 'Invoca la procedura di ordinamento b.Sort(BubbleSorter.SortOrder.Descending) 'E per ogni elemento presente nell'array finale '(quello restituito dalla funzione PopArray), ne stampa 'il valore a schermo For Each n As Double In (b.PopArray()) Console.Write(n & " ") Next Console.ReadKey() End Sub 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. '... Return [Valore restituito] 04. End Get 05. Set(ByVal value As [Tipo]) 06. 07. '... End Set 08. 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 'esattamente su quel valore 20. _Number = value 21. 22. End Set End Property 23. End Class 24. 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. Console.ReadLine() 48. End Sub 49. 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.

14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. End

Return _DynamicFrictionCoefficient End Get Set(ByVal value As Single) _DynamicFrictionCoefficient = value End Set End Property 'Massa, m Public Property Mass() As Single Get Return _Mass End Get Set(ByVal value As Single) _Mass = value End Set End Property 'Accelerazione di gravit che vale nel sistema, g Public Property GravityAcceleration() As Single Get Return _GravityAcceleration End Get Set(ByVal value As Single) _GravityAcceleration = value End Set End Property 'Calcola e restituisce la forza di attrito che agisce 'quando la massa in moto Public Function CalculateFrictionForce() As Single Return (Mass * GravityAcceleration) * DynamicFrictionCoefficient End Function End Class Sub Main() Dim F As New InertialFrame() Console.WriteLine("Sistema inerziale formato da:") Console.WriteLine(" - Un piano orizzontale e scabro;") Console.WriteLine(" - Una massa variabile.") Console.WriteLine() Console.WriteLine("Inserire i dati:") Console.Write("Coefficiente di attrito dinamico = ") F.DynamicFrictionCoefficient = Console.ReadLine Console.Write("Massa (Kg) = ") F.Mass = Console.ReadLine Console.Write("Accelerazione di gravit (m/s<sup>2</sup>) = ") F.GravityAcceleration = Console.ReadLine Console.WriteLine() Console.Write("Attrito dinamico = ") Console.WriteLine(F.CalculateFrictionForce() & " N") Console.ReadLine() End Sub 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") _Mass = 1 32. End If 33. End Set 34. End Property 35. 36. Public Property GravityAcceleration() As Single 37. Get 38. Return _GravityAcceleration 39. End Get 40. Set(ByVal value As Single) 41. _GravityAcceleration = Math.Abs(value) 42. End Set 43. End Property 44. 45. Public Function CalculateFrictionForce() As Single 46. Return (Mass * GravityAcceleration) * DynamicFrictionCoefficient 47. End Function 48. 49. End Class 50. 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 End Get 04. Set(ByVal value As Single) 05. 06. If (value > 30) And (value < 100) Then 07. _Number = value 08. Else _Number = 31 09. End If 10. End Set 11. 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.

07. 08. 09. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. End

Return _SideLength End Get Set(ByVal value As Single) If value > 0 Then _SideLength = value Else _SideLength = 1 End If End Set End Property Public Property Density() As Single Get Return _Density End Get Set(ByVal value As Single) If value > 0 Then _Density = value Else _Density = 1 End If End Set End Property Public ReadOnly Property SurfaceArea() As Single Get Return (SideLength ^ 2) End Get End Property Public ReadOnly Property Volume() As Single Get Return (SideLength ^ 3) End Get End Property Public ReadOnly Property Mass() As Single Get Return (Volume * Density) End Get End Property Class

Get

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. 02. 03. 04. 05. 06. 07. 08. 09. 10. 11. 12. 13. 14. 15. 16. Class ASystem Private _Box As Cube Public ReadOnly Property Box() As Cube Get Return _Box End Get End Property '... End Class '... Dim S As New ASystem() 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 Class NumberExtractor 04. Private _ExtractedNumbers() As Byte 05. 06. 'Generatore di numeri casuali. Random una classe 07. 'del namespace System Private Rnd As Random 08. 09. 'Questa propriet ha un parametro, Index, che 10. 'specifica a quale posizione dell'array si intende recarsi 11. 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 'elaborazione su macchine a 32 bit 16. 17. Public ReadOnly Property ExtractedNumbers(ByVal Index As Int32) As Byte 18. Get If (Index >= 0) And (Index < _ExtractedNumbers.Length) Then 19. Return _ExtractedNumbers(Index) 20. Else 21. Return 0 22. End If 23. End Get 24. End Property 25. 26. Public Sub New() 27. 'Essendo di tipo reference, si deve creare un nuovo 28. 'oggetto Random e assegnarlo a Rnd. La ragione per cui 29. 'Rnd un membro di classe consiste nel fatto 30. 'che se fosse stata variabile temporanea del corpo 31. 'della procedura ExtractNumbers, sarebbero usciti 32. 'gli stessi numeri. Questo perch la sequenza 33. 'pseudocasuale creata dalla classe non cambia se 34. 'non glielo si comunica espressamente usando un altro 35. 'costruttore. Non tratter questo argomento ora 36. Rnd = New Random() 37. ReDim _ExtractedNumbers(5) 38. End Sub 39. 40. Public Sub ExtractNumbers() 41. 'Estrae 6 numeri casuali tra 1 e 90 e li pone nell'array 42. For I As Int32 = 0 To 5 43. _ExtractedNumbers(I) = Rnd.Next(1, 91) 44. Next 45. End Sub 46. End Class 47. 48. Sub Main() 49. Dim E As New NumberExtractor() 50. 51. E.ExtractNumbers() 52. Console.WriteLine("Numeri estratti: ") 53. For I As Int32 = 0 To 5 54. Console.Write(E.ExtractedNumbers(I) & " ") 55. 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. Public Sub ExtractNumbers() 24. For I As Int32 = 0 To 5 25. 26. _ExtractedNumbers(I) = Rnd.Next(1, 91) Next 27. End Sub 28. End Class 29. 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. 2. 3. 4. Dim S As String = "Ciao" Dim C As Char = S.Chars(1) ' > C = "i", poich "i" il carattere alla posizione 1 ' 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. '... End Get 4. Private Set(ByVal value As Byte) 5. 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. '... End Get 4. 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. Class Cube 03. Private _SideLength As Single 04. Private _Density As Single 05. 06. 07. 'Questo campo Shared, condiviso. Come vedete, 'per dichiarare un membro come tale, si pone la 08. 09. 'keyword Shared dopo lo specificatore di accesso. Questa 'variabile conterr il numero di cubi creati 10. 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 Get 16. 17. Return _SideLength 18. End Get Set(ByVal value As Single) 19. If value > 0 Then 20. _SideLength = value 21. Else 22. _SideLength = 1 23. End If 24. End Set 25. End Property 26. 27. Public Property Density() As Single 28. Get 29. Return _Density 30. End Get 31. Set(ByVal value As Single) 32. If value > 0 Then 33. _Density = value 34. Else 35. _Density = 1 36. End If 37. End Set 38. End Property 39. 40. Public ReadOnly Property SurfaceArea() As Single 41. Get 42. Return (SideLength ^ 2) 43. End Get 44. End Property 45. 46. Public ReadOnly Property Volume() As Single 47. Get 48. Return (SideLength ^ 3) 49. 50.

51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91. 92. 93. 94. 95. End

End Get End Property Public ReadOnly Property Mass() As Single Get Return (Volume * Density) End Get End Property 'Allo stesso modo, la propriet che espone il membro 'shared deve essere anch'essa shared Public Shared ReadOnly Property CubesCount() As Int32 Get Return _CubesCount End Get End Property 'Ogni volta che un nuovo cubo viene creato, _CubesCount 'viene aumentato di uno, per rispecchiare il nuovo numero 'di istanze della classe Cube esistenti in memoria Sub New() _CubesCount += 1 End Sub End Class Sub Main() Dim Cube1 As New Cube() Cube1.SideLength = 1 Cube1.Density = 2700 Dim Cube2 As New Cube() Cube2.SideLength = 0.9 Cube2.Density = 3500 Console.Write("Cubi creati: ") 'Notate come si accede a un membro condiviso: poich 'appartiene alla classe e non alla singola istanza, vi si 'accede specificando prima il nome della classe, poi 'il comune operatore punto, e successivamente il nome 'del membro. Tutti i membri shared funzionano in questo 'modo Console.WriteLine(Cube.CubesCount) Console.ReadKey() End Sub 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. End Sub 4. 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. 02. 03. 04. 05. 06. 07. 08. 09. 10. 11. 12. Module Module2 Sub Hello() Console.WriteLine("Hello!") End Sub End Module Module Module1 Sub Main() Hello() ' = Module2.Hello() Console.ReadKey() End Sub 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. 02. 03. 04. 05. 06. 07. 08. 09. 10. 11. 12. 13. 14. 15. Module Module2 Sub Hello() Console.WriteLine("Hello Module2!") End Sub End Module Class Class2 Shared Sub Hello() Console.WriteLine("Hello Class2!") End Sub End Class Module Module1 Sub Main()

'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. Class Cube 03. 04. '... 05. End Class 06. 07. Sub Main() 'Crea un nuovo arraylist 08. 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) & " - ") Console.Write("Lato (m): ") 19. 20. C.SideLength = Console.ReadLine 21. 22. Console.Write(" Densit (kg/m<sup>3</sup>): ") C.Density = Console.ReadLine 23. 24. 25. 'Aggiunge un nuovo cubo alla collezione Cubes.Add(C) 26. 27. Console.WriteLine("Termina inserimento? y/n") 28. 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. 02. 03. 04. 05. 06. 07. 08. 09. 10. 11. 12. 13. 'Hashtabel contenente alcuni materiali e le 'relative densit Dim H As New Hashtable 'Aggiunge un elemento, contraddistinto da una chiave stringa H.Add("Acqua", 1000) H.Add("Alluminio", 2700) H.Add("Argento", 10490) H.Add("Nichel", 8800) '... 'Possiamo usare l'hashtable per associare 'facilmente densit ai nostri cubi: 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 Get 20. Return _FileName 21. 22. End Get End Property 23. 24. Public ReadOnly Property Text() As String 25. 26. Get Return _Text 27. End Get 28. End Property 29. 30. 31. 'Da notare il costruttore Private: nessun client al di 'fuori della classe pu inizializzare il nuovo 32. 'oggetto. Solo il metodo factory lo pu fare 33. Private Sub New(ByVal ID As Int16, ByVal Path As String) 34. Me._ID = ID 35. Me._FileName = Path 36. Me._Text = IO.File.ReadAllText(Path) 37. 'Me fa riferimento alla classe stessa 38. Documents.Add(ID, Me) 39. End Sub 40. 41. 'Il metodo factory crea un documento se non esiste l'ID 42. 'e se il percorso su disco diverso, altrimenti 43. 'restituisce il documento che esiste gi 44. Public Shared Function Create(ByVal ID As Int16, _ 45. ByVal Path As String) As Document 46. If Documents.ContainsKey(ID) Then 47. 'Ottiene il documento gi esistente con questo ID 48. 49.

50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. End

Dim D As Document = Documents(ID) 'Se coincidono sia l'ID che il nome del file, 'allora restituisce l'oggetto gi esistente If D.FileName = Path Then Return D Else 'Altrimenti restituisce Nothing, dato che non 'possono esistere due documenti con uguale ID, 'o si farebbe confusione Return Nothing End If End If 'Se non esiste un documento con questo ID, lo crea Return New Document(ID, Path) End Function End Class Sub Main() Dim D As Dim E As Dim F As Dim G As Document Document Document Document = = = = Document.Create(0, Document.Create(0, Document.Create(0, Document.Create(1, "C:\testo.txt") "C:\testo.txt") "C:\file.txt") "C:\file.txt")

'Dimostra che se ID e Path coincidono, i due oggetti 'sono la stessa istanza Console.WriteLine(E Is D) 'Dimostra che se l'ID esiste gi, ma il Path differisce, 'l'oggetto restituito Nothing Console.WriteLine(F Is Nothing) Console.ReadKey() End Sub 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. 2. 3. 4. 5. 'Questa stringa Dim S As String = 'Viene creata una 'e assegnata a S. S = "Buongiorno" immutabile "Ciao" nuova stringa temporanea con valore "Buongiorno" "Ciao" verr distrutta dal Garbage Colletcion

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 'Costante pubblica 04. Public Const Costante As Byte = 56 05. 'Variabile pubblica che non pu essere modificata 06. 07. Public ReadOnly Nome As String 'Variabile privata 08. Private Variabile As Char 09. 10. 'Costruttore della classe: accetta un parametro 11. 12. Sub New(ByVal Nome As String) 13. Console.WriteLine("Sto inizializzando un oggetto Esempio...") 'Le variabili ReadOnly sono assegnabli solo nel 14. 'costruttore della classe 15. Me.Nome = Nome 16. 17. Me.Variabile = "c" 18. End Sub End Class 19. 20. 'Costruttore del Modulo 21. Sub New() 22. Console.WriteLine("Sto inizializzando il Modulo...") 23. End Sub 24. 25. Sub Main() 26. Dim E As New Esempio("Ciao") 27. E.Nome = "Io" ' Sbagliato: Nome ReadOnly 28. Console.ReadKey() 29. End Sub 30. 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 'Da notare che un costruttore statico NON pu avere 08. 09. 'parametri: il motivo semplice. Se li potesse avere 'e ci fossero pi costruttori normali il compilatore 10. 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 Sub New() 19. Console.WriteLine("Costruttore normale: sto creando un oggetto...") 20. 21. End Sub 22. Public Sub WriteLine(ByVal Text As String) 23. 24. File.WriteLine(Text) End Sub 25. End Class 26. 27. Sub Main() 28. 'Qui viene eseguito il costruttore statico e quello normale 29. Dim E1 As New Errors 30. 'Qui solo quello normale 31. Dim E2 As New Errors 32. 33. E1.WriteLine("Nessun errore") 34. 35. Console.ReadKey() 36. End Sub 37. 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 Set(ByVal value As Int32) 020. 021. If value <> 0 Then 022. _Denumerator = value 023. Else 'Il denominatore non pu mai essere 0 024. 'Dovremmo lanciare un'eccezione, ma vedremo pi 025. 026. 'avanti come si fa. Per ora lo impostiamo a uno _Denumerator = 1 027. End If 028. End Set 029. End Property 030. 031. 'Costruttore con due parametri, che inizializza numeratore 032. 'e denominatore 033. Sub New(ByVal N As Int32, ByVal D As Int32) 034. Me.Numerator = N 035. Me.Denumerator = D 036. End Sub 037. 038. 'Restituisce la Fraction sottoforma di stringa 039. Function Show() As String 040. Return Me.Numerator & " / " & Me.Denumerator 041. End Function 042. 043. 'Semplifica la Fraction 044. Sub Semplify() 045. 046.

047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118.

Dim X As Int32 'Prende X come il valore meno alto in modulo 'e lo inserisce in X. X servir per un 'calcolo spicciolo del massimo comune divisore X = Math.Min(Math.Abs(Me.Numerator), Math.Abs(Me.Denumerator)) 'Prima di iniziare, per evitare errori, controlla 'se numeratore e denominatore sono entrambi negativi: 'in questo caso li divide per -1 If (Me.Numerator < 0) And (Me.Denumerator < 0) Then Me.Numerator /= -1 Me.Denumerator /= -1 End If 'E con un ciclo scova il valore pi alto di X 'per cui sono divisibili sia numeratore che denominatore '(massimo comune divisore) e li divide per quel numero. 'Continua a decrementare X finch non trova un 'valore per cui siano divisibili sia numeratore che 'denominatore: dato che era partito dall'alto, questo 'sar indubbiamente il MCD Do Until ((Me.Numerator Mod X = 0) And (Me.Denumerator Mod X = 0)) X -= 1 Loop 'Divide numeratore e denominatore per l'MCD Me.Numerator /= X Me.Denumerator /= X End Sub 'Somma due frazioni e restituisce la somma Shared Operator +(ByVal F1 As Fraction, ByVal F2 As Fraction) _ As Fraction Dim F3 As Fraction 'Se i denumeratori sono uguali, si limita a sommare 'i numeratori If F1.Denumerator = F2.Denumerator Then F3.Denumerator = F1.Denumerator F3.Numerator = F1.Numerator + F2.Numerator Else 'Altrimenti esegue tutta l'operazione 'x a x*b + a*y '- + - = --------'y b y*b F3.Denumerator = F1.Denumerator * F2.Denumerator F3.Numerator = F1.Numerator * F2.Denumerator + F2.Numerator * F1.Denumerator End If 'Semplifica la Fraction F3.Semplify() Return F3 End Operator 'Sottrae due Fraction e restituisce la differenza Shared Operator -(ByVal F1 As Fraction, ByVal F2 As Fraction) _ As Fraction 'Somma l'opposto del secondo membro F2.Numerator = -F2.Numerator Return F1 + F2 End Operator 'Moltiplica due frazioni e restituisce il prodotto Shared Operator *(ByVal F1 As Fraction, ByVal F2 As Fraction) _ As Fraction 'Inizializza F3 con il numeratore pari al prodotto 'dei numeratori e il denominatore pari al prodotto dei 'denominatori Dim F3 As Fraction = New Fraction(F1.Numerator * F2.Numerator, _ F1.Denumerator * F2.Denumerator)

119. 120. 121. 122. 123. 124. 125. 126. 127. 128. 129. 130. 131. 132. 133. 134. 135. 136. 137. 138. 139. 140. 141. 142. 143. 144. 145. 146. 147. 148. 149. 150. End

F3.Semplify() Return F3 End Operator 'Divide due frazioni e restituisce il quoziente Shared Operator /(ByVal F1 As Fraction, ByVal F2 As Fraction) _ As Fraction 'Inizializza F3 eseguendo l'operazione: 'a x a y '- / - = - * 'b y b x Dim F3 As Fraction = New Fraction(F1.Numerator * F2.Denumerator, _ F1.Denumerator * F2.Numerator) F3.Semplify() Return F3 End Operator End Structure Sub Main() Dim A As New Fraction(8, 112) Dim B As New Fraction(3, 15) A.Semplify() B.Semplify() Console.WriteLine(A.Show()) Console.WriteLine(B.Show()) Dim C As Fraction = A + B Console.WriteLine("A + B = " & C.Show()) Console.ReadKey() End Sub 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) Return F.Numerator \ F.Denumerator 4. 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. 02. 03. 04. 05. 06. 07. 08. 09. 10. 11. 12. 13. 14. 15. 16. 17. 18. Shared Operator <(ByVal F1 As Fraction, ByVal F2 As Fraction) As Boolean 'Converte le frazioni in double e confronta questi valori Return (CType(F1, Double) < CType(F2, Double)) End Operator Shared Operator >(ByVal F1 As Fraction, ByVal F2 As Fraction) As Boolean Return (CDbl(F1) > CDbl(F2)) End Operator Shared Operator =(ByVal F1 As Fraction, ByVal F2 As Fraction) As Boolean Return (CDbl(F1) = CDbl(F2)) End Operator Shared Operator <>(ByVal F1 As Fraction, ByVal F2 As Fraction) As Boolean 'L'operatore "diverso" restituisce sempre un valore opposto 'all'operatore "uguale" Return Not (F1 = F2) 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. 3. 4. 5. '... a = 10 b = 20 Console.WriteLine("a maggiore di b: " & (a > b)) '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. 02. 03. 04. 05. 06. 07. 08. 09. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. Class Person 'Per velocizzare la scrittura del codice, assumiamo che 'questi campi pubblici siano propriet Public FirstName, LastName As String Public ReadOnly Property CompleteName() As String Get Return FirstName & " " & LastName End Get End Property End Class 'Lo studente, ovviamente, una persona Class Student 'Student eredita da Person Inherits Person 'In pi, definisce anche questi campi pubblici 'La scuola frequentata Public School As String 'E l'anno di corso Public Grade As Byte 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 'Due campi protected 003. 004. Protected _FirstName, _LastName As String 005. 'Un campo private readonly: non c' ragione di rendere 'questo campo Protected poich la data di nascita non 006. 'cambia ed sempre accessibile tramite la propriet 007. 008. 'pubblica BirthDay 009. Private ReadOnly _BirthDay As Date 010. Public Property FirstName() As String 011. 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. Public Property LastName() As String 022. 023. Get Return _LastName 024. End Get 025. Set(ByVal Value As String) 026. If Value <> "" Then 027. 028.

029. 030. 031. 032. 033. 034. 035. 036. 037. 038. 039. 040. 041. 042. 043. 044. 045. 046. 047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099. 100.

_LastName = Value End If End Set End Property Public ReadOnly Property BirthDay() As Date Get Return _BirthDay End Get End Property Public ReadOnly Property CompleteName() As String Get Return _FirstName & " " & _LastName End Get End Property 'Costruttore che accetta tra parametri obbligatori Sub New(ByVal FirstName As String, ByVal LastName As String, _ ByVal BirthDay As Date) Me.FirstName = FirstName Me.LastName = LastName Me._BirthDay = BirthDay End Sub End Class 'Lo studente, ovviamente, una persona Class Student 'Student eredita da Person Inherits Person 'La scuola frequentata Private _School As String 'E l'anno di corso Private _Grade As Byte Public Property School() As String Get Return _School End Get Set(ByVal Value As String) If Value <> "" Then _School = Value End If End Set End Property Public Property Grade() As Byte Get Return _Grade End Get Set(ByVal Value As Byte) If Value > 0 Then _Grade = Value End If End Set End Property 'Questa nuova propriet si serve anche dei campi FirstName 'e LastName nel modo corretto, poich sono Protected anche 'nella classe derivata e fornisce un profilo completo 'dello studente Public ReadOnly Property Profile() As String Get 'Da notare l'accesso a BirthDay tramite la propriet 'Public: non possibile accedere al campo _BirthDay 'perch privato nella classe base Return _FirstName & " " & _LastName & ", nato il " & _ BirthDay.ToShortDateString & " frequenta l'anno " & _ _Grade & " alla scuola " & _School End Get End Property

101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121. 122. 123. 124. 125. 126. 127. 128. 129. 130. 131. 132. 133. 134. 135. End L'output: 1. 2. 3. 4. 5.

'Altra clausola importante: il costruttore della classe 'derivata deve sempre richiamare il costruttore della 'classe base Sub New(ByVal FirstName As String, ByVal LastName As String, _ ByVal BirthDay As Date, ByVal School As String, _ ByVal Grade As Byte) MyBase.New(FirstName, LastName, BirthDay) Me.School = School Me.Grade = Grade End Sub End Class Sub Main() Dim P As New Person("Pinco", "Pallino", Date.Parse("06/07/90")) Dim S As New Student("Tizio", "Caio", Date.Parse("23/05/92"), _ "Liceo Classico Ugo Foscolo", 2) Console.WriteLine(P.CompleteName) 'Come si vede, la classe derivata gode degli stessi membri 'di quella base, acquisiti secondo le regole 'dell'ereditariet appena spiegate Console.WriteLine(S.CompleteName) 'E in pi ha anche i suoi nuovi membri Console.WriteLine(S.Profile) 'Altra cosa interessante: dato che Student derivata da 'Person ed espone tutti i membri di Person, pi altri, 'non sbagliato assegnare un oggetto Student a una 'variabile Person P = S Console.WriteLine(P.CompleteName) Console.ReadKey() End Sub Module

Pinco Pallino Tizio Caio Tizio Caio, nato il 23/5/1992 frequenta l'anno 2 alla scuola Liceo Classico Ugo Foscolo 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. 2. 3. 4. 5. 6. 7. 8. Class [Classe base] Overridable [Membro] End Class Class [Classe derivata] Inherits [Classe base] Overrides [Membro] 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 End Set 026. End Property 027. 028. Public ReadOnly Property BirthDay() As Date 029. Get 030. Return _BirthDay 031. End Get 032. End Property 033. 034. 'Questa propriet sar ridefinita nella classe Teacher 035. Public Overridable ReadOnly Property CompleteName() As String 036. Get 037. Return _FirstName & " " & _LastName 038. End Get 039. 040.

041. 042. 043. 044. 045. 046. 047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112.

End Property 'Costruttore che accetta tra parametri obbligatori Sub New(ByVal FirstName As String, ByVal LastName As String, _ ByVal BirthDay As Date) Me.FirstName = FirstName Me.LastName = LastName Me._BirthDay = BirthDay End Sub End Class Class Teacher Inherits Person Private _Subject As String Public Property Subject() As String Get Return _Subject End Get Set(ByVal Value As String) If Value <> "" Then _Subject = Value End If End Set End Property 'Ridefinisce la propriet LastName in modo da aggiungere 'anche il titolo di Professore al cognome Public Overrides Property LastName() As String Get Return "Prof. " & _LastName End Get Set(ByVal Value As String) 'Da notare l'uso di MyBase e LastName: in questo 'modo si richiama la vecchia versione della 'propriet LastName e se ne imposta il 'valore. Viene quindi richiamato il blocco Set 'vecchio: si risparmiano due righe di codice 'poich non si deve eseguire il controllo 'If su Value MyBase.LastName = Value End Set End Property 'Ridefinisce la propriet CompleteName in modo da 'aggiungere anche la materia insegnata e il titolo di 'Professore Public Overrides ReadOnly Property CompleteName() As String Get 'Anche qui viene richiamata la vecchia versione di 'CompleteName, che restituisce semplicemente il 'nome completo Return "Prof. " & MyBase.CompleteName & _ ", dottore in " & Subject End Get End Property Sub New(ByVal FirstName As String, ByVal LastName As String, _ ByVal BirthDay As Date, ByVal Subject As String) MyBase.New(FirstName, LastName, BirthDay) Me.Subject = Subject End Sub End Class Sub Main() Dim T As New Teacher("Mario", "Rossi", Date.Parse("01/01/1950"), _ "Letteratura italiana") 'Usiamo le nuove propriet, ridefinite nella classe 'derivata Console.WriteLine(T.LastName) '> "Prof. Rossi"

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. 02. 03. 04. 05. 06. 07. 08. 09. 10. Dim Ps(2) As Person Ps(0) = New Person("Luigi", "Ciferri", Date.Parse("7/7/1982")) Ps(1) = New Student("Mario", "Bianchi", Date.Parse("19/10/1991"), _ "Liceo Scientifico Tecnologico Cardano", 5) Ps(2) = New Teacher("Ubaldo", "Nicola", Date.Parse("11/2/1980"), "Filosofia") For Each P As Person In Ps Console.WriteLine(P.CompleteName) 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. 02. 03. 04. 05. 06. 07. 08. 09. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. Class A Public Overridable Sub ShowText() Console.WriteLine("A: Testo di prova") End Sub End Class Class B Inherits A 'Come si vede il metodo ha: '- lo stesso nome: ShowText '- lo stesso tipo: una procedura '- gli stessi parametri: senza parametri 'Qualunque tentativo di cambiare una di queste caratteristiche 'produrr un errore del compilatore, che comunica di non poter 'ridefinire il metodo perch non ne esistono di uguali nella 'classe base Public Overrides Sub ShowText() Console.WriteLine("B: Testo di prova") End Sub 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 End Class 04. 05. 06. Class Deriv 07. Inherits Base 08. Public Shadows Sub Control(ByVal Msg As String) Console.WriteLine("Control, seconda versione: " & Msg) 09. End Sub 10. End Class 11. 12. 13. Sub Main() Dim B As New Base 14. 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, 'mentre nella seconda una procedura pubblica 19. Console.WriteLine(B.Control) 20. D.Control("Ciao") 21. 22. Console.ReadKey() 23. End Sub 24. 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. 02. 03. 04. 05. 06. 07. 08. 09. Dim Dim ' D Dim ' S Dim ' N Dim Dim I D = S = N = K A As Int32 As Double 34.0 As String "34" As Single 34.0 As String As Date = = 34 = Convert.ToDouble(I) = Convert.ToString(D) = Convert.ToSingle(S) = "31/12/2008" 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. 02. 03. 04. 05. 06. 07. 08. 09. 10. Dim I As Int32 I = Int32.Parse("27") ' I = 27 I = Int32.Parse("78.000") ' Errore di conversione! I = Int32.Parse("123,67") ' 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. 02. 03. 04. 05. 06. 07. 08. 09. 10. Dim I As Int32 I = CType("27", Int32) ' I = 27 I = CType("78.000", Int32) ' I = 78000 I = CType("123,67", Int32) ' 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. 02. 03. 04. 05. 06. 07. 08. 09. Dim S As String = "56/0/1000" 'S contiene una data non valida Dim D As Date If Date.TryParse(S, D) Then Console.WriteLine(D.ToLongDateString()) Else Console.WriteLine("Data non valida!") 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. 2. 3. 4. A = 1 B = 3 Console.WriteLine("La somma di {0} e {1} {2}.", A, B, A + B) '> "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.

13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. End

'e usa la precedente variante del metodo per 'ottenere il risultato Return GetElapsed(New Date(Year, 1, 1)) End Function 'Come le due sopra, ma il parametro di tipo stringa 'e indica la data Private Function GetElapsed(ByVal D As String) As Single Return GetElapsed(Date.Parse(D)) End Function Sub Main() 'GetElapsed viene 'diversi, ma sono Dim El1 As Single Dim El2 As Single Dim El3 As Single Console.ReadKey() End Sub Module chiamata con tre tipi di parametri tutti leciti = GetElapsed(New Date(1987, 12, 4)) = GetElapsed(1879) = GetElapsed("12/12/1991")

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 Set(ByVal Value As String) 10. 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) If Value <> "" Then 22. _LastName = Value 23. 24. End If End Set 25. End Property 26. 27. Public ReadOnly Property BirthDay() As Date 28. Get 29. Return _BirthDay 30. End Get 31. End Property 32. 33. Public Overridable ReadOnly Property CompleteName() As String 34. Get 35. Return _FirstName & " " & _LastName 36. End Get 37. End Property 38. 39.

40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91. 92. 93. End

'ToString una funzione definita nella classe 'System.Object e poich ogni cosa in .NET 'deriva da questa classe, &egrae; sempre possibile 'ridefinire tramite polimorfismo il metodo ToString. 'In questo caso ne scriveremo non una, ma due versioni, 'quindi deve essere dichiarato sia Overrides, perch 'sovrascrive System.Object.ToString, sia Overloads, 'perch una versione alternativa di 'quella che andremo a scrivere tra poco Public Overloads Overrides Function ToString() As String Return CompleteName End Function 'Questa versione accetta un parametro stringa che assume 'la funzione di stringa di formato: il metodo restituir 'la frase immessa, sostituendo {F} con FirstName e {L} con 'LastName. In questa versione sufficiente 'Overloads, dato che non esiste un metodo ToString che 'accetti un parametro stringa in System.Object e perci 'non lo potremmo modificare Public Overloads Function ToString(ByVal FormatString As String) _ As String Dim Temp As String = FormatString 'Sostituisce {F} con FirstName Temp = Temp.Replace("{F}", _FirstName) 'Sostituisce {L} con LastName Temp = Temp.Replace("{L}", _LastName) Return Temp End Function Sub New(ByVal FirstName As String, ByVal LastName As String, _ ByVal BirthDay As Date) Me.FirstName = FirstName Me.LastName = LastName Me._BirthDay = BirthDay End Sub End Class Sub Main() Dim P As New Person("Mario", "Rossi", Date.Parse("17/07/67")) Console.WriteLine(P.ToString) '> Mario Rossi 'vbCrLf una costante che rappresenta il carattere '"a capo" Console.WriteLine(P.ToString("Nome: {F}" & vbCrLf & "Cognome: {L}")) '> Nome: Mario '> Cognome: Rossi Console.ReadKey() End Sub 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. Console.ReadKey() 41. End Sub 42. 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) Else 26. Console.WriteLine("Divisione impossibile!") 27. 28. End If 29. Console.ReadKey() 30. End Sub 31. 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. 02. 03. 04. 05. 06. 07. 08. 09. 10. 11. 12. 13. 14. Try A B C D E F Catch Ex As Exception G H Finally I L End Try

Le istr uzioni eseguite sar anno: 01. 02. 03. 04. 05. 06. 07. 08. 09. A B C 'Eccezione: salta nel blocco Catch G H 'Alla fine esegue comunque il Finally I 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.

17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. End

Return "La password inserita sbagliata!" End Get End Property 'Modifica il link di aiuto Public Overrides Property HelpLink() As String Get Return "http://totem.altervista.org" End Get Set(ByVal Value As String) MyBase.HelpLink = value End Set End Property 'Il resto dei membri di Exception sono molto importanti 'e vengono inizializzati con dati prelevati tramite 'Reflection (ultimo argomento di questa sezione), perci ' conveniente non modificare altro. Potete 'semmai aggiungere qualche membro End Class Sub Main() Dim Pass As String = "b7dha90" Dim NewPass As String Console.WriteLine("Inserire la password:") NewPass = Console.ReadLine If NewPass <> Pass Then 'Lancia l'eccezione usando la keyword Throw Throw New IncorrectPasswordException End If Catch IPE As IncorrectPasswordException 'Visualizza il messaggio Console.WriteLine(IPE.Message) 'E il link d'aiuto Console.WriteLine("Help: " & IPE.HelpLink) End Try Console.ReadKey() End Sub Module Try

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() Console.WriteLine("Un oggetto sta per essere creato.") 04. End Sub 05. 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 'di vedere cosa viene scritto a schermo 14. 15. System.Threading.Thread.CurrentThread.Sleep(4000) 16. End Sub 17. End Class 18. Sub Main() 19. Dim O As New Oggetto 20. Console.WriteLine("Oggetto = Nothing") 21. Console.WriteLine("L'applicazione sta per terminare.") 22. End Sub 23. 24. End Module L'output sar : 1. 2. 3. 4. Un oggetto sta per essere creato. Oggetto = Nothing L'applicazione sta per terminare. 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. 2. 3. 4. Un oggetto sta per essere creato. Oggetto = Nothing Un oggetto sta per essere distrutto. 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. 2. 3. 4. 5. 6. 7. 8. Using [Oggetto] 'Codice da eseguire End Using 'Che corrisponde a scrivere: 'Codice da eseguire [Oggetto].Dispose() [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) Writer = New IO.StreamWriter(FileName) 024. Opened = True 025. 026. Console.WriteLine("FileWriter sta per essere aperto.") 'Registra l'oggetto per eseguire Finalize: ora il file 027. ' aperto e pu quindi essere chiuso 028. GC.ReRegisterForFinalize(Me) 029. End Sub 030. 031. 'Scrive del testo nel file 032. Public Sub Write(ByVal Text As String) 033. If Opened Then 034. Writer.Write(Text) 035. End If 036. End Sub 037. 038. 'Una procedura analoga a Open aiuta a impostare meglio 039. 'l'oggetto e non fa altro che richiamare Dispose: 040. 'pi una questione di completezza 041. 042.

043. 044. 045. 046. 047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. End L'output:

Public Sub Close() Dispose() End Sub 'Questa versione in overload perch l'altra viene 'chiamata solo dall'utente ( Public), mentre questa 'implementa tutto il codice che necessario eseguire 'per rilasciare le risorse. 'Il parametro Disposing indica se l'oggetto sta per 'essere distrutto, quindi manualmente, o finalizzato, 'quindi nel processo di GC: nel secondo caso altri oggetti 'che questa classe utilizza potrebbero non esistere pi, 'perci si deve controllare se possibile 'invocarli correttamente Protected Overridable Overloads Sub Dispose(ByVal Disposing _ As Boolean) 'Esegue il codice solo se l'oggetto esiste ancora If Disposed Then 'Se distrutto, esce dalla procedura Exit Sub End If If Disposing Then 'Qui possiamo chiamare altri oggetti con la 'sicurezza che esistano ancora Console.WriteLine("FileWriter sta per essere distrutto.") Else Console.WriteLine("FileWriter sta per essere finalizzato.") End If 'Chiude il file Writer.Close() Disposed = True Opened = False End Sub Public Overloads Sub Dispose() Implements IDisposable.Dispose 'L'oggetto stato distrutto Dispose(True) 'Quindi non deve pi essere finalizzato GC.SuppressFinalize(Me) End Sub Protected Overrides Sub Finalize() 'Processo di finalizzazione: Dispose(False) End Sub End Class Sub Main() Dim F As New FileWriter 'Questo blocco mostra l'esecuzione di Dispose F.Open("C:\test.txt") F.Write("Ciao") F.Close() 'Questo mostra l'esecuzione di Finalize F = New FileWriter F.Open("C:\test2.txt") F = Nothing GC.Collect() GC.WaitForPendingFinalizers() Console.ReadKey() End Sub Module

1. FileWriter sta per essere creato. 2.

3. 4. 5. 6.

FileWriter FileWriter FileWriter FileWriter FileWriter

sta sta sta sta sta

per per per per per

essere essere essere essere essere

aperto. distrutto. creato. aperto. 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. Delegate Sub Display(ByVal Message As String) 04. 05. 06. 'Una procedura dimostrativa 07. Sub Write1(ByVal S As String) Console.WriteLine("1: " & S) 08. 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 Dim D As Display 19. 20. 21. 'Inizializa D con un nuovo oggetto delegate contenente 'un riferimento al metodo Console.WriteLine 22. D = New Display(AddressOf Console.WriteLine) 23. 24. 'Invoca il metodo referenziato da D: in questo caso 25. 'equivarrebbe a scrivere Console.WriteLine("Ciao") 26. D("Ciao") 27. 28. 'Reinizializza D, assegnandogli l'indirizzo di Write1 29. D = New Display(AddressOf Write1) 30. ' come chiamare Write1("Ciao") 31. D("Ciao") 32. 33. 'Modo alternativo per inizializzare un delegate: si omette 34. 'New e si usa solo AddressOf. Questo genera una conversione 35. 'implicita che d errore di cast nel caso in cui Write1 36. 'non sia compatibile con la signature del delegate 37. D = AddressOf Write2 38. D("Ciao") 39. 40. 'Notare che D pu contenere metodi di istanza 41. '(come Console.WriteLine) e metodi statici (come Write1 42. 'e Write2) 43. 44. Console.ReadKey() 45. End Sub 46. 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.

003. 004. 005. 006. 007. 008. 009. 010. 011. 012. 013. 014. 015. 016. 017. 018. 019. 020. 021. 022. 023. 024. 025. 026. 027. 028. 029. 030. 031. 032. 033. 034. 035. 036. 037. 038. 039. 040. 041. 042. 043. 044. 045. 046. 047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074.

'Tipo delegate che pu contenere riferimenti a funzioni Single 'che accettino un parametro di tipo array di Single Delegate Function ProcessData(ByVal Data() As Single) As Single 'Tipo delegate che pu contenere riferimenti a procedure 'che accettino due parametri, un array di Single e un Boolean Delegate Sub PrintData(ByVal Data() As Single, ByVal ReverseOrder As Boolean) 'Funzione che calcola la media di alcuni valori. Notare che 'l'unico parametro indefinito, in quanto 'dichiarato come ParamArray Function CalculateAverage(ByVal ParamArray Data() As Single) As Single Dim Total As Single = 0 For I As Int32 = 0 To Data.Length - 1 Total += Data(I) Next Return (Total / Data.Length) End Function 'Funzione che calcola la varianza di alcuni valori. Notare che 'anche in questo caso il parametro indefinito Function CalculateVariance(ByVal ParamArray Data() As Single) As Single Dim Average As Single = CalculateAverage(Data) Dim Result As Single = 0 For I As Int32 = 0 To Data.Length - 1 Result += (Data(I) - Average) ^ 2 Next Return (Result / Data.Length) End Function 'Procedura che stampa i valori di un array in ordine normale 'o inverso. Notare che il secondo parametro opzionale Sub PrintNormal(ByVal Data() As Single, _ Optional ByVal ReverseOrder As Boolean = False) If ReverseOrder Then For I As Int32 = Data.Length - 1 To 0 Step -1 Console.WriteLine(Data(I)) Next Else For I As Int32 = 0 To Data.Length - 1 Console.WriteLine(Data(I)) Next End If End Sub 'Procedura che stampa i valori di un array nella forma: '"I+1) Data(I)" 'Notare che anche in questo caso il secondo parametro ' opzionale Sub PrintIndexed(ByVal Data() As Single, _ Optional ByVal ReverseOrder As Boolean = False) If ReverseOrder Then For I As Int32 = Data.Length - 1 To 0 Step -1 Console.WriteLine("{0}) {1}", Data.Length - I, Data(I)) Next Else For I As Int32 = 0 To Data.Length - 1 Console.WriteLine("{0}) {1}", (I + 1), Data(I)) Next End If End Sub Sub Main() Dim Process As ProcessData Dim Print As PrintData Dim Data() As Single Dim Len As Int32 Dim Cmd As Char

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 End Select 097. Console.WriteLine() 098. Console.WriteLine("Scegliere il metodo di stampa: ") 099. Console.WriteLine("s - Stampa i valori;") 100. Console.WriteLine("i - Stampa i valori con il numero ordinale a fianco.") 101. Cmd = Console.ReadKey().KeyChar 102. Select Case Cmd 103. Case "s" 104. Print = New PrintData(AddressOf PrintNormal) 105. Case "i" 106. Print = New PrintData(AddressOf PrintIndexed) 107. Case Else 108. Console.WriteLine("Comando non valido!") 109. Exit Sub 110. End Select 111. 112. Console.Clear() 113. 114. Console.WriteLine("Valori:") 115. 'Eccoci arrivati al punto. Come detto prima, i delegate 116. 'non possono definire una signature che comprenda parametri 117. 'opzionali o indefiniti, ma si 118. 'pu aggirare questa limitazione semplicemente dichiarando 119. 'un array di valori al posto del ParamArray (in quanto si 120. 'tratta comunque di due vettori) e lo stesso parametro 121. 'non opzionale al posto del parametro opzionale. 122. 'L'inconveniente, in questo ultimo caso, che il 123. 'parametro, pur essendo opzionale va sempre specificato 124. 'quando il metodo viene richiamato attraverso un oggetto 125. 'delegate. Questo escamotage permette di aumentare la 126. 'portata dei delegate, includendo anche metodi che 127. 'possono essere stati scritti tempo prima in un'altra 128. 'parte inaccessibile del codice: cos 129. 'non necessario riscriverli! 130. Print(Data, False) 131. Console.WriteLine("Risultato:") 132. Console.WriteLine(Process(Data)) 133. 134. Console.ReadKey() 135. End Sub 136. 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. 'Questo delegate referenzia una funzione che accetta un 05. 'parametro stringa e restituisce un valore booleano 06. 07. Delegate Function IsMyFile(ByVal FileName As String) As Boolean 08. 'Funzione 1, stampa il contenuto del file a schermo 09. Function PrintFile(ByVal FileName As String) As Boolean 10. 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 'IO.File.ReadAllText(F) restituisce il testo contenuto 14. 15. 'nel file F in una sola operazione 16. Console.WriteLine(IO.File.ReadAllText(FileName)) 17. Return True 18. End If Return False 19. End Function 20. 21. 'Funzione 2, copia il file sul desktop 22. Function CopyFile(ByVal FileName As String) As Boolean 23. If IO.Path.GetFileName(FileName) = File Then 24. 'IO.File.Copy(S, D) copia il file S nel file D: 25. 'se D non esiste viene creato, se esiste viene 26. 'sovrascritto 27. IO.File.Copy(FileName, _ 28. My.Computer.FileSystem.SpecialDirectories.Desktop & _ 29. "\" & File) 30. Return True 31. End If 32. Return False 33. End Function 34. 35. 'Procedura ricorsiva che cerca il file 36. Function SearchFile(ByVal Dir As String, ByVal IsOK As IsMyFile) _ 37. As Boolean 38. 'Ottiene tutte le sottodirectory 39. Dim Dirs() As String = IO.Directory.GetDirectories(Dir) 40. 'Ottiene tutti i files 41. Dim Files() As String = IO.Directory.GetFiles(Dir) 42. 43. 'Analizza ogni file per vedere se quello cercato 44. For Each F As String In Files 45. ' il file cercato, basta cercare 46. If IsOK(F) Then 47. 'Termina la funzione e restituisce Vero, cosicch 48. 'anche nel for sulle cartelle si termini 49. 'la ricerca 50. Return True 51. End If 52. Next 53. 54. 'Analizza tutte le sottocartelle 55. For Each D As String In Dirs 56. If SearchFile(D, IsOK) Then 57. 'Termina ricorsivamente la ricerca 58. Return True 59. End If 60. Next 61. End Function 62. 63. Sub Main() 64. Dim Dir As String 65. 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() Dim Dir As String 04. Dim D As IsMyFile 05. 06. Console.WriteLine("Inserire il nome file da cercare:") 07. File = Console.ReadLine 08. 09. Console.WriteLine("Inserire la cartella in cui cercare:") 10. Dir = Console.ReadLine 11. 12. 13. 'Crea un delegate multicast, unendo PrintFile e CopyFile. 'Da notare che in questa espressione necessario usare 14. 15. 'delle vere e proprie variabili delegate, poich 'l'operatore AddressOf da solo non valido in questo caso 16. 17. D = System.Delegate.Combine(New IsMyFile(AddressOf PrintFile), _ 18. New IsMyFile(AddressOf CopyFile)) 'Per la cronaca, Combine un metodo factory 19. 20. 'Ora il file trovato viene sia visualizzato che copiato 21. 'sul desktop 22. SearchFile(Dir, D) 23. 24. 'Se si vuole rimuovere uno o pi riferimenti a metodi del 25. 'delegate multicast si deve utilizzare il metodo statico Remove: 26. D = System.Delegate.Remove(D, New IsMyFile(AddressOf CopyFile)) 27. 'Ora D far visualizzare solamente il file trovato 28. 29. Console.ReadKey() 30. End Sub 31. 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. 002. 003. 004. 005. 006. 007. 008. 009. 010. 011. 012. 013. 014. 015. 016. 017. 018. 019. 020. 021. 022. 023. 024. 025. 026. 027. 028. 029. 030. 031. 032. 033. 034. 035. 036. 037. 038. 039. 040. 041. 042. 043. 044. 045. 046. 047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 'Questo esempio si basa completamente sulla manipolazione 'di file e cartelle, argomento non ancora affrontato. Se volete, 'potete dare uno sguardo ai capitoli relativi nelle parti 'successive della guida, oppure potete anche limitarvi a leggere 'i commenti, che spiegano tutto ci che accade. Module Module1 'In questo esempio eseguiremo delle operazioni su file con i delegate. 'Nel men sar possibile scegliere quali operazioni 'eseguire (una o tutte insieme) e sotto quali condizioni modificare 'un file. 'Il delegate FileFilter rappresenta una funzione che restituisce 'True se la condizione soddisfatta. Le condizioni 'sono racchiuse in un delegate multicast che contiene pi 'funzioni di questo tipo Delegate Function FileFilter(ByVal FileName As String) As Boolean 'Il prossimo delegate rappresenta un'operazione su un file Delegate Sub MassFileOperation(ByVal FileName As String) 'AskForData un delegate del tipo pi semplice. 'Servir per reperire le informazioni necessarie ad 'eseguire le operazioni (ad esempio, se si sceglie di copiare 'tutti i file di una cartella, si dovr anche scegliere 'dove copiare questi file). Delegate Sub AskForData() 'Queste variabili globali rappresentano le informazioni necesarie 'per lo svolgimento delle operazioni o la verifica delle condizioni. 'Stringa di formato per rinominare i file Dim RenameFormat As String 'Posizione di un file nella cartella Dim FileIndex As Int32 'Directory in cui copiare i file Dim CopyDirectory As String 'File in cui scrivere. Il tipo StreamWriter permette di scrivere 'facilmente stringhe su un file usando WriteLine come in Console Dim LogFile As IO.StreamWriter 'Limitazioni sulla data di creazione del file Dim CreationDateFrom, CreationDateTo As Date 'Limitazioni sulla data di ultimo accesso al file Dim LastAccessDateFrom, LastAccessDateTo As Date 'Limitazioni sulla dimensione Dim SizeFrom, SizeTo As Int64 'Rinomina un file Sub Rename(ByVal Path As String) 'Ne prende il nome semplice, senza estensione Dim Name As String = IO.Path.GetFileNameWithoutExtension(Path) 'Apre un oggetto contenente le informazioni sul file 'di percorso Path Dim Info As New IO.FileInfo(Path) 'Formatta il nome secondo la stringa di formato RenameFormat Name = String.Format(RenameFormat, _ Name, FileIndex, Info.Length, Info.LastAccessTime, Info.CreationTime) 'E aggiunge ancora l'estensione al nome modificato Name &= IO.Path.GetExtension(Path) 'Copia il vecchio file nella stessa cartella, ma con il nuovo nome IO.File.Copy(Path, IO.Path.GetDirectoryName(Path) & "\" & Name) 'Elimina il vecchio file IO.File.Delete(Path) 'Aumenta l'indice di uno FileIndex += 1 End Sub 'Funzione che richiede i dati necessari per far funzionare 'il metodo Rename Sub InputRenameFormat()

070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121. 122. 123. 124. 125. 126. 127. 128. 129. 130. 131. 132. 133. 134. 135. 136. 137. 138. 139.

Console.WriteLine("Immettere una stringa di formato valida per rinominare i file.") Console.WriteLine("I parametri sono:") Console.WriteLine("0 = Nome originale del file;") Console.WriteLine("1 = Posizione del file nella cartella, in base 0;") Console.WriteLine("2 = Dimensione del file, in bytes;") Console.WriteLine("3 = Data dell'ultimo accesso;") Console.WriteLine("4 = Data di creazione.") RenameFormat = Console.ReadLine End Sub 'Elimina un file di percorso Path Sub Delete(ByVal Path As String) IO.File.Delete(Path) End Sub 'Copia il file da Path alla nuova cartella Sub Copy(ByVal Path As String) IO.File.Copy(Path, CopyDirectory & "\" & IO.Path.GetFileName(Path)) End Sub 'Richiede una cartella valida in cui copiare i file. Se non esiste, la crea Sub InputCopyDirectory() Console.WriteLine("Inserire una cartella valida in cui copiare i file:") CopyDirectory = Console.ReadLine If Not IO.Directory.Exists(CopyDirectory) Then IO.Directory.CreateDirectory(CopyDirectory) End If End Sub 'Scrive il nome del file sul file aperto Sub Archive(ByVal Path As String) LogFile.WriteLine(IO.Path.GetFileName(Path)) End Sub 'Chiede il nome di un file su cui scrivere tutte le informazioni Sub InputLogFile() Console.WriteLine("Inserire il percorso del file su cui scrivere:") LogFile = New IO.StreamWriter(Console.ReadLine) End Sub 'Verifica che la data di creazione del file cada tra i limiti fissati Function IsCreationDateValid(ByVal Path As String) As Boolean Dim Info As New IO.FileInfo(Path) Return (Info.CreationTime >= CreationDateFrom) And (Info.CreationTime >= CreationDateTo) End Function 'Richiede di immettere una limitazione temporale per considerare 'solo certi file Sub InputCreationDates() Console.WriteLine("Verranno considerati solo i file con data di creazione:") Console.Write("Da: ") CreationDateFrom = Date.Parse(Console.ReadLine) Console.Write("A: ") CreationDateTo = Date.Parse(Console.ReadLine) End Sub 'Verifica che la data di ultimo accesso al file cada tra i limiti fissati Function IsLastAccessDateValid(ByVal Path As String) As Boolean Dim Info As New IO.FileInfo(Path) Return (Info.LastAccessTime >= LastAccessDateFrom) And (Info.LastAccessTime >= LastAccessDateTo) End Function 'Richiede di immettere una limitazione temporale per considerare 'solo certi file Sub InputLastAccessDates() Console.WriteLine("Verranno considerati solo i file con data di creazione:") Console.Write("Da: ") LastAccessDateFrom = Date.Parse(Console.ReadLine) Console.Write("A: ")

140. 141. 142. 143. 144. 145. 146. 147. 148. 149. 150. 151. 152. 153. 154. 155. 156. 157. 158. 159. 160. 161. 162. 163. 164. 165. 166. 167. 168. 169. 170. 171. 172. 173. 174. 175. 176. 177. 178. 179. 180. 181. 182. 183. 184. 185. 186. 187. 188. 189. 190. 191. 192. 193. 194. 195. 196. 197. 198. 199. 200. 201. 202. 203. 204. 205. 206. 207. 208. 209. 210. 211.

LastAccessDateTo = Date.Parse(Console.ReadLine) End Sub 'Verifica che la dimensione del file sia coerente coi limiti fissati Function IsSizeValid(ByVal Path As String) As Boolean Dim Info As New IO.FileInfo(Path) Return (Info.Length >= SizeFrom) And (Info.Length >= SizeTo) End Function 'Richiede di specificare dei limiti dimensionali per i file Sub InputSizeLimit() Console.WriteLine("Verranno considerati solo i file con dimensione compresa:") Console.Write("Tra (bytes):") SizeFrom = Console.ReadLine Console.Write("E (bytes):") SizeTo = Console.ReadLine End Sub 'Classe che rappresenta un'operazione eseguibile su file Class Operation Private _Description As String Private _Execute As MassFileOperation Private _RequireData As AskForData Private _Enabled As Boolean 'Descrizione Public Property Description() As String Get Return _Description End Get Set(ByVal value As String) _Description = value End Set End Property 'Variabile che contiene l'oggetto delegate associato 'a questa operazione, ossia un riferimento a una delle Sub 'definite poco sopra Public Property Execute() As MassFileOperation Get Return _Execute End Get Set(ByVal value As MassFileOperation) _Execute = value End Set End Property 'Variabile che contiene l'oggetto delegate che serve 'per reperire informazioni necessarie ad eseguire 'l'operazione, ossia un riferimento a una delle sub 'di Input definite poco sopra. E' Nothing quando 'non serve nessun dato ausiliario (come nel caso 'di Delete) Public Property RequireData() As AskForData Get Return _RequireData End Get Set(ByVal value As AskForData) _RequireData = value End Set End Property 'Determina se l'operazione va eseguita oppure no Public Property Enabled() As Boolean Get Return _Enabled End Get Set(ByVal value As Boolean) _Enabled = value End Set End Property

212. 213. 214. 215. 216. 217. 218. 219. 220. 221. 222. 223. 224. 225. 226. 227. 228. 229. 230. 231. 232. 233. 234. 235. 236. 237. 238. 239. 240. 241. 242. 243. 244. 245. 246. 247. 248. 249. 250. 251. 252. 253. 254. 255. 256. 257. 258. 259. 260. 261. 262. 263. 264. 265. 266. 267. 268. 269. 270. 271. 272. 273. 274. 275. 276. 277. 278. 279. 280. 281. 282. 283.

Sub New(ByVal Description As String, _ ByVal ExecuteMethod As MassFileOperation, _ ByVal RequireDataMethod As AskForData) Me.Description = Description Me.Execute = ExecuteMethod Me.RequireData = RequireDataMethod Me.Enabled = False End Sub End Class 'Classe che rappresenta una condizione a cui sottoporre 'i file nella cartella: verranno elaborati solo quelli che 'soddisfano tutte le condizioni Class Condition Private _Description As String Private _Verify As FileFilter Private _RequireData As AskForData Private _Enabled As Boolean Public Property Description() As String Get Return _Description End Get Set(ByVal value As String) _Description = value End Set End Property 'Contiene un oggetto delegate associato a una delle 'precedenti funzioni Public Property Verify() As FileFilter Get Return _Verify End Get Set(ByVal value As FileFilter) _Verify = value End Set End Property Public Property RequireData() As AskForData Get Return _RequireData End Get Set(ByVal value As AskForData) _RequireData = value End Set End Property Public Property Enabled() As Boolean Get Return _Enabled End Get Set(ByVal value As Boolean) _Enabled = value End Set End Property Sub New(ByVal Description As String, _ ByVal VerifyMethod As FileFilter, _ ByVal RequireDataMethod As AskForData) Me.Description = Description Me.Verify = VerifyMethod Me.RequireData = RequireDataMethod End Sub End Class Sub Main() 'Contiene tutte le operazioni da eseguire: sar, quindi, un 'delegate multicast Dim DoOperations As MassFileOperation 'Contiene tutte le condizioni da verificare Dim VerifyConditions As FileFilter

284. 285. 286. 287. 288. 289. 290. 291. 292. 293. 294. 295. 296. 297. 298. 299. 300. 301. 302. 303. 304. 305. 306. 307. 308. 309. 310. 311. 312. 313. 314. 315. 316. 317. 318. 319. 320. 321. 322. 323. 324. 325. 326. 327. 328. 329. 330. 331. 332. 333. 334. 335. 336. 337. 338. 339. 340. 341. 342. 343. 344. 345. 346. 347. 348. 349. 350.

'Indica la cartella di cui analizzare i file Dim Folder As String 'Hashtable di caratteri-Operation o carattri-Condition. Il 'carattere indica quale tasto necessario 'premere per attivare/disattivare l'operazione/condizione Dim Operations As New Hashtable Dim Conditions As New Hashtable Dim Cmd As Char 'Aggiunge le operazioni esistenti. La 'c' messa dopo la stringa 'indica che la costante digitata un carattere e non una 'stringa. Il sistema non riesce a distinguere tra stringhe di lunghezza 1 e caratteri, al contrario di come accade in C With Operations .Add("r"c, New Operation("Rinomina tutti i file nella cartella;", _ New MassFileOperation(AddressOf Rename), _ New AskForData(AddressOf InputRenameFormat))) .Add("c"c, New Operation("Copia tutti i file nella cartella in un'altra cartella;", _ New MassFileOperation(AddressOf Copy), _ New AskForData(AddressOf InputCopyDirectory))) .Add("a"c, New Operation("Scrive il nome di tutti i file nella cartella su un file;", _ New MassFileOperation(AddressOf Archive), _ New AskForData(AddressOf InputLogFile))) .Add("d"c, New Operation("Cancella tutti i file nella cartella;", _ New MassFileOperation(AddressOf Delete), _ Nothing)) End With 'Aggiunge le condizioni esistenti With Conditions .Add("r"c, New Condition("Seleziona i file da elaborare in base alla data di creazione;", _ New FileFilter(AddressOf IsCreationDateValid), _ New AskForData(AddressOf InputCreationDates))) .Add("l"c, New Condition("Seleziona i file da elaborare in base all'ultimo accesso;", _ New FileFilter(AddressOf IsLastAccessDateValid), _ New AskForData(AddressOf InputLastAccessDates))) .Add("s"c, New Condition("Seleziona i file da elaborare in base alla dimensione;", _ New FileFilter(AddressOf IsSizeValid), _ New AskForData(AddressOf InputSizeLimit))) End With Console.WriteLine("Modifica in massa di file ---") Console.WriteLine() Do Console.WriteLine("Immetti il percorso della cartella su cui operare:") Folder = Console.ReadLine Loop Until IO.Directory.Exists(Folder) Do Console.Clear() Console.WriteLine("Premere la lettera corrispondente per selezionare la voce.") Console.WriteLine("Premere 'e' per procedere.") Console.WriteLine() For Each Key As Char In Operations.Keys 'Disegna sullo schermo una casella di spunta, piena: ' [X] 'se l'operazione attivata, altrimenti vuota: ' [ ] Console.Write("[") If Operations(Key).Enabled = True Then Console.Write("X") Else Console.Write(" ") End If Console.Write("] ") 'Scrive quindi il carattere da premere e vi associa la descrizione

351. 352. 353. 354. 355. 356. 357. 358. 359. 360. 361. 362. 363. 364. 365. 366. 367. 368. 369. 370. 371. 372. 373. 374. 375. 376. 377. 378. 379. 380. 381. 382. 383. 384. 385. 386. 387. 388. 389. 390. 391. 392. 393. 394. 395. 396. 397. 398. 399. 400. 401. 402. 403. 404. 405. 406. 407. 408. 409. 410. 411. 412. 413. 414. 415. 416. 417. 418. 419. 420. 421. 422.

Next Cmd = Console.ReadKey().KeyChar If Operations.ContainsKey(Cmd) Then Operations(Cmd).Enabled = Not Operations(Cmd).Enabled End If Loop Until Cmd = "e"c Console.Clear() Console.WriteLine("Premere la lettera corrispondente per selezionare la voce.") Console.WriteLine("Premere 'e' per procedere.") Console.WriteLine() For Each Key As Char In Conditions.Keys Console.Write("[") If Conditions(Key).Enabled = True Then Console.Write("X") Else Console.Write(" ") End If Console.Write("] ") Console.Write(Key) Console.Write(" - ") Console.WriteLine(Conditions(Key).Description) Next Cmd = Console.ReadKey().KeyChar If Conditions.ContainsKey(Cmd) Then Conditions(Cmd).Enabled = Not Conditions(Cmd).Enabled End If Loop Until Cmd = "e"c Console.Clear() Console.WriteLine("Acquisizione informazioni") Console.WriteLine() 'Cicla su tutte le operazioni presenti nell'Hashtable. For Each Op As Operation In Operations.Values 'Se l'operazione attivata... If (Op.Enabled) Then 'Se richiede dati ausiliari, invoca il delegate memorizzato 'nella propriet RequireData. Invoke un metodo 'di istanza che invoca i metodi contenuti nel delegate. 'Si pu anche scrivere: ' Op.RequireData()() 'Dove la prima coppia di parentesi indica che la propriet 'non indicizzata e la seconda, in questo caso, specifica 'che il metodo sotteso dal delegate non richiede parametri. ' pi comprensibile la prima forma If Op.RequireData IsNot Nothing Then Op.RequireData.Invoke() End If 'Se DoOperations non contiene ancora nulla, vi inserisce Op.Execute If DoOperations Is Nothing Then DoOperations = Op.Execute Else 'Altrimenti, combina gli oggetti delegate gi memorizzati 'con il nuovo DoOperations = System.Delegate.Combine(DoOperations, Op.Execute) End If End If Next For Each C As Condition In Conditions.Values If C.Enabled Then If C.RequireData IsNot Nothing Then C.RequireData.Invoke() End If If VerifyConditions Is Nothing Then VerifyConditions = C.Verify Else Do

Console.Write(Key) Console.Write(" - ") Console.WriteLine(Operations(Key).Description)

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 'Se le condizioni sono verificate, esegue le operazioni 447. If Ok Then 448. Try 449. DoOperations(File) 450. Catch Ex As Exception 451. Console.WriteLine("Impossibile eseguire l'operazione: " & Ex.Message) 452. End Try 453. End If 454. Next 455. 'Chiude il file di log se era aperto 456. If LogFile IsNot Nothing Then 457. LogFile.Close() 458. End If 459. 460. Console.WriteLine("Operazioni eseguite con successo!") 461. Console.ReadKey() 462. End Sub 463. 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. 'Dato che di equazioni ce ne possono essere molte tipologie 004. 'differenti, non ha senso rendere questa classe istanziabile. 005. 006. 'Provando a scrivere qualcosa come: 007. ' Dim Eq As New EquationSolver() 008. 'Vi verr comunicato un errore, in quanto le classi 'astratte sono per loro natura non istanziabili 009. MustInherit Class EquationSolver 010. 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. 'La prossima classe rappresenta un risolutore di equazioni 020. 'polinomiali. Dato che la tipologia ben definita, 021. 'avremmo potuto anche <i>non</i> rendere astratta la classe 022. 'e, nella funzione Solve, utilizzare un Select Case per 023. 'controllare il grado dell'equazione. Ad ogni modo, 024. 'utile vedere come si comporta l'erediteriet attraverso 025. 'pi classi astratte. 026. 'Inoltre, ci ritorner molto utile in seguito disporre 027. 'di questa classe astratta intermedia 028. MustInherit Class PolynomialEquationSolver 029. Inherits EquationSolver 030. 031. Private _Coefficients() As Single 032. 033. 'Array di Single che contiene i coefficienti dei 034. 'termini di i-esimo grado all'interno dell'equazione. 035. 'L'elemento 0 dell'array indica il coefficiente del 036. 'termine a grado massimo. 037. Public Property Coefficients() As Single() 038. Get 039. Return _Coefficients 040. End Get 041. Set(ByVal value As Single()) 042. _Coefficients = value 043. End Set 044. End Property 045. 046. 'Ecco quello a cui volevo arrivare. Se un metodo astratto 047. 'lo si vuole mantenere tale anche nella classe derivata, 048. 'non basta scrivere: 049. ' MustOverride Function Solve() As Single() 050. 'Perc in questo caso verrebbe interpretato come 051. 'un membro che non c'entra niente con MyBase.Solve, 052. 'e si genererebbe un errore in quanto stiamo tentando 053. 'di dichiarare un nuovo membro con lo stesso nome 054. 'di un membro della classe base. 055. 'Per questo motivo, dobbiamo comunque usare il polimorfismo 056. 'come se si trattasse di un normale metodo e dichiararlo 057. 'Overrides. In aggiunta a questo, deve anche essere 058. 'astratto, e perci aggiungiamo MustOverride: 059. Public MustOverride Overrides Function Solve() As Single() 060. 061. 'Anche in questo caso usiamo il polimorfismo, ma ci riferiamo 062. 'alla semplice funzione ToString, derivata dalla classe base 063. 'di tutte le entit esistenti, System.Object. 064. 'Questa si limita a restituire una stringa che rappresenta 065. 'l'equazione a partire dai suoi coefficienti. Ad esempio: 066. ' 3x^2 + 2x^1 + 4x^0 = 0 067. 'Potete modificare il codice per eliminare le forme ridondanti 068. 069. 'x^1 e x^0. 070. Public Overrides Function ToString() As String 071.

072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121. 122. 123. 124. 125. 126. 127. 128. 129. 130. 131. 132. 133. 134. 135. 136. 137. 138. 139. 140. 141. 142. 143.

Dim Result As String = "" For I As Int16 = 0 To Me.Coefficients.Length - 1 If I > 0 Then Result &= " + " End If Result &= String.Format("{0}x^{1}", _ Me.Coefficients(I), Me.Coefficients.Length - 1 - I) Next Result &= " = 0" Return Result End Function End Class 'Rappresenta un risolutore di equazioni non polinomiali. 'La classe non astratta, ma non presenta alcun codice. 'Per risolvere questo tipo di equazioni, necessario 'sapere qualche cosa in pi rispetto al punto in cui siamo 'arrivati, perci mi limiter a lasciare in bianco Class NonPolynomialEquationSolver Inherits EquationSolver Public Overrides Function Solve() As Single() Return Nothing End Function End Class 'Rappresenta un risolutore di equazioni di primo grado. Eredita 'da PolynomialEquationSolver poich, ovviamente, si 'tratta di equazioni polinomiali. In pi, definisce 'le propriet a e b che sono utili per inserire i 'coefficienti. Infatti, l'equazione standard : ' ax + b = 0 Class LinearEquationSolver Inherits PolynomialEquationSolver Public Property a() As Single Get Return Me.Coefficients(0) End Get Set(ByVal value As Single) Me.Coefficients(0) = value End Set End Property Public Property b() As Single Get Return Me.Coefficients(1) End Get Set(ByVal value As Single) Me.Coefficients(1) = value End Set End Property 'Sappiamo gi quanti sono i coefficienti, dato 'che si tratta di equazioni lineari, quindi ridimensioniamo 'l'array il prima possibile. Sub New() ReDim Me.Coefficients(1) End Sub 'Funzione Overrides che sovrascrive il metodo astratto della 'classe base. Avrete notato che quando scrivete: ' Inherits PolynomialEquationSolver 'e premete invio, questa funzione viene aggiunta automaticamente 'al codice. Questa un'utile feature dell'ambiente 'di sviluppo Public Overrides Function Solve() As Single() If a <> 0 Then Return New Single() {-b / a}

144. 145. 146. 147. 148. 149. 150. 151. 152. 153. 154. 155. 156. 157. 158. 159. 160. 161. 162. 163. 164. 165. 166. 167. 168. 169. 170. 171. 172. 173. 174. 175. 176. 177. 178. 179. 180. 181. 182. 183. 184. 185. 186. 187. 188. 189. 190. 191. 192. 193. 194. 195. 196. 197. 198. 199. 200. 201. 202. 203. 204. 205. 206. 207. 208. 209. 210. 211. 212. 213. 214. 215.

Return Nothing End If End Function End Class 'Risolutore di equazioni di secondo grado: ' ax<sup>2</sup> + bx + c = 0 Class QuadraticEquationSolver Inherits LinearEquationSolver Public Property c() As Single Get Return Me.Coefficients(2) End Get Set(ByVal value As Single) Me.Coefficients(2) = value End Set End Property Sub New() ReDim Me.Coefficients(2) End Sub Public Overrides Function Solve() As Single() If b ^ 2 - 4 * a * c >= 0 Then Return New Single() { _ (-b - Math.Sqrt(b ^ 2 - 4 * a * c)) / 2, _ (-b + Math.Sqrt(b ^ 2 - 4 * a * c)) / 2} Else Return Nothing End If End Function End Class 'Risolutore di equazioni di grado superiore al secondo. So 'che avrei potuto inserire anche una classe relativa 'alle cubiche, ma dato che si tratta di un esempio, vediamo 'di accorciare il codice... 'Comunque, dato che non esiste formula risolutiva per 'le equazioni di grado superiore al quarto (e gi, 'ci mancava un'altra classe!), usiamo in questo caso 'un semplice ed intuitivo metodo di approssimazione degli 'zeri, il metodo dicotomico o di bisezione (che vi pu 'essere utile per risolvere un esercizio dell'eserciziario) Class HighDegreeEquationSolver Inherits PolynomialEquationSolver Private _Epsilon As Single Private _IntervalLowerBound, _IntervalUpperBound As Single 'Errore desiderato: l'algoritmo si fermer una volta 'raggiunta una precisione inferiore a Epsilon Public Property Epsilon() As Single Get Return _Epsilon End Get Set(ByVal value As Single) _Epsilon = value End Set End Property 'Limite inferiore dell'intervallo in cui cercare la soluzione Public Property IntervalLowerBound() As Single Get Return _IntervalLowerBound End Get Set(ByVal value As Single) _IntervalLowerBound = value End Set End Property

Else

216. 217. 218. 219. 220. 221. 222. 223. 224. 225. 226. 227. 228. 229. 230. 231. 232. 233. 234. 235. 236. 237. 238. 239. 240. 241. 242. 243. 244. 245. 246. 247. 248. 249. 250. 251. 252. 253. 254. 255. 256. 257. 258. 259. 260. 261. 262. 263. 264. 265. 266. 267. 268. 269. 270. 271. 272. 273. 274. 275. 276. 277. 278. 279. 280. 281. 282. 283. 284. 285. 286. 287.

'Limite superiore dell'intervallo in cui cercare la soluzione Public Property IntervalUpperBound() As Single Get Return _IntervalUpperBound End Get Set(ByVal value As Single) _IntervalUpperBound = value End Set End Property 'Valuta la funzione polinomiale. Dati i coefficienti immessi, 'noi disponiamo del polinomio p(x), quindi possiamo calcolare 'i valori che esso assume per ogni x Private Function EvaluateFunction(ByVal x As Single) As Single Dim Result As Single = 0 For I As Int16 = 0 To Me.Coefficients.Length - 1 Result += Me.Coefficients(I) * x ^ (Me.Coefficients.Length - 1 - I) Next Return Result End Function Public Overrides Function Solve() As Single() Dim a, b, c As Single Dim fa, fb, fc As Single Dim Interval As Single = 100 Dim I As Int16 = 0 Dim Result As Single a = IntervalLowerBound b = IntervalUpperBound 'Non esiste uno zero tra a e b se f(a) e f(b) hanno 'lo stesso segno If EvaluateFunction(a) * EvaluateFunction(b) > 0 Then Return Nothing End If Do 'c il punto medio tra a e b c = (a + b) / 2 'Calcola f(a), f(b) ed f(c) fa = EvaluateFunction(a) fb = EvaluateFunction(b) fc = EvaluateFunction(c) 'Se uno tra f(a), f(b) e f(c) vale zero, allora abbiamo 'trovato una soluzione perfetta, senza errori, ed 'usciamo direttamente dal ciclo If fa = 0 Then c = a Exit Do End If If fb = 0 Then c = b Exit Do End If If fc = 0 Then Exit Do End If 'Altrimenti, controlliamo quale coppia di valori scelti 'tra f(a), f(b) ed f(c) ha segni discorsi: lo zero si trover 'tra le ascisse di questi If fa * fc < 0 Then b = c Else a = c End If Loop Until Math.Abs(a - b) < Me.Epsilon

288. 289. 290. 291. 292. 293. 294. 295. 296. 297. 298. 299. 300. 301. 302. 303. 304. 305. 306. 307. 308. 309. 310. 311. 312. 313. 314. 315. 316. 317. 318. 319. 320. 321. 322. 323. 324. 325. 326. 327. 328. 329. 330. 331. 332. 333. 334. 335. 336. 337. 338. 339. 340. 341. 342. 343. 344. 345. 346. 347. 348. 349. 350. 351. 352. 353. 354. 355. 356. 357. 358.

'Cicla finch l'ampiezza dell'intervallo non 'sufficientemente piccola, quindi assume come zero pi 'probabile il punto medio tra a e b: Result = c Return New Single() {Result} End Function End Class Sub Main() 'Contiene un generico risolutore di equazioni. Non sappiamo ancora 'quale tipologia di equazione dovremo risolvere, ma sappiamo per 'certo che lo dovremo fare, ed EquationSolver la classe 'base di tutti i risolutori che espone il metodo Solve. Dim Eq As EquationSolver Dim x() As Single Dim Cmd As Char Console.WriteLine("Scegli una tipologia di equazione: ") Console.WriteLine(" l - lineare;") Console.WriteLine(" q - quadratica;") Console.WriteLine(" h - di grado superiore al secondo;") Console.WriteLine(" e - non polinomiale;") Cmd = Console.ReadKey().KeyChar Console.Clear() If Cmd <> "e" Then 'Ancora, sappiamo che si tratta di un'equazione polinomiale 'ma non di quale grado Dim Poly As PolynomialEquationSolver 'Ottiene i dati relativi a ciascuna equazione Select Case Cmd Case "l" Dim Linear As New LinearEquationSolver() Poly = Linear Case "q" Dim Quadratic As New QuadraticEquationSolver() Poly = Quadratic Case "h" Dim High As New HighDegreeEquationSolver() Dim CoefNumber As Int16 Console.WriteLine("Inserire il numero di coefficienti: ") CoefNumber = Console.ReadLine ReDim High.Coefficients(CoefNumber - 1) Console.WriteLine("Inserire i limti dell'intervallo in cui cercare gli zeri:") High.IntervalLowerBound = Console.ReadLine High.IntervalUpperBound = Console.ReadLine Console.WriteLine("Inserire la precisione (epsilon):") High.Epsilon = Console.ReadLine Poly = High End Select 'A questo punto la variabile Poly contiene sicuramente un oggetto '(LinearEquationSolver, QuadraticEquationSolver oppure 'HighDegreeEquationSolver), anche se non sappiamo quale. Tuttavia, 'tutti questi sono pur sempre polinomiali e perci tutti 'hanno bisogno di sapere i coefficienti del polinomio. 'Ecco che allora possiamo usare Poly con sicurezza perc 'sicuramente contiene un oggetto e la propriet Coefficients ' stata definita proprio nella classe PolynomialEquationSolver. '<b>N.B.: ricordate tutto quello che abbiamo detto sull'assegnamento ' di un oggetto di classe derivata a uno di classe base!</b> Console.WriteLine("Inserire i coefficienti: ") For I As Int16 = 1 To Poly.Coefficients.Length - 1 Console.Write("a{0} = ", Poly.Coefficients.Length - I) Poly.Coefficients(I - 1) = Console.ReadLine Next 'Assegnamo Poly a Eq. Osservate che siamo andati via via dal

'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. 'Se ci pensate bene, vi verr pi spontaneo capire, 384. 385. 'poich noi, ora, stiamo guardando ATTRAVERSO il tipo 'EquationSolver un oggetto di altro tipo. come osservare 386. 'attraverso filtri via via sempre pi fitti (cfr 387. 'immagine seguente) 388. x = Eq.Solve() 389. 390. If x IsNot Nothing Then 391. Console.WriteLine("Soluzioni trovate: ") 392. For Each s As Single In x 393. Console.WriteLine(s) 394. Next 395. Else 396. Console.WriteLine("Nessuna soluzione") 397. End If 398. 399. Console.ReadKey() 400. End Sub 401. 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. 02. 03. 04. 05. 06. 07. 08. 09. 10. 11. 12. 13. 14. 15. 16. Class A Sub DoSomething() '... End Sub End Class Class B Inherits A 'Questa procedura sovrascrive la precedente versione 'di DoSomething dichiarata in A, ma preclude a tutte le 'classi derivate da B la possibilit di fare lo stesso NotOverridable Overrides Sub DoSomething() '... End Sub 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. 06. 07. 08. 09. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36.

'... End Sub End Class 'Nel file Codice2.vb Class A Sub Two() '... End Sub End Class 'Nel file Codice3.vb Class A Sub Three() '... End Sub End Class 'Tutte le classi A vengono compilate come un'unica classe 'perch una possiede la keyword Partial: Class A Sub One() '... End Sub Sub Two() '... End Sub Sub Three() '... End Sub 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. 02. 03. 04. 05. 06. 07. 08. 09. 10. 11. 12. 13. 14. 15. Dim Ar() As Int32 = {1, 2, 3, 4, 5, 6} Dim Al As New ArrayList For I As Int32 = 1 To 40 Al.Add(I) Next 'Stampa i valori di Ar: For Each K As Int32 In Ar Console.WriteLine(K) Next 'Stampa i valori di Al For Each K As Int32 In Al Console.WriteLine(K) 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. 02. 03. 04. 05. 06. 07. 08. 09. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 'Questa interfaccia dal nome improbabile indica che 'la classe che la implementa rappresenta qualcosa di '"identificabile" e per questo espone una propriet Integer Id 'e una funzione ToString. Id e ToString, infatti, sono gli 'elementi pi utili per identificare qualcosa, prima in 'base a un codice univoco e poi grazie ad una rappresentazione 'comprensibile dall'uomo Interface IIdentifiable ReadOnly Property Id() As Int32 Function ToString() As String End Interface 'La prossima interfaccia, invece, indica qualcosa di resettabile 'e obbliga le classi implementanti a esporre il metodo Reset 'e la propriet DefaultValue, che dovrebbe rappresentare 'il valore di default dell'oggetto. Dato che non sappiamo ora 'quali classi implementeranno questa interfaccia, dobbiamo 'per forza usare un tipo generico come Object per rappresentare 'un valore reference. Vedremo come aggirare questo ostacolo 'fra un po', con i Generics Interface IResettable Property DefaultValue() As Object Sub Reset() End Interface 'Come avete visto, i nomi di interfaccia iniziano per convenzione '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.

08. 09. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79.

'Rappresenta un pacco da spedire Class Pack 'Implementa l'interfaccia IIdentifiable, in quanto un pacco 'dovrebbe poter essere ben identificato Implements IIdentifiable 'Notate bene che l'interfaccia ci obbliga a definire una 'propriet, ma non ci obbliga a definire un campo 'ad essa associato Private _Id As Int32 Private _Destination As String Private _Dimensions(2) As Single 'La classe definisce una propriet id di tipo Integer 'e la associa all'omonima presente nell'interfaccia in 'questione. Il legame tra questa propriet Id e quella 'presenta nell'interfaccia dato solamente dalla 'clausola (si chiama cos in gergo) "Implements", 'la quale avvisa il sistema che il vincolo imposto ' stato soddisfatto. 'N.B.: il fatto che il nome di questa propriet sia uguale 'a quella definita in IIdentifiable non significa nulla. 'Avremmo potuto benissimo chiamarla "Pippo" e associarla 'a Id tramite il codice "Implements IIdentifiable.Id", ma 'ovviamente sarebbe stata una palese idiozia XD Public ReadOnly Property Id() As Integer Implements IIdentifiable.Id Get Return _Id End Get End Property 'Destinazione del pacco. 'Il fatto che l'interfaccia ci obblighi a definire quei due 'membri non significa che non possiamo definirne altri Public Property Destination() As String Get Return _Destination End Get Set(ByVal value As String) _Destination = value End Set End Property 'Piccolo ripasso delle propriet indicizzate e 'della gestione degli errori Public Property Dimensions(ByVal Index As Int32) As Single Get If (Index >= 0) And (Index < 3) Then Return _Dimensions(Index) Else Throw New IndexOutOfRangeException() End If End Get Set(ByVal value As Single) If (Index >= 0) And (Index < 3) Then _Dimensions(Index) = value Else Throw New IndexOutOfRangeException() End If End Set End Property Public Overrides Function ToString() As String Implements IIdentifiable.ToString Return String.Format("{0}: Pacco {1}x{2}x{3}, Destinazione: {4}", _ Me.Id, Me.Dimensions(0), Me.Dimensions(1), _ Me.Dimensions(2), Me.Destination) End Function End Class Sub Main() '... End Sub

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. Private _Id As Int32 011. 012. Private _Destination As String 013. Private _Dimensions(2) As Single 014. 015. Public ReadOnly Property Id() As Integer Implements IIdentifiable.Id Get 016. Return _Id 017. 018. End Get End Property 019. 020. Public Property Destination() As String 021. Get 022. Return _Destination 023. End Get 024. Set(ByVal value As String) 025. _Destination = value 026. End Set 027. End Property 028. 029. Public Property Dimensions(ByVal Index As Int32) As Single 030. Get 031. If (Index >= 0) And (Index < 3) Then 032. Return _Dimensions(Index) 033. Else 034. Throw New IndexOutOfRangeException() 035. End If 036. End Get 037. Set(ByVal value As Single) 038. If (Index >= 0) And (Index < 3) Then 039. _Dimensions(Index) = value 040. Else 041. Throw New IndexOutOfRangeException() 042. End If 043. End Set 044. End Property 045. 046. Sub New(ByVal Id As Int32) 047. _Id = Id 048. End Sub 049. 050. Public Overrides Function ToString() As String Implements IIdentifiable.ToString 051. Return String.Format("{0:0000}: Pacco {1}x{2}x{3}, Destinazione: {4}", _ 052. Me.Id, Me.Dimensions(0), Me.Dimensions(1), _ 053. Me.Dimensions(2), Me.Destination) 054. End Function 055. End Class 056. 057. 058.

059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121. 122. 123. 124. 125. 126. 127. 128. 129. 130.

Class Telegram Implements IIdentifiable Private _Id As Int32 Private _Recipient As String Private _Message As String Public ReadOnly Property Id() As Integer Implements IIdentifiable.Id Get Return _Id End Get End Property Public Property Recipient() As String Get Return _Recipient End Get Set(ByVal value As String) _Recipient = value End Set End Property Public Property Message() As String Get Return _Message End Get Set(ByVal value As String) _Message = value End Set End Property Sub New(ByVal Id As Int32) _Id = Id End Sub Public Overrides Function ToString() As String Implements IIdentifiable.ToString Return String.Format("{0:0000}: Telegramma per {1} ; Messaggio = {2}", _ Me.Id, Me.Recipient, Me.Message) End Function End Class Class MoneyOrder Implements IIdentifiable Private _Id As Int32 Private _Recipient As String Private _Money As Single Public ReadOnly Property Id() As Integer Implements IIdentifiable.Id Get Return _Id End Get End Property Public Property Recipient() As String Get Return _Recipient End Get Set(ByVal value As String) _Recipient = value End Set End Property Public Property Money() As Single Get Return _Money End Get Set(ByVal value As Single) _Money = value End Set End Property

131. 132. 133. 134. 135. 136. 137. 138. 139. 140. 141. 142. 143. 144. 145. 146. 147. 148. 149. 150. 151. 152. 153. 154. 155. 156. 157. 158. 159. 160. 161. 162. 163. 164. 165. 166. 167. 168. 169. 170. 171. 172. 173. 174. 175. 176. 177. 178. 179. 180. 181. 182. 183. 184. 185. 186. 187. 188. 189. 190. 191. 192. 193. 194. 195. 196. 197. 198. 199. 200. 201. 202.

Sub New(ByVal Id As Int32) _Id = Id End Sub Public Overrides Function ToString() As String Implements IIdentifiable.ToString Return String.Format("{0:0000}: Vaglia postale per {1} ; Ammontare = {2}", _ Me.Id, Me.Recipient, Me.Money) End Function End Class 'Classe che elabora dati di tipo IIdentifiable, ossia qualsiasi 'oggetto che implementi tale interfaccia Class PostalProcessor 'Tanto per tenersi allenati coi delegate, ecco una 'funzione delegate che funge da filtro per i vari id Public Delegate Function IdSelector(ByVal Id As Int32) As Boolean Private _StorageCapacity As Int32 Private _NextId As Int32 = 0 'Un array di interfacce. Quando una variabile viene 'dichiarata come di tipo interfaccia, ci 'che pu contenere qualsiasi oggetto 'che implementi quell'interfaccia. Per lo stesso 'discorso fatto nel capitolo precedente, noi 'possiamo vedere <i>attraverso</i> l'interfaccia 'solo quei membri che essa espone direttamente, anche 'se il contenuto vero e proprio qualcosa 'di pi Private Storage() As IIdentifiable 'Capacit del magazzino. Assumeremo che tutti 'gli oggetti rappresentati dalle classi Pack, Telegram 'e MoneyOrder vadano in un magazzino immaginario che, 'improbabilmente, riserva un solo posto per ogni 'singolo elemento Public Property StorageCapacity() As Int32 Get Return _StorageCapacity End Get Set(ByVal value As Int32) _StorageCapacity = value ReDim Preserve Storage(value) End Set End Property 'Modifica od ottiene un riferimento all'Index-esimo 'oggetto nell'array Storage Public Property Item(ByVal Index As Int32) As IIdentifiable Get If (Index >= 0) And (Index < Storage.Length) Then Return Me.Storage(Index) Else Throw New IndexOutOfRangeException() End If End Get Set(ByVal value As IIdentifiable) If (Index >= 0) And (Index < Storage.Length) Then Me.Storage(Index) = value Else Throw New IndexOutOfRangeException() End If End Set End Property 'Restituisce la prima posizione libera nell'array 'Storage. Anche se in questo esempio non l'abbiamo 'contemplato, gli elementi possono anche essere rimossi 'e quindi lasciare un posto libero nell'array Public ReadOnly Property FirstPlaceAvailable() As Int32 Get For I As Int32 = 0 To Me.Storage.Length - 1 If Me.Storage(I) Is Nothing Then

203. 204. 205. 206. 207. 208. 209. 210. 211. 212. 213. 214. 215. 216. 217. 218. 219. 220. 221. 222. 223. 224. 225. 226. 227. 228. 229. 230. 231. 232. 233. 234. 235. 236. 237. 238. 239. 240. 241. 242. 243. 244. 245. 246. 247. 248. 249. 250. 251. 252. 253. 254. 255. 256. 257. 258. 259. 260. 261. 262. 263. 264. 265. 266. 267. 268. 269. 270. 271. 272. 273. 274.

Next Return (-1) End Get End Property

Return I End If

'Tutti gli oggetti che inizializzeremo avranno bisogno 'di un id: ce lo fornisce la stessa classe Processor 'tramite questa propriet che si autoincrementa Public ReadOnly Property NextId() As Int32 Get _NextId += 1 Return _NextId End Get End Property 'Due possibili costruttori: uno che accetta un insieme 'gi formato di elementi... Public Sub New(ByVal Items() As IIdentifiable) Me.Storage = Items _SorageCapacity = Items.Length End Sub '... e uno che accetta solo la capacit del magazzino Public Sub New(ByVal Capacity As Int32) Me.StorageCapacity = Capacity End Sub 'Stampa a schermo tutti gli elementi che la funzione 'contenuta nel parametro Selector di tipo delegate 'considera validi (ossia tutti quelli per cui 'Selector.Invoke restituisce True) Public Sub PrintByFilter(ByVal Selector As IdSelector) For Each K As IIdentifiable In Storage If K Is Nothing Then Continue For End If If Selector.Invoke(K.Id) Then Console.WriteLine(K.ToString()) End If Next End Sub 'Stampa l'oggetto con Id specificato Public Sub PrintById(ByVal Id As Int32) For Each K As IIdentifiable In Storage If K Is Nothing Then Continue For End If If K.Id = Id Then Console.WriteLine(K.ToString()) Exit For End If Next End Sub 'Cerca tutti gli elementi che contemplano all'interno 'della propria descrizione la stringa Str e li 'restituisce come array di Id Public Function SearchItems(ByVal Str As String) As Int32() Dim Temp As New ArrayList For Each K As IIdentifiable In Storage If K Is Nothing Then Continue For End If If K.ToString().Contains(Str) Then Temp.Add(K.Id) End If Next

275. 276. 277. 278. 279. 280. 281. 282. 283. 284. 285. 286. 287. 288. 289. 290. 291. 292. 293. 294. 295. 296. 297. 298. 299. 300. 301. 302. 303. 304. 305. 306. 307. 308. 309. 310. 311. 312. 313. 314. 315. 316. 317. 318. 319. 320. 321. 322. 323. 324. 325. 326. 327. 328. 329. 330. 331. 332. 333. 334. 335. 336. 337. 338. 339. 340. 341. 342. 343. 344. 345. 346.

Dim Result(Temp.Count - 1) As Int32 For I As Int32 = 0 To Temp.Count - 1 Result(I) = Temp(I) Next Temp.Clear() Temp = Nothing Return Result End Function End Class Private Processor As New PostalProcessor(10) Private Cmd As Char Private IdFrom, IdTo As Int32 Function SelectId(ByVal Id As Int32) As Boolean Return (Id >= IdFrom) And (Id <= IdTo) End Function Sub InsertItems(ByVal Place As Int32) Console.WriteLine("Scegliere la tipologia di oggetto:") Console.WriteLine(" p - pacco;") Console.WriteLine(" t - telegramma;") Console.WriteLine(" v - vaglia postale;") Cmd = Console.ReadKey().KeyChar Console.Clear() Select Case Cmd Case "p" Dim P As New Pack(Processor.NextId) Console.WriteLine("Pacco - Id:{0:0000}", P.Id) Console.Write("Destinazione: ") P.Destination = Console.ReadLine Console.Write("Larghezza: ") P.Dimensions(0) = Console.ReadLine Console.Write("Lunghezza: ") P.Dimensions(1) = Console.ReadLine Console.Write("Altezza: ") P.Dimensions(2) = Console.ReadLine Processor.Item(Place) = P Case "t" Dim T As New Telegram(Processor.NextId) Console.WriteLine("Telegramma - Id:{0:0000}", T.Id) Console.Write("Destinatario: ") T.Recipient = Console.ReadLine Console.Write("Messaggio: ") T.Message = Console.ReadLine Processor.Item(Place) = T Case "v" Dim M As New MoneyOrder(Processor.NextId) Console.WriteLine("Vaglia - Id:{0:0000}", M.Id) Console.Write("Beneficiario: ") M.Recipient = Console.ReadLine Console.Write("Somma: ") M.Money = Console.ReadLine Processor.Item(Place) = M Case Else Console.WriteLine("Comando non riconosciuto.") Console.ReadKey() Exit Sub End Select Console.WriteLine("Inserimento eseguito!") Console.ReadKey() End Sub Sub ProcessData() Console.WriteLine("Selezionare l'operazione:") Console.WriteLine(" c - cerca;") Console.WriteLine(" v - visualizza;")

347. 348. 349. 350. 351. 352. 353. 354. 355. 356. 357. 358. 359. 360. 361. 362. 363. 364. 365. 366. 367. 368. 369. 370. 371. 372. 373. 374. 375. 376. 377. 378. 379. 380. 381. 382. 383. 384. 385. 386. 387. 388. 389. 390. 391. 392. 393. 394. 395. 396. 397. 398. 399. 400. 401. 402. 403. 404. 405. 406. 407. 408. 409. 410. 411. 412. 413. 414. 415. 416. End

Cmd = Console.ReadKey().KeyChar Console.Clear() Select Case Cmd Case "c" Dim Str As String Console.WriteLine("Inserire la parola da cercare:") Str = Console.ReadLine Dim Ids() As Int32 = Processor.SearchItems(Str) Console.WriteLine("Trovati {0} elementi. Visualizzare? (y/n)", Ids.Length) Cmd = Console.ReadKey().KeyChar Console.WriteLine() If Cmd = "y" Then For Each Id As Int32 In Ids Processor.PrintById(Id) Next End If Case "v" Console.WriteLine("Visualizzare gli elementi") Console.Write("Da Id: ") IdFrom = Console.ReadLine Console.Write("A Id: ") IdTo = Console.ReadLine Processor.PrintByFilter(AddressOf SelectId) Case Else Console.WriteLine("Comando sconosciuto.") End Select Console.ReadKey() End Sub Sub Main() Do Console.WriteLine("Gestione ufficio") Console.WriteLine() Console.WriteLine("Selezionare l'operazione da effettuare:") Console.WriteLine(" i - inserimento oggetti;") Console.WriteLine(" m - modifica capacit magazzino;") Console.WriteLine(" p - processa i dati;") Console.WriteLine(" e - esci.") Cmd = Console.ReadKey().KeyChar Console.Clear() Select Case Cmd Case "i" Dim Index As Int32 = Processor.FirstPlaceAvailable Console.WriteLine("Inserimento oggetti in magazzino") Console.WriteLine() If Index > -1 Then InsertItems(Index) Else Console.WriteLine("Non c' pi spazio in magazzino!") Console.ReadKey() End If Case "m" Console.WriteLine("Attuale capacit: " & Processor.StorageCapacity) Console.WriteLine("Inserire una nuova dimensione: ") Processor.StorageCapacity = Console.ReadLine Console.WriteLine("Operazione effettuata.") Console.ReadKey() Case "p" ProcessData() End Select Console.Clear() Loop Until Cmd = "e" End Sub 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. Interface IPayable 04. Function CalculateSendCost() As Single 05. End Interface 06. 07. 08. Class Telegram 'Nel caso di pi interfacce, le si separa con la virgola 09. Implements IIdentifiable, IPayable 10. 11. 12. '... 13. Public Function CalculateSendCost() As Single Implements IPayable.CalculateSendCost 14. 15. 'Come vedremo nel capitolo dedicato alle stringhe, 'la funzione Split(c) spezza la stringa in tante 16. 17. 'parti, divise dal carattere c, e le restituisce 18. 'sottoforma di array. In questo caso, tutte le sottostringhe 'separate da uno spazio sono all'incirca tante 19. 'quanto il numero di parole nella frase 20. Select Case Me.Message.Split(" ").Length 21. Case Is <= 20 22. Return 4.39 23. Case Is <= 50 24. Return 6.7 25. Case Is <= 100 26. Return 10.3 27. Case Is <= 200 28. Return 19.6 29. Case Is <= 500 30. Return 39.75 31. End Select 32. End Function 33. End Class 34. 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.

14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27.

End Interface Class A Private _SaveInfo As ISaveable.FileInfo 'SBAGLIATO! '... End Class Class B Implements ISaveable Private _SaveInfo As ISaveable.FileInfo 'GIUSTO '... 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. 2. 3. 4. 5. 6. 7. 8. Interface A Property PropA() As Int32 End Interface Interface B Inherits A Sub SubB() 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) 'E visualizza i numeri in ordine crescente 24. For I As Int16 = 0 To UBound(B) 25. 26. Console.WriteLine(B(I)) Next 27. 28. 'Anche String espone questo metodo, quindi si pu ordinare 29. 'alfabeticamente un insieme di stringhe: 30. 31. Dim C As New ArrayList C.Add("Banana") 32. C.Add("Zanzara") 33. C.Add("Anello") 34. C.Add("Computer") 35. 'Ordina l'insieme 36. C.Sort() 37. For I As Int16 = 0 To C.Count - 1 38. Console.WriteLine(C(I)) 39. Next 40. 41. Console.ReadKey() 42. 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 Private ReadOnly _BirthDay As Date 05. 06. 07. Public Property FirstName() As String Get 08. 09. Return _FirstName End Get 10. Set(ByVal Value As String) 11. 12. If Value <> "" Then 13. _FirstName = Value 14. End If 15. End Set End Property 16. 17. 18. Public Property LastName() As String 19. Get 20. Return _LastName 21. End Get Set(ByVal Value As String) 22. If Value <> "" Then 23. 24. _LastName = Value 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 ReadOnly Property CompleteName() As String 35. Get 36. Return _FirstName & " " & _LastName 37. End Get 38. End Property 39. 40. 'Per definizione, purtroppo, CompareTo deve sempre usare 41. 'un parametro di tipo Object: risolveremo questo problema 42. 43.

44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. End

'pi in l utilizzando i Generics Public Function CompareTo(ByVal obj As Object) As Integer _ Implements IComparable.CompareTo 'Un oggetto non-nothing (questo) sempre maggiore di 'un oggetto Nothing (ossia obj) If obj Is Nothing Then Return 1 End If 'Tenta di convertire obj in Person Dim P As Person = DirectCast(obj, Person) 'E restituisce il risultato dell'operazione di 'comparazione tra stringhe dei rispettivi nomi Return String.Compare(Me.CompleteName, P.CompleteName) End Function Sub New(ByVal FirstName As String, ByVal LastName As String, _ ByVal BirthDay As Date) Me.FirstName = FirstName Me.LastName = LastName Me._BirthDay = BirthDay End Sub End Class Sub Main() 'Crea un array di oggetti Person Dim Persons() As Person = _ {New Person("Marcello", "Rossi", Date.Parse("10/10/1992")), _ New Person("Guido", "Bianchi", Date.Parse("01/12/1980")), _ New Person("Bianca", "Brega", Date.Parse("23/06/1960")), _ New Person("Antonio", "Felice", Date.Parse("16/01/1930"))} 'E li ordina, avvalendosi di IComparable.CompareTo Array.Sort(Persons) For I As Int16 = 0 To UBound(Persons) Console.WriteLine(Persons(I).CompleteName) Next Console.ReadKey() End Sub 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.

19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. End

'Anche questa funzione deve usare parametri object Public Function Compare(ByVal x As Object, ByVal y As Object) _ As Integer Implements System.Collections.IComparer.Compare 'Se entrambi gli oggetti sono Nothing, allora sono 'uguali If x Is Nothing And y Is Nothing Then Return 0 ElseIf x Is Nothing Then 'Se x Nothing, y maggiore Return -1 ElseIf y Is Nothing Then 'Se y Nothing, x maggiore Return 1 Else Dim P1 As Person = DirectCast(x, Person) Dim P2 As Person = DirectCast(y, Person) 'Compara le date Return Date.Compare(P1.BirthDay, P2.BirthDay) End If End Function End Class Sub Main() Dim Persons() As Person = _ {New Person("Marcello", "Rossi", Date.Parse("10/10/1992")), _ New Person("Guido", "Bianchi", Date.Parse("01/12/1980")), _ New Person("Bianca", "Brega", Date.Parse("23/06/1960")), _ New Person("Antonio", "Felice", Date.Parse("16/01/1930"))} 'Ordina gli elementi utilizzando il nuovo oggetto 'inizializato in linea BirthDayComparer Array.Sort(Persons, New BirthDayComparer()) For I As Int16 = 0 To UBound(Persons) Console.WriteLine(Persons(I).CompleteName) Next Console.ReadKey() End Sub 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 'La lista delle persone 04. Private _Persons As New ArrayList 05. 06. 07. 'Teoricamente, si dovrebbero ridefinire tutti i metodi 'di una collection comune, ma per mancanza di spazio, 08. 09. 'accontentiamoci Public ReadOnly Property Persons() As ArrayList 10. Get 11. Return _Persons 12. 13. End Get End Property 14. 15. 'Restituisce l'et media. TimeSpan una struttura che si 16. 'ottiene sottraendo fra loro due oggetti date e indica un 17. 18. 'intervallo di tempo Public ReadOnly Property AverageAge() As String 19. Get 20. 'Variabile temporanea 21. Dim Temp As TimeSpan 22. 'Somma tutte le et 23. For Each P As Person In _Persons 24. Temp = Temp.Add(Date.Now - P.BirthDay) 25. Next 26. 'Divide per il numero di persone 27. Temp = TimeSpan.FromSeconds(Temp.TotalSeconds / _Persons.Count) 28. 29. 'Dato che TimeSpan pu contenere al massimo 30. 'giorni e non mesi o anni, dobbiamo fare qualche 31. 'calcolo 32. Dim Years As Int32 33. 'Gli anni, ossia il numero dei giorni fratto 365 34. 'Divisione intera 35. Years = Temp.TotalDays \ 365 36. 'Sottrae gli anni: da notare che 37. '(Temp.TotalDays \ 365) * 365) non un passaggio 38. 'inutile. Infatti, per determinare il numero di 39. 'giorni che rimangono, bisogna prendere la 40. 'differenza tra il numero totale di giorni e 41. 'il multiplo pi vicino di 365 42. Temp = _ 43. Temp.Subtract(TimeSpan.FromDays((Temp.TotalDays \ 365) * 365)) 44. 45. Return Years & " anni e " & CInt(Temp.TotalDays) & " giorni" 46. End Get 47. End Property 48. 49. 'La funzione GetEnumerator restituisce un oggetto di tipo 50. 'IEnumerator che vedremo fra breve: esso permette di 51. 'scorrere ogni elemento ordinatamente, dall'inizio 52. 'alla fine. In questo caso, poich non abbiamo ancora 53. 'analizzato questa interfaccia, ci limitiamo a restituisce 54. 55.

56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. End

'l'IEnumerator predefinito per un ArrayList Public Function GetEnumerator() As IEnumerator _ Implements IEnumerable.GetEnumerator Return _Persons.GetEnumerator End Function End Class Sub Main() Dim Persons As New PersonCollection With Persons.Persons .Add(New Person("Marcello", "Rossi", Date.Parse("10/10/1992"))) .Add(New Person("Guido", "Bianchi", Date.Parse("01/12/1980"))) .Add(New Person("Bianca", "Brega", Date.Parse("23/06/1960"))) .Add(New Person("Antonio", "Felice", Date.Parse("16/01/1930"))) End With For Each P As Person In Persons Console.WriteLine(P.CompleteName) Next Console.WriteLine("Et media: " & Persons.AverageAge) '> 41 anni e 253 giorni Console.ReadKey() End Sub 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 Sub New(ByVal Persons As ArrayList) 024. 'Ricordate: poich ArrayList deriva da Object, 025. 026. 'un tipo reference. Assegnare Persons a Me.Persons 'equivale ad assegnarne l'indirizzo e quindi ogni 027. 'modifica su questo arraylist privato si rifletter 028. 'su quello passato come parametro. Si pu 029. 'evitare questo problema clonando la lista 030. 031.

032. 033. 034. 035. 036. 037. 038. 039. 040. 041. 042. 043. 044. 045. 046. 047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099. 100. 101. 102. 103.

Me.Persons = Persons.Clone 'MoveNext viene richiamato prima di usare Current, 'quindi Index verr incrementata subito. 'Per farla diventare 0 al primo ciclo la si 'deve impostare a -1 Index = -1 'Dato che l'enumeratore deve scorrere la lista 'secondo l'anno di nascita, bisogna prima ordinarla Me.Persons.Sort(New BirthDayComparer) End Sub 'Restituisce l'elemento corrente Public ReadOnly Property Current() As Object _ Implements System.Collections.IEnumerator.Current Get Return Persons(Index) End Get End Property 'Restituisce True se esiste l'elemento successivo e lo 'imposta, altrimenti False Public Function MoveNext() As Boolean _ Implements System.Collections.IEnumerator.MoveNext If Index = Persons.Count - 1 Then Return False Else Index += 1 Return True End If End Function 'Resetta il ciclo Public Sub Reset() _ Implements System.Collections.IEnumerator.Reset Index = -1 End Sub End Class Public ReadOnly Property Persons() As ArrayList Get Return _Persons End Get End Property Public ReadOnly Property AverageAge() As String Get Dim Temp As TimeSpan For Each P As Person In _Persons Temp = Temp.Add(Date.Now - P.BirthDay) Next Temp = TimeSpan.FromSeconds(Temp.TotalSeconds / _Persons.Count) Dim Years As Int32 Years = Temp.TotalDays \ 365 Temp = _ Temp.Subtract(TimeSpan.FromDays((Temp.TotalDays \ 365) * 365)) Return Years & " anni e " & CInt(Temp.TotalDays) & " giorni" End Get End Property 'La funzione GetEnumerator restituisce ora un oggetto di 'tipo IEnumerator che abbiamo definito in una classe 'nidificata e il ciclo For Each scorrer quindi 'dal pi vecchio al pi giovane Public Function GetEnumerator() As IEnumerator _ Implements IEnumerable.GetEnumerator Return New PersonAgeEnumerator(_Persons) End Function End Class Sub Main()

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. Console.ReadKey() 122. 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 Dim S2 As New ArrayList 05. 06. 07. S2 = S1 08. 09. 'Verifica che S1 e S2 puntano lo stesso oggetto Console.WriteLine(S1 Is S2) 10. 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.

11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. End

Set(ByVal Value As Int32) _Campo = Value End Set End Property 'Restituisce una copia dell'oggetto Public Function Clone() As Object Implements ICloneable.Clone 'La funzione Protected MemberwiseClone, ereditata da 'Object, esegue una copia superficiale dell'oggetto, 'come spiegher fra poco: quello che 'serve in questo caso Return Me.MemberwiseClone End Function 'L'operatore = permette di definire de due oggetti hanno un 'valore uguale Shared Operator =(ByVal O1 As UnOggetto, ByVal O2 As UnOggetto) As _ Boolean Return O1.Campo = O2.Campo End Operator Shared Operator <>(ByVal O1 As UnOggetto, ByVal O2 As UnOggetto) As _ Boolean Return Not (O1 = O2) End Operator End Class Sub Main() Dim O1 As New UnOggetto Dim O2 As UnOggetto = O1.Clone 'I due oggetti NON sono lo stesso oggetto: il secondo ' solo una copia, disgiunta da O1 Console.WriteLine(O1 Is O2) '> False 'Tuttavia hanno lo stesso identico valore Console.WriteLine(O1 = O2) '> True Console.ReadKey() End Sub 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) _Destination = value 026. End Set 027. 028. End Property 029. Public Property Dimensions(ByVal Index As Int32) As Single 030. Get 031. 032.

033. 034. 035. 036. 037. 038. 039. 040. 041. 042. 043. 044. 045. 046. 047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099. 100. 101. 102. 103. 104.

If (Index >= 0) And (Index < 3) Then Return _Dimensions(Index) Else Throw New IndexOutOfRangeException() End If End Get Set(ByVal value As Single) If (Index >= 0) And (Index < 3) Then _Dimensions(Index) = value Else Throw New IndexOutOfRangeException() End If End Set End Property Public Sub New(ByVal Id As Int32) _Id = Id End Sub Public Overrides Function ToString() As String Implements IIdentifiable.ToString Return String.Format("{0:0000}: Pacco {1}x{2}x{3}, Destinazione: {4}", _ Me.Id, Me.Dimensions(0), Me.Dimensions(1), _ Me.Dimensions(2), Me.Destination) End Function End Class Public Class Telegram Implements IIdentifiable Private _Id As Int32 Private _Recipient As String Private _Message As String Public ReadOnly Property Id() As Integer Implements IIdentifiable.Id Get Return _Id End Get End Property Public Property Recipient() As String Get Return _Recipient End Get Set(ByVal value As String) _Recipient = value End Set End Property Public Property Message() As String Get Return _Message End Get Set(ByVal value As String) _Message = value End Set End Property Public Sub New(ByVal Id As Int32) _Id = Id End Sub Public Overrides Function ToString() As String Implements IIdentifiable.ToString Return String.Format("{0:0000}: Telegramma per {1} ; Messaggio = {2}", _ Me.Id, Me.Recipient, Me.Message) End Function End Class Public Class MoneyOrder Implements IIdentifiable Private _Id As Int32 Private _Recipient As String

105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121. 122. 123. 124. 125. 126. 127. 128. 129. 130. 131. 132. 133. 134. 135. 136. 137. 138. 139. 140. 141. 142. 143. 144. 145. 146. 147. 148. 149. 150. 151. 152. 153. 154. 155. 156. 157. 158. 159. 160. 161. 162. 163. 164. 165. 166. 167. 168. 169. 170. 171. 172. 173. 174. 175. 176.

Private _Money As Single Public ReadOnly Property Id() As Integer Implements IIdentifiable.Id Get Return _Id End Get End Property Public Property Recipient() As String Get Return _Recipient End Get Set(ByVal value As String) _Recipient = value End Set End Property Public Property Money() As Single Get Return _Money End Get Set(ByVal value As Single) _Money = value End Set End Property Public Sub New(ByVal Id As Int32) _Id = Id End Sub Public Overrides Function ToString() As String Implements IIdentifiable.ToString Return String.Format("{0:0000}: Vaglia postale per {1} ; Ammontare = {2}", _ Me.Id, Me.Recipient, Me.Money) End Function End Class Public Class PostalProcessor Public Delegate Function IdSelector(ByVal Id As Int32) As Boolean Private _StorageCapacity As Int32 Private _NextId As Int32 = 0 Private Storage() As IIdentifiable Public Property StorageCapacity() As Int32 Get Return _StorageCapacity End Get Set(ByVal value As Int32) _StorageCapacity = value ReDim Preserve Storage(value) End Set End Property Public Property Item(ByVal Index As Int32) As IIdentifiable Get If (Index >= 0) And (Index < Storage.Length) Then Return Me.Storage(Index) Else Throw New IndexOutOfRangeException() End If End Get Set(ByVal value As IIdentifiable) If (Index >= 0) And (Index < Storage.Length) Then Me.Storage(Index) = value Else Throw New IndexOutOfRangeException() End If End Set End Property Public ReadOnly Property FirstPlaceAvailable() As Int32 Get

177. 178. 179. 180. 181. 182. 183. 184. 185. 186. 187. 188. 189. 190. 191. 192. 193. 194. 195. 196. 197. 198. 199. 200. 201. 202. 203. 204. 205. 206. 207. 208. 209. 210. 211. 212. 213. 214. 215. 216. 217. 218. 219. 220. 221. 222. 223. 224. 225. 226. 227. 228. 229. 230. 231. 232. 233. 234. 235. 236. 237. 238. 239. 240. 241. 242. 243. 244. 245. 246. 247. 248.

For I As Int32 = 0 To Me.Storage.Length - 1 If Me.Storage(I) Is Nothing Then Return I End If Next Return (-1) End Get End Property Public ReadOnly Property NextId() As Int32 Get _NextId += 1 Return _NextId End Get End Property Public Sub New(ByVal Items() As IIdentifiable) Me.Storage = Items _StorageCapacity = Items.Length End Sub Public Sub New(ByVal Capacity As Int32) Me.StorageCapacity = Capacity End Sub Public Sub PrintByFilter(ByVal Selector As IdSelector) For Each K As IIdentifiable In Storage If K Is Nothing Then Continue For End If If Selector.Invoke(K.Id) Then Console.WriteLine(K.ToString()) End If Next End Sub Public Sub PrintById(ByVal Id As Int32) For Each K As IIdentifiable In Storage If K Is Nothing Then Continue For End If If K.Id = Id Then Console.WriteLine(K.ToString()) Exit For End If Next End Sub Public Function SearchItems(ByVal Str As String) As Int32() Dim Temp As New ArrayList For Each K As IIdentifiable In Storage If K Is Nothing Then Continue For End If If K.ToString().Contains(Str) Then Temp.Add(K.Id) End If Next Dim Result(Temp.Count - 1) As Int32 For I As Int32 = 0 To Temp.Count - 1 Result(I) = Temp(I) Next Temp.Clear() Temp = Nothing Return Result End Function End Class

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 bin\Release 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. Sub Main() 05. 06. Dim P As New PostalProcessor(10) 07. Dim Pk As New Pack(P.NextId) 08. P.Item(P.FirstPlaceAvailable) = Pk 09. '... 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. 02. 03. 04. 05. 06. 07. 08. 09. 10. Dim A As New ArrayList A.Add(2) A.Add(3) A.Add("C") 'A run-time, sar lanciata un'eccezione inerente il cast 'poich la stringa "C" non del tipo specificato 'nel blocco For Each For Each V As Int32 In A Console.WriteLine(V) 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. 02. 03. 04. 05. 'La lista accetta solo oggetti di tipo Int32: per questo motivo 'si genera un'eccezione quando si tenta di inserirvi elementi di 'tipo diverso e la velocit di elaborazione aumenta! Dim A As New List(Of Int32)

06. 07. 08. 09. 10. 11.

A.Add(1) A.Add(4) A.Add(8) 'A.Add("C") '<- Impossibile For Each V As Int32 In A Console.WriteLine(V) 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() For I As Int32 = 1 To 1000000 19. TipForte.Add(I) 20. 21. Next S.Stop() 22. Console.WriteLine(S.ElapsedMilliseconds & _ 23. " millisecondi per List(Of T)!") 24. 25. Console.ReadKey() 26. End Sub 27. 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.

012. 013. 014. 015. 016. 017. 018. 019. 020. 021. 022. 023. 024. 025. 026. 027. 028. 029. 030. 031. 032. 033. 034. 035. 036. 037. 038. 039. 040. 041. 042. 043. 044. 045. 046. 047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083.

' come se avessimo "dichiarato" l'esistenza di T 'come tipo fittizio. Private _Values() As T 'Restituisce l'Index-esimo elemento di Values (anch'esso ' di tipo T) Public Property Values(ByVal Index As Int32) As T Get If (Index >= 0) And (Index < _Values.Length) Then Return _Values(Index) Else Throw New IndexOutOfRangeException() End If End Get Set(ByVal value As T) If (Index >= 0) And (Index < _Values.Length) Then _Values(Index) = value Else Throw New IndexOutOfRangeException() End If End Set End Property 'Propriet che restituiscono il primo e l'ultimo 'elemento della collezione Public ReadOnly Property First() As T Get Return _Values(0) End Get End Property Public ReadOnly Property Last() As T Get Return _Values(_Values.Length - 1) End Get End Property 'Stampa tutti i valori presenti nella collezione a schermo. 'Su un tipo generic sempre possibile usare 'l'operatore Is (ed il suo corrispettivo IsNot) e 'confrontarlo con Nothing. Se si tratta di un tipo value 'l'uguaglianza con Nothing sar sempre falsa. Public Sub PrintAll() For Each V As T In _Values If V IsNot Nothing Then Console.WriteLine(V.ToString()) End If Next End Sub 'Inizializza la collezione con Count elementi, tutti del 'valore DefaultValue Sub New(ByVal Count As Int32, ByVal DefaultValue As T) If Count < 1 Then Throw New ArgumentOutOfRangeException() End If ReDim _Values(Count - 1) For I As Int32 = 0 To _Values.Length - 1 _Values(I) = DefaultValue Next End Sub End Class Sub Main() 'Dichiara quattro variabili contenenti quattro nuovi 'oggetti Collection. Ognuno di questi, per, ' specifico per un solo tipo che decidiamo 'noi durante la dichiarazione. String, Int32, Date 'e Person, ossia i tipi che stiamo inserendo nel tipo

'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. 2. 3. 4. 5. 6. 7. Sub [NomeProcedura](Of T)([Parametri]) '... End Sub Function [NomeFunzione](Of T)([Parametri]) As [TipoRestituito] '... 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. 'Soluzione 1: si esplicita il tipo generic collegato 41. Swap(Of Double)(Z, X) 42. 'Soluzione 2: si converte Z in double esplicitamente 43. Swap(CDbl(Z), X) 44. 45. Console.ReadKey() 46. End Sub 47. 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 Private Obj2 As T2 05. 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 End Sub 22. End Class 23. 24. Sub Main() 25. 'Crea una relazione fra uno studente e un insegnante, 26. 'utilizzando le classi create nei capitoli precedenti 27. Dim R As Relation(Of Student, Teacher) 28. Dim S As New Student("Pinco", "Pallino", Date.Parse("25/06/1990"), _ 29. "Liceo Scientifico N. Copernico", 4) 30. Dim T As New Teacher("Mario", "Rossi", Date.Parse("01/07/1950"), _ 31. 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. 2. 3. 4. 5. 6. 7. Sub Example(Of T)(ByVal A As T) '... End Sub Sub Example(Of T1, T2)(ByVal A As T1) '... 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 Class StudentByGradeComparer 04. Implements IComparer(Of Student) 05. 06. 07. 'Come potete osservare, in questo metodo non viene eseguito 08. 'nessun tipo di cast, poich l'interfaccia IComparer(Of T) 'prevede un metodo Compare a tipizzazione forte. Dato che 09. 'abbiamo specificato come tipo generic collegato Student, 10. 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 End Class 18. 19. Sub Main() 20. 'Crea un nuovo array di oggeti Student 21. Dim S(2) As Student 22. 23. 'Inizializza ogni oggetto 24. S(0) = New Student("Mario", "Rossi", New Date(1993, 2, 3), "Liceo Classico Ugo 25. Foscolo", 2) S(1) = New Student("Luigi", "Bianchi", New Date(1991, 6, 27), "Liceo Scientifico 26. Fermi", 4) S(2) = New Student("Carlo", "Verdi", New Date(1992, 5, 12), "ITIS Cardano", 1) 27. 28. 'Ordina l'array con il comparer specificato 29. Array.Sort(S, New StudentByGradeComparer()) 30. 31. 'Stampa il profilo di ogni studente: vedrete che essi sono 32. 'in effetti ordinati in base all'anno di corso 33. For Each St As Student In S 34. Console.WriteLine(St.Profile) 35. Next 36. Console.ReadKey() 37. End Sub 38. 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. 'Questa classe rappresenta una collezione di 003. 'elementi che possono essere comparati. Per questo 004. 'motivo, il tipo T espone un vincolo di interfaccia 005. 'che obbliga tutti i tipi generics collegati ad 006. 007. 'implementare tale interfaccia. 'Notate bene che in questo caso particolare ho usato 008. 'un generics doppio, poich il vincolo non 009. 010. 'si riferisce a IComparable, ma a IComparable(Of T). 'D'altra parte, abbastanza ovvio che se 011. 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: ' - non dovremo definire tutti i metodi per aggiungere, 019. 020. ' rimuovere o cercare elementi, in quanto vengono tutti 021. ' ereditati dalla classe base List; 022. ' - non dovremo neanche implementare l'interfaccia ' IEnumerable(Of T), poich la classe base la 023. ' implementa di per s. 024. 025. Inherits List(Of T) 026. 'Dato che gli oggetti contenuti in oggetti di 027. 'questo tipo sono per certo comparabili, possiamo 028. 'trovarne il massimo ed il minimo. 029. 030. 'Trova il massimo elemento 031. Public ReadOnly Property Max() As T 032. Get 033. 034.

035. 036. 037. 038. 039. 040. 041. 042. 043. 044. 045. 046. 047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099. 100. 101. 102. 103. 104. 105.

If Me.Count > 0 Then Dim Result As T = Me(0) For Each Element As T In Me 'Ricordate che A.CompareTo(B) restituisce '1 se A > B If Element.CompareTo(Result) = 1 Then Result = Element End If Next Else Return Result

Return Nothing End If End Get End Property 'Trova il minimo elemento Public ReadOnly Property Min() As T Get If Me.Count > 0 Then Dim Result As T = Me(0) For Each Element As T In Me If Element.CompareTo(Result) = -1 Then Result = Element End If Next Else Return Result

Return Nothing End If End Get End Property 'Trova tutti gli elementi uguali ad A e ne restituisce 'gli indici Public Function FindEquals(ByVal A As T) As Int32() Dim Result As New List(Of Int32) For I As Int32 = 0 To Me.Count - 1 If Me(I).CompareTo(A) = 0 Then Result.Add(I) End If Next 'Converte la lista di interi in un array di interi 'con gli stessi elementi Return Result.ToArray() End Function End Class Sub Main() 'Tre collezioni, una di interi, una di stringhe e 'una di date Dim A As New ComparableCollection(Of Int32) Dim B As New ComparableCollection(Of String) Dim C As New ComparableCollection(Of Date) A.AddRange(New Int32() {4, 19, 6, 90, 57, 46, 4, 56, 4}) B.AddRange(New String() {"acca", "casa", "zen", "rullo", "casa"}) C.AddRange(New Date() {New Date(2008, 1, 1), New Date(1999, 12, 31), New Date(2100, 4, 12)}) Console.WriteLine(A.Min()) ' > 4 Console.WriteLine(A.Max()) ' > 90 Console.WriteLine(B.Min())

' > 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. 2. 3. 4. Dim P As Person Dim S As Student '... 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 ' una classe particolarmente utile e versatile 21. 22. 'che trova applicazioni in molte situazioni. 23. 'Avere un bel metodo generics che ne crea 10 in una 'volta una gran comodit. 24. 'Ovviamente possiamo fare la stessa cosa con tutti 25. 26. 'i tipi che espongono almeno un New senza parametri Dim Streams As IO.MemoryStream() = CreateArray(Of IO.MemoryStream)(10) 27. 28. '... 29. End Sub 30. 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 Class DataFilter(Of T) 04. Delegate Function FilterData(ByVal Data As T) As Boolean 05. 06. 07. 'La signature chilometrica fatta apposta per 08. 'farvi impazzire XD Vediamo le parti una per una: ' - TSerach: deve essere un tipo uguale a T o derivato 09. 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 ' quindi lo sia anche T: questa complicazione solo 14. 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 ' l'interfaccia IList(Of TSearch), ossia deve 19. ' essere una lista; 20. 21. ' - ResultList: lista in cui riporre i risultati (passata ' per indirizzo); 22. ' - Filter: delegate che punta alla funzione usata per 23. ' selezionare i valori; 24. ' - Data: paramarray contenente i valori da filtrare. 25. Sub Filter(Of TSearch As {ICloneable, T}, TList As {IList(Of TSearch), New, Class}) _ 26. (ByRef ResultList As TList, ByVal Filter As FilterData, ByVal ParamArray Data() As 27. TSearch) 28. 'Se la lista Nothing, la inizializza. 29. 'Notare che non avremmo potuto compararla a Nothing 30. 'senza il vincolo Class, n inizializzarla 31. 'senza il vincolo New 32. If ResultList Is Nothing Then 33. ResultList = New TList() 34. End If 35. 36. 'Itera sugli elementi di data 37. For Each Element As TSearch In Data 38. 'E aggiunge una copia di quelli che 39. 'soddisfano la condizione 40. If Filter.Invoke(Element) Then 41. 'Aggiunge una copia dell'elemento alla lista. 42. 'Anche in questo non avremmo potuto richiamare 43. 'Add senza il vincolo interfaccia su IList, n 44. 'clonare Element senza il vincolo interfaccia ICloneable 45. ResultList.Add(Element.Clone()) 46. End If 47. Next 48. End Sub 49. End Class 50. 51. 'Controlla se la stringa A palindroma 52. Function IsPalindrome(ByVal A As String) As Boolean 53. Dim Result As Boolean = True 54. 55. For I As Int32 = 0 To (A.Length / 2) - 1 56. If A.Chars(I) <> A.Chars(A.Length - 1 - I) Then 57. Result = False 58. Exit For 59. End If 60. Next 61. 62. Return Result 63. End Function 64. 65. Sub Main() 66. Dim DF As New DataFilter(Of String) 67. 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 Variable = 0 04. 'Per impostare il valore di default, bisognava ripetere 05. 'questi If tante volte quanti erano i tipi in gioco, poich 06. 07. 'non c'era modo di assegnare un valore Null a tutti 'in un solo colpo 08. 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. 2. 3. 4. 5. 'Dichiarazione esplicita: Dim [Nome] As Nullable(Of [Tipo]) 'Dichiarazione implicita: 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, 'trattandosi di tipi nullable, questi oggetti possono 27. 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) Sent = RowValues(3) 34. 35. 'Scrive a schermo il valore di ogni variabile, se ne 36. 'contiene uno, oppure il valore di default se non 37. 'contiene alcun valore. 38. Console.WriteLine("{0} {1} {2} {3}", _ 39. Number.GetValueOrDefault, _ 40. Data.GetValueOrDefault, _ 41. Cost.GetValueOrDefault, _ 42. Sent.GetValueOrDefault) 43. 44. 'Provando a stampare una variabile nullable priva 45. 'di valore senza usare la funzione GetValueOrDefault, 46. 'semplicemente non stamperete niente: 47. ' Console.WriteLine(Number) 48. 'Non stampa niente e va a capo. 49. 50. Console.ReadKey() 51. End Sub 52. 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 Tr ue False Tr ue False

Oper ator e And And Or Or

Valor e 2 Null Null Null Null

Risultato Null False Tr ue Null

Tr ue False

Xor Xor

Null Null

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 System.Dr aw ing.dll, System.Cor e.dll, ecceter a...). la pr ogr ammazione .NET (ad esempio System.dll,

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:\WINDOWS\assembly 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. 'mscorlib l'assembly pi importante di 05. 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 'uno zero. 22. 23. Console.Write("Public Key: ") For Each B As Byte In Name.GetPublicKey() 24. Console.Write("{0:X2}", B) 25. 26. Next Console.WriteLine() 27. 28. Console.Write("Public Key token: ") 29. For Each B As Byte In Name.GetPublicKeyToken 30. 31. Console.Write("{0:X2}", B) Next 32. Console.WriteLine() 33. 34. Console.WriteLine("Processore: " & _ 35. Name.ProcessorArchitecture.ToString) 36. 37. Console.ReadKey() 38. 39. End Sub 40. 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. 'AppDomain una variabile globale, oggetto singleton, da cui 05. 'si possono trarre informazioni sull'AppDomain corrente o 06. 'crearne degli altri. 07. 08. For Each Asm In AppDomain.CurrentDomain.GetAssemblies Name = Asm.GetName 09. Console.WriteLine("Nome: " & Name.Name) 10. Console.WriteLine("Versione: " & Name.Version.ToString) 11. 12. Console.Write("Public Key Token: ") 13. For Each B As Byte In Name.GetPublicKeyToken Console.Write(Hex(B)) 14. 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 'l'operatore GetType 05. 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() Console.WriteLine(ArrayListType.FullName) 24. 25. 26. Console.ReadKey() End Sub 27. 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. Console.WriteLine(Type1 Is Type2) 08. 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) Dim Category As String 04. 05. 06. 'GetTypes restituisce un array di Type che 07. 'indicano tutti i tipi definiti all'interno 08. 'dell'assembly Asm For Each T As Type In Asm.GetTypes 09. If T.IsClass Then 10. Category = "Class" 11. 12. ElseIf T.IsInterface Then 13. Category = "Interface" ElseIf T.IsEnum Then 14. 15. Category = "Enumerator" ElseIf T.IsValueType Then 16. 17. Category = "Structure" 18. ElseIf T.IsPrimitive Then Category = "Base Type" 19. End If 20. Console.WriteLine("{0} ({1})", T.Name, Category) 21. Next 22. End Sub 23. 24. Sub Main() 25. 'Ottiene un riferimento all'assembly in esecuzione, 26. 'quindi al programma. Non otterrete molti tipi 27. 'usando questo codice, a meno che il resto del 28. 'modulo non sia pieno di codice vario come nel 29. 'mio caso XD 30. Dim Asm As Assembly = Assembly.GetExecutingAssembly() 31. 32. EnumerateTypes(Asm) 33. 34. Console.ReadKey() 35. End Sub 36. 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.

10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. End

AnalyzeInheritance(T.BaseType) End If End Sub Enum Status Enabled Disabled Standby End Enum Structure Example Dim A As Int32 End Structure Delegate Sub Sample() Sub Main() Console.WriteLine("Integer:") AnalyzeInheritance(GetType(Integer)) Console.WriteLine() Console.WriteLine("Enum Status:") AnalyzeInheritance(GetType(Status)) Console.WriteLine() Console.WriteLine("Structure Example:") AnalyzeInheritance(GetType(Example)) Console.WriteLine() Console.WriteLine("Delegate Sample:") AnalyzeInheritance(GetType(Sample)) Console.WriteLine() Console.ReadKey() End Sub 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() Dim T As Type = GetType(String) 03. 04. 'Elenca tutti i membri di String 05. For Each M As MemberInfo In T.GetMembers 06. 07. 'La propriet MemberType restituisce un enumeratore che 'specifica di che tipo sia il membro, se una propriet, 08. 09. 'un metodo, un costruttore, eccetera... Console.WriteLine(M.MemberType.ToString & " " & M.Name) 10. Next 11. 12. 13. Console.ReadKey() End Sub 14. 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.

011. 012. 013. 014. 015. 016. 017. 018. 019. 020. 021. 022. 023. 024. 025. 026. 027. 028. 029. 030. 031. 032. 033. 034. 035. 036. 037. 038. 039. 040. 041. 042. 043. 044. 045. 046. 047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082.

Dim CompleteName As New System.Text.StringBuilder 'Lo specificatore di accesso Dim Scope As String 'Gli eventuali modificatori Dim Modifier As String 'La categoria: Sub o Function Dim Category As String 'La signature del metodo, che andremo a costruire Dim Signature As New System.Text.StringBuilder 'Di solito, tutti i metodi hanno un tipo restituito, 'poich, in analogia con la sintassi del C#, una 'procedura una funzione che restituisce Void, 'ossia niente. Per questo bisogna controllare anche il 'nome del tipo di ReturnParameter If MI.ReturnParameter IsNot Nothing AndAlso _ MI.ReturnType.FullName <> "System.Void" Then Category = "Function" Else Category = "Sub" End If If MI.IsConstructor Then Name = "New" Else Name = MI.Name End If If MI.IsAssembly Then Scope = "Friend" ElseIf MI.IsFamily Then Scope = "Protected" ElseIf MI.IsFamilyOrAssembly Then Scope = "Protected Friend" ElseIf MI.IsPrivate Then Scope = "Private" Else Scope = "Public" End If If MI.IsFinal Then 'Vorrei far notare una sottigliezza. Se il metodo 'Final, ossia NotOverridable, significa che non pu 'essere modificato nelle classi derivate. Ma tutti i 'membri non dichiarati esplicitamente Overridable 'non sono modificabili nelle classi derivate. Quindi, 'definire un metodo senza modificatori polimorfici '(come quelli che seguono qua in basso), equivale a 'definirlo NotOverridable. Perci non si 'aggiunge nessun modificatore in questo caso ElseIf MI.IsAbstract Then Modifier = "MustOverride" ElseIf MI.IsVirtual Then Modifier = "Overridable" ElseIf MI.GetBaseDefinition IsNot Nothing AndAlso _ MI IsNot MI.GetBaseDefinition Then Modifier = "Overrides" End If If MI.IsStatic Then If Modifier <> "" Then Modifier = "Shared " & Modifier Else Modifier = "Shared" End If End If 'Inizia la signature con una parentesi tonda aperta. 'Append aggiunge una stringa a Signature Signature.Append("(") For Each P As ParameterInfo In MI.GetParameters 'Se P un parametro successivo al primo, lo separa dal

083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121. 122. 123. 124. 125. 126. 127. 128. 129. 130. 131. 132. 133. 134. 135. 136. 137. 138. 139. 140. 141. 142. 143. 144. 145. 146. 147. 148. 149. 150. 151. 152. 153. 154.

'precedente con una virgola If P.Position > 0 Then Signature.Append(", ") End If 'Se P passato per valore, ci vuole ByVal, altrimenti 'ByRef. IsByRef un membro di Type, ma viene 'usato solo quando il tipo in questione indica il tipo 'di un parametro If P.ParameterType.IsByRef Then Signature.Append("ByRef ") Else Signature.Append("ByVal ") End If 'Se P opzionale, ci vuole la keyword Optional If P.IsOptional Then Signature.Append("Optional ") End If Signature.Append(P.Name) If P.ParameterType.IsArray Then Signature.Append("()") End If 'Dato che la sintassi del nome in stile C#, al 'posto delle parentesi tonde in un array ci sono delle 'quadre: rimediamo Signature.Append(" As " & P.ParameterType.Name.Replace("[]","")) 'Si ricordi che i parametri optional hanno un valore 'di default If P.IsOptional Then Signature.Append(" = " & P.DefaultValue.ToString) End If

Next Signature.Append(")")

If MI.ReturnParameter IsNot Nothing AndAlso _ MI.ReturnType.FullName <> "System.Void" Then Signature.Append(" As " & MI.ReturnType.Name) End If 'Ed ecco il nome completo CompleteName.AppendFormat("{0} {1} {2} {3}{4}", Scope, Modifier, _ Category, Name, Signature.ToString) Console.WriteLine(CompleteName.ToString) Console.WriteLine() 'Ora ci occupiamo del corpo Dim MB As MethodBody = MI.GetMethodBody If MB Is Nothing Then Exit Sub End If 'Massima memoria occupata sullo stack Console.WriteLine("Massima memoria stack : {0} bytes", _ MB.MaxStackSize) Console.WriteLine() 'Variabili locali (LocalVariableInfo una variante di 'FieldInfo) Console.WriteLine("Variabili locali:") For Each L As LocalVariableInfo In MB.LocalVariables 'Dato che non si pu ottenere il nome, ci si deve 'accontentare di un indice Console.WriteLine(" Var({0}) As {1}", L.LocalIndex, _ L.LocalType.Name) Next Console.WriteLine()

155. 156. 157. 158. 159. 160. 161. 162. 163. 164. 165. 166. 167. 168. 169. 170. 171. 172. 173. 174. 175. 176. 177. 178. 179. 180. 181. 182. 183. 184. 185. 186. 187. 188. 189. 190. 191. 192. 193.

'Gestione delle eccezioni Console.WriteLine("Gestori di eccezioni:") For Each Ex As ExceptionHandlingClause In MB.ExceptionHandlingClauses 'Tipo di clausola: distingue tra filtro (When), 'clausola (Catch) o un blocco Finally Console.WriteLine(" Tipo : {0}", Ex.Flags.ToString) 'Se si tratta di un blocco Catch, ne specifica la 'natura If Ex.Flags = ExceptionHandlingClauseOptions.Clause Then Console.WriteLine(" Catch Ex As " & Ex.CatchType.Name) End If 'Offset, ossia posizione in bytes nel Try, del gestore Console.WriteLine(" Offset : {0}", Ex.HandlerOffset) 'Lunghezza, in bytes, del codice eseguibile del gestore Console.WriteLine(" Lunghezza : {0}", Ex.HandlerLength) Console.WriteLine() Next End Sub Sub Test(ByVal Num As Int32, ByVal S As String) Dim T As Date Dim V As String Console.WriteLine("Prova 1, 2, 3") Catch Ex As ArithmeticException Console.WriteLine("Errore 1") Catch Ex As ArgumentException Console.WriteLine("Errore 2") Finally Console.WriteLine("Ciao") End Try End Sub Sub Main() Dim T As Type = GetType(Module1) Dim Methods() As MethodInfo = T.GetMethods Dim Index As Int16 Console.WriteLine("Inserire un numero tra i seguenti per analizzare il metodo corrispondente:") Console.WriteLine() For I As Int16 = 0 To Methods.Length - 1 Console.WriteLine("{0} - {1}", I, Methods(I).Name) Next Console.WriteLine() Index = Console.ReadLine Try

194. 195. 196. 197. 198. 199. 200. If Index >= 0 And Index &rt; Methods.Length Then 201. AnalyzeMethod(Methods(Index)) 202. End If 203. 204. Console.ReadKey() 205. End Sub 206. 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 'Example(Of T) bisogna per forza usare una funzione 26. 'che restituisca tutti i tipi esistenti e poi 27. 28. 'cercarlo tra questi. For Each T As Type In ModuleType.GetNestedTypes() 29. 30.

31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41.

Next

If T.Name.StartsWith("Example") Then Console.WriteLine("{0} - IsGenericTypeDefinition: {1}", _ T.Name, T.IsGenericTypeDefinition) End If

'Ottiene un riferimento al campo E dichiarayo sopra Dim EField As FieldInfo = ModuleType.GetField("E") 'E ne ottiene il tipo Dim EType As Type = EField.FieldType 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 'si sta iniziando una dichiarazione generic 26. Name &= "(Of " 27. 28. 'Quindi aggiunge tutti gli argomenti generic For Each GenT As Type In T.GetGenericArguments 29. 'Se il parametro non il primo, lo separa dal 30. 'precedente con una virgola. 31. If GenT.GenericParameterPosition > 0 Then 32. Name &= ", " 33. End If 34. 'Quindi vi aggiunge il nome 35. Name &= GenT.Name 36. 37.

38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. End

Next End Sub

Next 'E chiude la parentesi Name &= ")" Console.WriteLine(Name) End If

'Notate che la classe Type espone molte propriet che 'si possono usare solo in determinati casi. Ad esempio, in 'questo codice lecito richiamare GenericParametrPosition 'poich sappiamo a priori che quel Type indica un tipo 'generic in una signature generic. Ma un in un qualsiasi OT 'non ha alcun senso usare tale propriet! Sub Main() 'Ottiene un riferimento all'assembly corrente Dim Asm As Assembly = Assembly.GetExecutingAssembly() EnumerateGenerics(Asm) Console.ReadKey() End Sub 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.

18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. End

Next End Sub

Next Name &= ")" Console.WriteLine("Dim {0} As {1}", F.Name, Name) End If

'Dato che non si stanno analizzando dei 'parametri generic, non si pu utilizzare 'la propriet GenericParameterPosition If I > 0 Then Name &= ", " End If Name &= GenP.Name I += 1

Public L As New List(Of Integer) Public I As Int32? Sub Main() EnumerateGenericFieldMembers(GetType(Module1)) Console.ReadKey() End Sub 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. 'Stampa tutte le informazioni ricavabili dalle 03. 'propriet di un dato oggetto O. Indent solo 04. 'una variabile d'appoggio per la formattazione, in modo 05. 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, 'ma potete completare il codice se desiderate. 20. If Prop.GetIndexParameters().Count > 0 Then 21. 22. Continue For End If 23. 24. 'Se un di tipo base o una stringa (giacch le 25. 'stringhe non sono tipo base ma reference), ne stampa 26. 27. 'direttamente il valore a schermo If (PropType.IsPrimitive) Or (PropType Is GetType(String)) Then 28. Console.WriteLine("{0} {1} = {2}", _ 29. 30.

31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. End

Indent, Prop.Name, Prop.GetValue(O, Nothing)) 'Altrimenti, se si tratta di un oggetto, lo analizza a 'sua volta ElseIf PropType.IsClass Then Console.WriteLine("{0} {1} = ", Indent, Prop.Name) PrintInfo(Prop.GetValue(O, Nothing), Indent & " ") End If

Next Console.WriteLine() End Sub Sub Main() 'Crea alcuni Dim P As New Dim T As New Dim R As New Dim Q As New Dim K As New

oggetti vari Person("Mario", "Rossi", New Date(1982, 3, 17)) Teacher("Luigi", "Bianchi", New Date(1879, 8, 21), "Storia") Relation(Of Person, Teacher)(P, T) List(Of Int32) Text.StringBuilder()

'Ne stampa le propriet, senza sapere nulla a priori 'sulla natura degli oggetti. 'Notate che i nomi generics rimangono con l'accento... PrintInfo(P, "") PrintInfo(T, "") PrintInfo(R, "") PrintInfo(Q, "") PrintInfo(K, "") Console.ReadKey() End Sub 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. 'Non riscrivo PrintInfo, ma considero che stia 03. 'ancora in questo modulo 04. 05. Sub Main() 06. Dim P As New Person("Mario", "Rossi", New Date(1982, 3, 17)) 07. Dim T As New Teacher("Luigi", "Bianchi", New Date(1879, 8, 21), "Storia") 08. Dim R As New Relation(Of Person, Teacher)(P, T) 09. Dim Q As New List(Of Int32) 10. Dim K As New Text.StringBuilder() 11. Dim Objects() As Object = {P, T, R, Q, K} 12. 13. Dim Cmd As Int32 14. 15. Console.WriteLine("Oggetti nella collezione: ") For I As Int32 = 0 To Objects.Length - 1 16. Console.WriteLine("{0} - Istanza di {1}", _ 17. I, Objects(I).GetType().Name) 18. Next 19. Console.WriteLine("Inserire il numero corrispondente all'oggetto da modificare: ") 20. Cmd = Console.ReadLine 21. 22. If Cmd < 0 Or Cmd > Objects.Length - 1 Then 23. Console.WriteLine("Nessun oggetto corrispondente!") 24. Exit Sub 25. End If 26. 27. Dim Selected As Object = Objects(Cmd) 28. Dim SelectedType As Type = Selected.GetType() 29. Dim Properties As New List(Of PropertyInfo) 30. 31. For Each Prop As PropertyInfo In SelectedType.GetProperties() 32. If (Prop.PropertyType.IsPrimitive Or Prop.PropertyType Is GetType(String)) And _ 33. Prop.CanWrite Then 34. Properties.Add(Prop) 35. End If 36. Next 37. 38. Console.Clear() 39. Console.WriteLine("Propriet dell'oggetto:") 40. For I As Int32 = 0 To Properties.Count - 1 41. Console.WriteLine("{0} - {1}", _ 42. I, Properties(I).Name) 43. Next 44. Console.WriteLine("Inserire il numero corrispondente alla propriet da modificare:") 45. Cmd = Console.ReadLine 46. 47. If Cmd < 0 Or Cmd > Objects.Length - 1 Then 48. Console.WriteLine("Nessuna propriet corrispondente!") 49. Exit Sub 50. 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] SelectedProp.SetValue(Selected, NewValue, Nothing) 76. 77. Console.WriteLine("Propriet modificata!") 78. 79. PrintInfo(Selected, "") 80. 81. Console.ReadKey() 82. End Sub 83. 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.

007. 008. 009. 010. 011. 012. 013. 014. 015. 016. 017. 018. 019. 020. 021. 022. 023. 024. 025. 026. 027. 028. 029. 030. 031. 032. 033. 034. 035. 036. 037. 038. 039. 040. 041. 042. 043. 044. 045. 046. 047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078.

'vuole usare come identificatore. In generale, si possono usare 'le parentesi quadre per trasformare ogni keyword in un normale 'nome. Class [Function] Private _Expression As String Private EvaluateFunction As MethodInfo 'Contiene l'espressione matematica che costruisce il valore 'della funzione in base alla variabile x Public Property Expression() As String Get Return _Expression End Get Set(ByVal value As String) _Expression = value Me.CreateEvaluator() End Set End Property 'La prossima la procedura centrale di tutto l'esempio. 'Il suo compito consiste nel compilare una libreria di 'classi in cui definita una funzione che, ricevuto 'come parametro un x decimale, ne restituisce il valore 'f(x). Il corpo di tale funzione varia in base 'all'espressione immessa dall'utente. Private Sub CreateEvaluator() 'Crea il codice della libreria: una sola classe 'contenente la sola funzione statica Evaluate. Gli {0} 'verranno sostituti con caratteri di "a capo", mentre 'in {1} verr posta l'espressione che produce 'il valore desiderato, ad esempio "x ^ 2 + 1". Dim Code As String = String.Format( _ "Imports Microsoft.VisualBasic{0}" & _ "Imports System{0}" & _ "Imports System.Math{0}" & _ "Public Class Evaluator{0}" & _ " Public Shared Function Evaluate(ByVal X As Double) As Double{0}" & _ " Return {1}{0}" & _ " End Function{0}" & _ "End Class", Environment.NewLine, Me.Expression) 'Crea un nuovo oggetto CompilerParameters, per 'contenere le informazioni relative alla compilazione Dim Parameters As New CompilerParameters With Parameters 'Indica se creare un eseguibile o una libreria di 'classi: in questo caso ci interessa la seconda, 'quindi impostiamo la propriet su False .GenerateExecutable = False 'Gli warning vengono considerati come errori .TreatWarningsAsErrors = True 'Non vogliamo tenere alcun file temporaneo: ci 'interessa solo l'assembly compilato .TempFiles.KeepFiles = False 'L'assembly verr tenuto in memoria temporanea .GenerateInMemory = True 'I due riferimenti di cui abbiamo bisogno, che si 'trovano nella GAC (quindi basta specificarne il 'nome). In questo caso, si richiede anche 'l'estensione (*.dll) .ReferencedAssemblies.Add("Microsoft.VisualBasic.dll") .ReferencedAssemblies.Add("System.dll") End With 'Crea un nuovo provider di compilazione Dim Provider As New VBCodeProvider 'E compila il codice seguendo i parametri di 'compilazione forniti dall'oggetto Parameters. Il 'valore restituito dalla funzione

079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121. 122. 123. 124. 125. 126. 127. 128. 129. 130. 131. 132. 133. 134. 135. 136. 137. 138. 139. 140. 141. 142. 143. 144. 145. 146. 147. 148. 149. 150.

'CompileAssemblyFromSource di tipo 'CompilerResults e viene salvato in CompResults Dim CompResults As CompilerResults = _ Provider.CompileAssemblyFromSource(Parameters, Code) 'Se ci sono errori, lancia un'eccezione If CompResults.Errors.Count > 0 Then Throw New FormatException("Espressione non valida!") Else 'Altrimenti crea un riferimento all'assembly di 'output. La propriet CompiledAssembly di 'CompResults contiene un riferimento diretto a 'quell'assembly, quindi ci molto comoda. Dim Asm As Reflection.Assembly = CompResults.CompiledAssembly 'Dall'assembly ottiene un OT che rappresenta 'l'unico tipo ivi definito, e da questo ne 'estrae un MethodInfo con informazioni sul 'metodo Evaluate (l'unico presente). Me.EvaluateFunction = _ Asm.GetType("Evaluator").GetMethod("Evaluate") End If End Sub 'Per richiamare la funzione, basta invocare il metodo 'Evaluate estratto precedentemente. Al pari delle 'propriet e dei campi, che possono essere letti o 'scritti dinamicamente, anche i metodi possono essere 'invocati dinamicamete attraverso MethodInfo. Si usa 'la funzione Invoke: il primo parametro da 'passare l'oggetto da cui richiamare il metodo, mentre 'il secondo un array di oggetti che indicano i 'parametri da passare a tale metodo. 'In questo caso, il primo parametro Nothing poich 'Evaluate una funzione statica e non ha bisgno di nessuna 'istanza per essere richiamata. Public Function Apply(ByVal X As Double) As Double Return EvaluateFunction.Invoke(Nothing, New Object() {X}) End Function End Class Sub Main() Dim F As New [Function]() Do Try

Loop

Console.Clear() Console.WriteLine("Inserisci una funzione: ") Console.Write("f(x) = ") F.Expression = Console.ReadLine Exit Do Catch ex As Exception Console.WriteLine("Espressione non valida!") Console.ReadKey() End Try

Dim Input As String Dim X As Double Do Try

Console.Clear() Console.WriteLine("Immettere 'stop' per terminare.") Console.WriteLine("Il programma calcola il valore di f in X: ") Console.Write("x = ") Input = Console.ReadLine If Input <> "stop" Then X = CType(Input, Double) Console.WriteLine("f(x) = {0}", F.Apply(X)) Console.ReadKey() Else

Exit Do 151. End If 152. Catch Ex As Exception 153. Console.WriteLine(Ex.Message) Console.ReadKey() 154. 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) _Name = value 024. End Set 025. 026. End Property 027. Public Sub New() 028. _Fields = New Dictionary(Of String, String) 029. End Sub 030. 031. 032.

033. 034. 035. 036. 037. 038. 039. 040. 041. 042. 043. 044. 045. 046. 047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099. 100. 101. 102.

'Genera il codice della propriet Public Function GenerateCode() As String Dim Code As New Text.StringBuilder() Code.AppendLine("Class " & Name) For Each Field As String In Me.Fields.Keys Code.AppendFormat("Private _{0} As {1}{2}", _ Field, Me.Fields(Field), Environment.NewLine) Code.AppendFormat( _ "Public Property {0} As {1}{2}" & _ " Get{2}" & _ " Return _{0}{2}" & _ " End Get{2}" & _ " Set(ByVal value As {1}){2}" & _ " _{0} = value{2}" & _ " End Set{2}" & _ "End Property{2}", _ Field, Me.Fields(Field), Environment.NewLine) Next Code.AppendLine("End Class") Return Code.ToString() End Function End Class 'Classe statica contenente la funzione per scrivere 'e generare il nuovo programma Class ProgramCreator 'Accetta come input il nuovo tipo di dato 'da gestire. Restituisce in output il percorso 'dell'eseguibile creato Public Shared Function CreateManagingProgram(ByVal T As TypeCreator) As String Dim Code As New Text.StringBuilder() Code.AppendLine("Imports System") Code.AppendLine("Imports System.Collections.Generic") Code.AppendLine("Module Module1") Code.AppendLine(T.GenerateCode()) Code.AppendLine("Sub Main()") Code.AppendLine(" Dim Storage As New List(Of " & T.Name & ")") Code.AppendLine(" Dim Cmd As Char") Code.AppendLine(" Do") Code.AppendLine(" Console.Clear()") Code.AppendLine(" Console.WriteLine(""Inserimento di oggetti " & T.Name & """)") Code.AppendLine(" Console.WriteLine()") Code.AppendLine(" Console.Writeline(""Scegliere un'operazione: "")") Code.AppendLine(" Console.WriteLine("" i - inserimento;"")") Code.AppendLine(" Console.WriteLine("" e - elenca;"")") Code.AppendLine(" Console.WriteLine("" u - uscita."")") Code.AppendLine(" Cmd = Console.ReadKey().KeyChar") Code.AppendLine(" Console.Clear()") Code.AppendLine(" Select Case Cmd") Code.AppendLine(" Case ""i"" ") Code.AppendLine(" Dim O As New " & T.Name & "()") Code.AppendLine(" Console.WriteLine(""Inserire i dati: "")") 'Legge ogni membro del nuovo tipo. Usa la CType 'per essere sicuri che tutto venga interpretato nel 'modo corretto. For Each Field As String In T.Fields.Keys Code.AppendFormat("Console.Write("" {0} = ""){1}", Field, Environment.NewLine) Code.AppendFormat("O.{0} = CType(Console.ReadLine(), {1}){2}", Field, T.Fields(Field), Environment.NewLine) Next Code.AppendLine(" Storage.Add(O)") Code.AppendLine(" Console.WriteLine(""Inserimento completato!"")") Code.AppendLine(" Case ""e"" ") Code.AppendLine(" For I As Int32 = 0 To Storage.Count - 1") Code.AppendLine(" Console.WriteLine(""{0:000} + "", I)")

103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121. 122. 123. 124. 125. 126. 127. 128. 129. 130. 131. 132. 133. 134. 135. 136. 137. 138. 139. 140. 141. 142. 143. 144. 145. 146. 147. 148. 149. 150. 151. 152. 153. 154. 155. 156. 157. 158. 159. 160. 161. 162. 163. 164. 165. 166. 167. 168. 169. 170. 171. 172. 173.

'Fa scrivere una linea per ogni propriet 'dell'oggetto, mostrandone il valore For Each Field As String In T.Fields.Keys Code.AppendFormat("Console.WriteLine("" {0} = "" & Storage(I). {0}.ToString()){1}", Field, Environment.NewLine) Next Code.AppendLine(" Next") Code.AppendLine(" Console.ReadKey()") Code.AppendLine(" End Select") Code.AppendLine(" Loop Until Cmd = ""u""") Code.AppendLine("End Sub") Code.AppendLine("End Module") Dim Parameters As New CompilerParameters With Parameters .GenerateExecutable = True .TreatWarningsAsErrors = True .TempFiles.KeepFiles = False .GenerateInMemory = False .ReferencedAssemblies.Add("Microsoft.VisualBasic.dll") .ReferencedAssemblies.Add("System.dll") End With Dim Provider As New VBCodeProvider Dim CompResults As CompilerResults = _ Provider.CompileAssemblyFromSource(Parameters, Code.ToString()) If CompResults.Errors.Count = 0 Then Return CompResults.PathToAssembly Else For Each E As CompilerError In CompResults.Errors Stop Next Return Nothing End If End Function End Class Sub Main() Dim NewType As New TypeCreator() Dim I As Int16 Dim Field, FieldType As String Console.WriteLine("Creazione di un tipo") Console.WriteLine() Console.Write("Nome del tipo = ") NewType.Name = Console.ReadLine Console.WriteLine("Inserisci il nome del campo e il suo tipo:") I = 1 Do Console.Write("Nome campo {0}: ", I) Field = Console.ReadLine If String.IsNullOrEmpty(Field) Then Exit Do End If Console.Write("Tipo campo {0}: ", I) FieldType = Console.ReadLine 'Dovrete immettere il nome completo e con 'le maiuscole al posto giusto. Ad esempio: ' System.String 'e non string o system.String o STring. If Type.GetType(FieldType) Is Nothing Then Console.WriteLine("Il tipo {0} non esiste!", FieldType)

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 'Per creare un array a estremi variabili necessario 03. 'usare una funzione della classe Array, ed altrettanto 04. 'necessario dichiarare l'array come di tipo Array, anche se 05. 'questo solitamente sconsigliato. Per creare il 06. 07. 'suddetto oggetto, bisogna passare alla funzione tre 'parametri: 08. 09. ' - il tipo degli oggetti che l'array contiene; 10. ' - un array contenente le lunghezze di ogni rango dell'array; ' - un array contenente gli indici iniziali di ogni rango. 11. 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. CustomArray(3) = 1 18. 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. 02. 03. 04. 05. 06. 07. 08. 09. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. <Conditional("DEBUG")> _ Sub WriteStatus() 'Scriva a schermo la quantit di memoria usata, 'senza aspettare la prossima garbage collection Console.WriteLine("Memory: {0}", GC.GetTotalMemory(False)) End Sub 'Crea un nuovo oggetto Function CreateObject(Of T As New)() As T Dim Result As New T 'Richiama WriteStatus: questa chiamata viene IGNORATA 'in qualsiasi caso, tranne quando siamo in debug. WriteStatus() Return Result End Function Sub Main() Dim k As Text.StringBuilder = _ CreateObject(Of Text.StringBuilder)() '... 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. 'Questo un nuovo attributo completamente vuoto. 005. 'L'informazione che trasporta consiste nel fatto stesso 006. 'che esso sia applicato ad un membro. 007. 008. 'Nel metodo di cronometraggio, rintracceremo e useremo 'solo i metodi a cui sia stato assegnato questo attributo. 009. Public Class TimeAttribute 010. 011. Inherits Attribute 012. 013. End Class 014. 015. 'I prossimi quattro metodi sono procedure di test. Ognuna 'esegue una certa operazione 100mila o 10 milioni di volte. 016. 017. 018. <Time()> _ Sub AppendString() 019. Dim S As String = "" 020. 021. For I As Int32 = 1 To 100000 S &= "a" 022. Next 023. S = Nothing 024. End Sub 025. 026. <Time()> _ 027. Sub AppendBuilder() 028. Dim S As New Text.StringBuilder() 029. For I As Int32 = 1 To 100000 030. S.Append("a") 031. Next 032. S = Nothing 033. End Sub 034. 035. <Time()> _ 036. Sub SumInt32() 037. Dim S As Int32 038. For I As Int32 = 1 To 10000000 039. S += 1 040. Next 041. End Sub 042. 043. <Time()> _ 044. Sub SumDouble() 045. Dim S As Double 046. For I As Int32 = 1 To 10000000 047. S += 1.0 048. Next 049. End Sub 050. 051. 'Questa procedura analizza il tipo T e ne estrae tutti 052. 'i metodi statici e senza parametri marcati con l'attributo 053. 'Time, quindi li esegue e li cronometra, poi riporta 054. 'i risultati a schermo per ognuno. 055. 'Vogliamo che i metodi siano statici e senza parametri 056. 'per evitare di raccogliere tutte le informazioni per la 057. 'funzione Invoke. 058. Sub ReportTiming(ByVal T As Type) 059. Dim Methods() As MethodInfo = T.GetMethods() 060. Dim TimeType As Type = GetType(TimeAttribute) 061. Dim TimeMethods As New List(Of MethodInfo) 062. 063. 'La funzione GetCustomAttributes accetta due parametri 064. 'nel secondo overload: il primo il tipo di 065. 'attributo da cercare, mentre il secondo specifica se 066. 'cercare tale attributo in tutto l'albero di 067. 'ereditariet del membro. Restituisce come 068. 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.

073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. End

'vuoto, ossia se il metodo stato marcato con Time For Each M As MethodInfo In Methods If M.GetCustomAttributes(TimeType, False).Length > 0 And _ M.GetParameters().Count = 0 And _ M.IsStatic Then TimeMethods.Add(M) End If Next Methods = Nothing 'La classe Stopwatch rappresenta un cronometro. Start 'per farlo partire, Stop per fermarlo e Reset per 'resettarlo a 0 secondi. Dim Crono As New Stopwatch For Each M As MethodInfo In TimeMethods Crono.Reset() Crono.Start() M.Invoke(Nothing, New Object() {}) Crono.Stop() Console.WriteLine("Method: {0}", M.Name) Console.WriteLine(" Time: {0}ms", Crono.ElapsedMilliseconds) Next TimeMethods.Clear() TimeMethods = Nothing End Sub Sub Main() Dim This As Type = GetType(Module1) 'Non vi allarmate se il programma non stampa nulla 'per qualche secondo. Il primo metodo molto 'lento XD ReportTiming(This) Console.ReadKey() End Sub Module

Ecco i r isultati del benchmar king (ter mine tecnico) sul mio por tatile: Method: Time: Method: Time: Method: Time: Method: Time: AppendString 4765ms AppendBuilder 2ms SumInt32 27ms SumDouble 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 'debba essere valorizzata dall'utente e se sia 004. 'obbligatoria o meno. Il costruttore impone un solo 005. 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. 'Come specificato dalle convenzioni, la propriet 010. 'impostata nel costruttore ReadOnly, mentre 011. 012. 'le altre (l'altra) normale. 013. <AttributeUsage(AttributeTargets.Property)> _ 014. Class UserInputAttribute 015. Inherits Attribute 016. Private _IsUserScope As Boolean 017. Private _IsCompulsory As Boolean = False 018. 019. Public ReadOnly Property IsUserScope() As Boolean 020. Get 021. Return _IsUserScope 022. End Get 023. End Property 024. 025. Public Property IsCompulsory() As Boolean 026. Get 027. Return _IsCompulsory 028. End Get 029. Set(ByVal value As Boolean) 030. _IsCompulsory = value 031. End Set 032. End Property 033. 034. Sub New(ByVal IsUserScope As Boolean) 035. _IsUserScope = IsUserScope 036. End Sub 037. 038. End Class 039. 040. 'Cubo 041. Class Cube 042. Private _SideLength As Single 043. Private _Density As Single 044. Private _Cost As Single 045. 046. 'Se i parametri del costruttore vanno specificati 047. 'tra parentesi quando si assegna l'attributo, allora 048. 'come si fa a impostare le altre propriet 049. 'facoltative? Si usa un particolare operatore di 050. 'assegnamento ":=" e si impostano esplicitamente 051. 'i valori delle propriet ad uno ad uno, 052. 'separati da virgole, ma sempre nelle parentesi. 053. <UserInput(True, IsCompulsory:=True)> _ 054. Public Property SideLength() As Single 055. Get 056. Return _SideLength 057. End Get 058. Set(ByVal value As Single) 059. _SideLength = value 060. End Set 061. End Property 062. 063. 064.

065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121. 122. 123. 124. 125. 126. 127. 128. 129. 130. 131. 132. 133. 134. 135.

<UserInput(True)> _ Public Property Density() As Single Get Return _Density End Get Set(ByVal value As Single) _Density = value End Set End Property 'Cost non verr chiesto all'utente <UserInput(False)> _ Public Property Cost() As Single Get Return _Cost End Get Set(ByVal value As Single) _Cost = value End Set End Property End Class 'Crea un oggetto di tipo T richiendendo all'utente di 'impostare le propriet marcate con UserInput 'in cui IsUserScope True. Function GetInfo(ByVal T As Type) As Object Dim O As Object = T.Assembly.CreateInstance(T.FullName) For Each PI As PropertyInfo In T.GetProperties() If Not PI.CanWrite Then Continue For End If Dim Attributes As Object() = PI.GetCustomAttributes(GetType(UserInputAttribute), True) If Attributes.Count = 0 Then Continue For End If 'Ottiene il primo (e l'unico) elemento dell'array, 'un oggetto di tipo UserInputAttribute che rappresenta 'l'attributo assegnato e contiene tutte le informazioni 'passate, sottoforma di propriet. Dim Attr As UserInputAttribute = Attributs(0) 'Se la propriet non richiesta all'utente, 'allora continua il ciclo If Not Attr.IsUserScope Then Continue For End If Dim Value As Object = Nothing 'Se obbligatoria, continua a richiederla 'fino a che l'utente non immette un valore corretto. If Attr.IsCompulsory Then Do Try Console.Write("* {0} = ", PI.Name) Value = Convert.ChangeType(Console.ReadLine, PI.PropertyType) Catch Ex As Exception Value = Nothing Console.WriteLine(Ex.Message) End Try Loop Until Value IsNot Nothing Else 'Altrimenti la richiede una sola volta Try Console.Write("{0} = ", PI.Name) Value = Convert.ChangeType(Console.ReadLine, PI.PropertyType) Catch Ex As Exception

136. 137. 138. 139. 140. 141. 142. 143. 144. 145. 146. 147. 148. 149. 150. 151. 152. 153. 154. 155. 156. 157. 158. 159. End

Next

Value = Nothing End Try End If If Value IsNot Nothing Then PI.SetValue(O, Value, Nothing) End If

Return O End Function Sub Main() Dim O As Object Console.WriteLine("Riempire i campi (* = obbligatorio):") O = GetInfo(GetType(Cube)) 'Stampa i valori con il metodo PrintInfo scritto qualche 'capitolo fa PrintInfo(O, "") Console.ReadKey() End Sub 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 Sub Main() 04. Stringa = "Ciao" 05. 06. 07. For I = 1 To 20 08. If I > 10 Then 09. Strnga = I End If 10. Console.WriteLine(Stringa) 11. Next 12. 13. Console.ReadKey() 14. End Sub 15. 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() Dim I As Int32 04. Dim P As Student 05. 06. 07. 'Conversione implicita da Double a Int32: viene 08. 'segnalata come errore I = 4.0 09. 'Conversione implicita da Person a Student: viene 10. 'segnalata come errore 11. 12. P = New Person("Mario", "Rossi", New Date(1968, 9, 12)) 13. Console.ReadKey() 14. 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() 'Infer disattivata: I viene considerata di 05. 'tipo Object 06. 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() Dim S1, S2 As String 04. Dim C1, C2 As Dictionary(Of Char, Int32) 05. Dim Result As Boolean = True 06. 07. Console.Write("Stringa 1: ") 08. S1 = Console.ReadLine 09. Console.Write("Stringa 2: ") 10. S2 = Console.ReadLine 11. 12. 13. If S1.Length = S2.Length Then C1 = New Dictionary(Of Char, Int32) 14. 15. For I As Int16 = 0 To S1.Length - 1 If C1.ContainsKey(S1.Chars(I)) Then 16. 17. C1(S1.Chars(I)) += 1 18. Else C1.Add(S1.Chars(I), 1) 19. End If 20. Next 21. 22. C2 = New Dictionary(Of Char, Int32) 23. For I As Int16 = 0 To S2.Length - 1 24. If C2.ContainsKey(S2.Chars(I)) Then 25. C2(S2.Chars(I)) += 1 26. Else 27. C2.Add(S2.Chars(I), 1) 28. End If 29. Next 30. 31. If C1.Keys.Count = C2.Keys.Count Then 32. For Each C As Char In C1.Keys 33. If Not C2.ContainsKey(C) Then 34. Result = False 35. ElseIf C1(C) <> C2(C) Then 36. Result = False 37. End If 38. If Not Result Then 39. Exit For 40. End If 41. Next 42. Else 43. Result = False 44. End If 45. Else 46. Result = False 47. End If 48. 49. If Result Then 50. Console.WriteLine("Sono anagrammi!") 51. 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. 02. 03. 04. 05. 06. 07. 08. 09. 10. 'Data l'equazione di un'ellisse: 'x^2 y^2. '--- + --- = 1 'a^2 b^2 'Ricava x: 'x^2 / a^2 = 1 - (y^2 / b^2) 'x^2 = (1 - (y^2 / b^2)) * a^2 'x = sqrt((1 - (y^2 / b^2)) * a^2) 'Prende la soluzione positiva: 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. 02. 03. 04. 05. 06. 07. 08. 09. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. Public Function Fattoriale(ByVal X as byte) As UInt64 If X = 1 Then Return 1 Else Dim T As UInt64 = 1 For I As Byte = 1 To X T *= I Next Return T End If End Function 'Diventa: Public Function Fattoriale(ByVal X as byte) As UInt64 If X = 1 Then Return 1 Else Return X * Fattoriale(X - 1) End If 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. 06. 07. 08. 09. 10. 11. 12. 13. 14. 15.

W.Write(R.ReadToEnd) R.Close() W.Close() End Sub 'Diventa IO.File.Copy(Da, A) 'Spesso anche il non conoscere tutte le possibilit '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 icoconcettuale 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 'elementi che pu essere ordinata con l'algoritmo 004. 'Bubble Sort gi analizzato 005. 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 'disporre di tutti i metodi delle liste scrivendo 009. 010. 'solo una linea di codice 011. Inherits List(Of T) 012. 013. 'Questo campo indica il numero di millisecondi impiegati 'ad ordinare tutta la collezione 014. 015. Private _TimeElapsed As Single = 0 016. 017. Public ReadOnly Property TimeElapsed() As Single 018. Get Return _TimeElapsed 019. End Get 020. End Property 021. 022. 'Ecco gli eventi: 023. 'Il primo viene lanciato prima che inizi la procedura di 024. 'ordinamento, e per tale motivo di tipo 025. 'CancelEventHandler. Questo delegate espone come 026. 'secondo parametro della signature una variabile "e" 027. 'al cui intero disponibile una propriet 028. 'Cancel che indica se cancellare oppure no l'operazione. 029. 'Se si volesse cancellare l'operazione sarebbe possibile 030. 'farlo nell'evento BeforeSorting. 031. 'In genere, si usa il tipo CancelEventHandler o un suo 032. 'derivato ogni volta che bisogna gestire un evento 033. 'che inizia un'operazione annullabile. 034. Event BeforeSorting As System.ComponentModel.CancelEventHandler 035. 'Il secondo viene lanciato dopo aver terminato la procedura 036. 'di ordinamento e serve solo a notificare un'azione 037. 'avvenuta. Il tipo un semplicissimo EventHandler 038. Event AfterSorting As EventHandler 039. 040. 'Scambia l'elemento alla posizione Index con il suo 041. 'successivo 042. Private Sub SwapInList(ByVal Index As Int32) 043. Dim Temp As T = Me(Index + 1) 044. Me.RemoveAt(Index + 1) 045. Me.Insert(Index, Temp) 046. End Sub 047. 048. 'In List(Of T) gi presente un metodo Sort, 049. 'perci bisogna oscurarlo con Shadows (in quanto non 050. ' sovrascrivibile con il polimorfismo) 051. Public Shadows Sub Sort() 052. Dim Occurrences As Int32 053. Dim J As Int32 054. Dim Time As New Stopwatch 055. 'Attenzione! non bisogna confondere EventHandlers con 056. 'EventArgs: il primo un tipo delegate e costituisce 057. 'il tipo dell'evento; il secondo un normale tipo 058. 'reference e rappresenta tutti gli argomenti opzionali 059. 'inerenti alle operazioni svolte 060. Dim e As New System.ComponentModel.CancelEventArgs 061. 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 End If 086. 087. If Me(I).CompareTo(Me(I + 1)) = 1 Then SwapInList(I) 088. Occurrences += 1 089. End If 090. Next 091. J += 1 092. Loop Until Occurrences = 0 093. Time.Stop() 094. _TimeElapsed = Time.ElapsedMilliseconds 095. 096. 'Qui genera semplicemente l'evento 097. RaiseEvent AfterSorting(Me, EventArgs.Empty) 098. End Sub 099. 100. End Class 101. 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) Me.RemoveAt(Index + 1) 019. Me.Insert(Index, Temp) 020. End Sub 021. 022. Public Shadows Sub Sort() 023. Dim Occurrences As Int32 024. Dim J As Int32 025. Dim Time As New Stopwatch 026. Dim e As New System.ComponentModel.CancelEventArgs 027. 028. RaiseEvent BeforeSorting(Me, e) 029. If e.Cancel Then 030. Exit Sub 031. End If 032. 033. Time.Start() 034. J = 0 035. Do 036. Occurrences = 0 037. For I As Int32 = 0 To Me.Count - 1 - J 038. If I = Me.Count - 1 Then 039. Continue For 040. End If 041. If Me(I).CompareTo(Me(I + 1)) = 1 Then 042. SwapInList(I) 043. Occurrences += 1 044. End If 045. Next 046. J += 1 047. Loop Until Occurrences = 0 048. Time.Stop() 049. _TimeElapsed = Time.ElapsedMilliseconds 050. 051. 'Qui genera semplicemente l'evento 052. RaiseEvent AfterSorting(Me, EventArgs.Empty) 053. End Sub 054. 055. End Class 056. 057. 'Bubble WithEvents poich ne utilizzeremo 058. 'gli eventi 059. WithEvents Bubble As New BubbleCollection(Of Int32) 060. Sub Main() 061. Dim I As Int32 062. 063. Console.WriteLine("Inserire degli interi (0 per terminare):") 064. I = Console.ReadLine 065. Do While I <> 0 066. 067.

068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099. 100. 101. 102. End

Loop

Bubble.Add(I) I = Console.ReadLine

'Il corpo di Main termina con l'esecuzione di Sort, ma 'il programma non finisce qui, poich Sort 'scatena due eventi, BeforeSorting e AfterSorting. 'Questi comportano l'esecuzione prima del metodo 'Bubble_BeforeSorting e poi di Bubble_AfterSorting. 'Vedrete bene il risultato eseguendo il programma Bubble.Sort() End Sub Private Sub Bubble_BeforeSorting(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles Bubble.BeforeSorting If Bubble.Count = 0 Then e.Cancel = True Console.WriteLine("Lista vuota!") Console.ReadKey() End If End Sub Private Sub Bubble_AfterSorting(ByVal sender As Object, ByVal e As EventArgs) Handles Bubble.AfterSorting Console.WriteLine("Lista ordinata:") 'Scrive a schermo tutti gli elementi di Bubble 'mediante un delegate generico. Bubble.ForEach(AddressOf Console.WriteLine) Console.WriteLine("Tempo impiegato: {0} ms", Bubble.TimeElapsed) Console.ReadKey() End Sub 'Handles significa "gestisce". In questo come in molti altri 'casi, il codice molto simile al linguaggio. 'Ad esempio, traducendo in italiano si avrebbe: ' Bubble_AfterSorting gestisce Bubble.AfterSorting 'Il VB molto chiaro nelle keywords 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. 2. 3. 4. 'Add Handler = Aggiungi Gestore; molto intuitiva come keyword AddHandler [Oggetto].[Evento], AddressOf [Gestore] 'E per rimuovere il gestore dall'invocation list: 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.

21. 22. 23. 24. 25. 26. 27. 28.

'il programma non finisce qui, poich Sort 'scatena due eventi, BeforeSorting e AfterSorting. 'Questi comportano l'esecuzione prima del metodo 'Bubble_BeforeSorting e poi di Bubble_AfterSorting. 'Vedrete bene il risultato eseguendo il programma Bubble.Sort() End Sub Private Sub Bubble_BeforeSorting(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) If Bubble.Count = 0 Then e.Cancel = True Console.WriteLine("Lista vuota!") Console.ReadKey() End If End Sub

29. 30. 31. 32. 33. 34. 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").

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 Try 04. Dim Radius As Single = txtRadius.Text 05. 06. Dim Area As Single 07. Area = Radius ^ 2 * Math.PI MessageBox.Show("L'area del cerchio " & Area & ".", Me.Text, 08. MessageBoxButtons.OK, MessageBoxIcon.Information) 09. Catch ICE As InvalidCastException 10. MessageBox.Show("Inserire un valore numerico valido!", Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Error) End Try 11. End Sub 12. 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 MessageBox.Show("Il raggio non pu essere negativo o nullo!", Me.Text, 17. 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 File = New IO.FileStream(FileName, IO.FileMode.Open) 21. 22. 23. Dim Buffer() As Byte Dim Number, ReadBytes As Int32 24. 25. 26. 'Chiede all'utente quanti bytes vuole leggere, e 'memorizza tale numero in Number 27. Console.WriteLine("Quanti bytes leggere?") 28. Number = CType(Console.ReadLine, Int32) 29. 'Se Number un numero positivo e non siamo ancora 30. 31. 'arrivati alla fine del file, allora legge quei bytes. 'La propriet Position restituisce la posizione 32. 'corrente all'interno del file (a iniziare da 0), mentre 33. 'File.Length restituisce la lunghezza del file, in bytes. 34. Do While (Number > 0) And (File.Position < File.Length - 1) 35. 'Ridimensiona il buffer 36. ReDim Buffer(Number - 1) 37. 'Legge Number bytes e li mette in Buffer, a partire 38. 'dall'inizio dell'array. Read una funzione, e 39. 'restituisce come risultato il numero di bytes 40. 'effettivamente letti dallo stream. 41. ReadBytes = File.Read(Buffer, 0, Number) 42. 43. Console.WriteLine("Bytes letti:") 44. For I As Int32 = 0 To ReadBytes - 1 45. Console.Write("{0:000} ", Buffer(I)) 46. 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. 2. 3. 4. 5. 6. 'Si sposta alla posizione 100 File.Seek(100, IO.SeekOrigin.Begin) 'Si sposta di 250 bytes indietro rispetto alla posizione corrente File.Seek(-250, IO.SeekOrigin.Current) 'Si sposta a 100 bytes dalla fine del file 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 Dim Mode As Char 04. 05. Console.WriteLine("Premere R per leggere un file, W per scriverne uno.") 06. 07. 'Console.ReadKey restituisce un oggetto ConsoleKeyInfo, 08. 'al cui interno ci sono tre propriet: Key, 'enumeratore che definisce il codice del pulsante premuto; 09. 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 Mode = Console.ReadKey.KeyChar 14. 15. 'Dato che potrebbe essere attivo il Bloc Num, ci si 'assicura che Mode contenga un carattere maiuscolo 16. 17. 'con la funzione statica ToUpper del tipo base Char 18. Mode = Char.ToUpper(Mode) 'Pulisce lo schermo 19. Console.Clear() 20. 21. Select Case Mode 22. Case "R" 23. Console.WriteLine("Inserire il percorso del file da leggere:") 24. File = Console.ReadLine 25. 26. 'Cosntrolla che il file esista 27. If Not IO.File.Exists(File) Then 28. 'Se non esiste, visualizza un messggio ed esce 29. Console.WriteLine("Il file specificato non esiste!") 30. Console.ReadKey() 31. Exit Sub 32. End If 33. 34. Dim Reader As New IO.StreamReader(File) 35. 36. 'Legge ogni singola riga del file, fintanto che non 37. 'si raggiunta la fine 38. Do While Not Reader.EndOfStream 39. 'Come Console.Readline, la funzione d'istanza 40. 'ReadLine restituisce una linea di testo 41. 'dal file 42. Console.WriteLine(Reader.ReadLine) 43. Loop 44. 45. 'Quindi chiude il file 46. Reader.Close() 47. Case "W" 48. Console.WriteLine("Inserire il percorso del file da creare:") 49. File = Console.ReadLine 50. 51. Dim Writer As New IO.StreamWriter(File) 52. Dim Line As String 53. 54. Console.WriteLine("Immettere il testo del file, " & _ 55. "premere due volte invio per terminare") 56. 'Fa immettere righe di testo fino a quando 57. 'si termina 58. Do 59. Line = Console.ReadLine 60. 'Come Console.WriteLine, la funzione d'istanza 61. 'WriteLine scrive una linea di testo sul file 62. 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.

12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36.

Exit Sub End If Dim File As New IO.FileStream(txtPath.Text, IO.FileMode.Open) 'Converte il testo digitato in una sequenza di bytes, 'secondo gli standard della codifica UTF8 Dim TextBytes() As Byte = _ System.Text.Encoding.UTF8.GetBytes(txtText.Text) 'Va alla fine del file File.Seek(0, IO.SeekOrigin.End) 'Scrive i bytes File.Write(TextBytes, 0, TextBytes.Length) File.Close() MessageBox.Show("Testo nascosto con successo!", Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Information) End Sub Private Sub btnRead_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnRead.Click If Not IO.File.Exists(txtPath.Text) Then MessageBox.Show("File inesistente!", Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Error) Exit Sub End If If IO.Path.GetExtension(txtPath.Text) <> ".jpg" Then MessageBox.Show("Il file deve essere in formato JPEG!", Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Exclamation) Exit Sub End If

37. 38. 39. Dim File As New IO.FileStream(txtPath.Text, IO.FileMode.Open) 40. Dim TextBytes() As Byte 41. Dim B1, B2 As Byte 42. 43. 'Legge un byte 44. B1 = File.ReadByte() 45. Do 46. 'Legge un altro byte 47. B2 = File.ReadByte() 48. 'Se i bytes formano la sequenza FF D9, si ferma. 49. 'In Visual Basic, in numeri esadecimali si scrivono 50. 'facendoli precedere da "&H" 51. If B1 = &HFF And B2 = &HD9 Then 52. Exit Do 53. End If 54. 'Passa il valore di B2 in B1 55. B1 = B2 56. Loop While (File.Position < File.Length - 1) 57. 58. ReDim TextBytes(File.Length - File.Position - 1) 59. 'Legge ci che rimane dopo FF D9 60. File.Read(TextBytes, 0, TextBytes.Length) 61. File.Close() 62. 63. txtText.Text = System.Text.Encoding.UTF8.GetString(TextBytes) 64. End Sub 65. 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. Class Item 03. Private Shared IDCounter As Int32 = 0 04. 05. Private _ID As Int32 06. Private _Description As String 07. 08. Public ReadOnly Property ID() As Int32 09. Get 10. Return _ID 11. End Get 12. End Property 13. 14. Public Property Description() As String 15. Get 16. Return _Description 17. End Get 18. Set(ByVal value As String) 19. _Description = value 20. End Set 21. End Property 22. 23. Sub New() 24. _ID = IDCounter 25. IDCounter += 1 26. End Sub 27. 28. End Class 29. 30. Private Sub btnDoSomething_Click(ByVal sender As Object, ByVal e As EventArgs) 31. Handles btnDoSomething.Click lstItems.Items.Add(New Item() With {.Description = "Asus Eee PC 900"}) 32. lstItems.Items.Add(New Item() With {.Description = "Hp Pavillion Dv6000"}) 33. End Sub 34. 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. Public Overrides Function ToString() As String 4. Return Me.Description 5. 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.

14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. End

'Number Number = Rnd.Next(0, 100) 'Imposta Tried su False Tried = False 'Nasconde la soluzione lblNumber.Text = "***" End Sub Private Sub cmdTry_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles cmdTry.Click 'Se si gi provato, esce dalla procedura If Tried Then MessageBox.Show("Hai gi fatto un tentativo! Premi " & _ "Genera e prova con un altro numero!", "Numeri a caso", _ MessageBoxButtons.OK, MessageBoxIcon.Exclamation) Exit Sub End If 'Se NumericUpDown corrisponde al numero generato, 'l'utente vince If nudValue.Value = Number Then MessageBox.Show("Complimenti, hai vinto!", "Numeri a caso", _ MessageBoxButtons.OK, MessageBoxIcon.Information) Else MessageBox.Show("Risposta sbagliata!", "Numeri a caso", _ MessageBoxButtons.OK, MessageBoxIcon.Exclamation) End If 'Ormai l'utente ha fatto la sua scelta Tried = True 'Fa vedere la soluzione lblNumber.Text = Number End Sub 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 'Per ulteriori informazioni sui controlli OpenFileDialog e 24. 25.

26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. End

'SaveFileDialog vedere capitolo relativo Dim Open As New OpenFileDialog Open.Filter = "File immagine|*.jpg;*.jpeg;*.gif;*.png;*.bmp;" & _ "*.tif;*.tiff;*.emf;*.exif;*.wmf" If Open.ShowDialog = Windows.Forms.DialogResult.OK Then 'Apre l'immagine, caricandola dal file selezionato 'nella finestra di dialogo tramite la funzione 'statica FromFile imgPreview.Image = Image.FromFile(Open.FileName) End If End Sub Private Sub cmdSave_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles cmdSave.Click 'Se c' un'immagine da salvare, la salva If imgPreview.Image IsNot Nothing Then Dim Save As New SaveFileDialog Save.Filter = "File Jpeg|*.jpeg;*.jpg|File Bitmap|*.bmp|" & _ "File Png|*.png|File Gif|*.gif|File Tif|*.tif;" & _ "*.tiff|File Wmf|*.wmf|File Emf|*.emf" If Save.ShowDialog = Windows.Forms.DialogResult.OK Then 'Dato che la propriet Image di tipo Image, usa 'il metodo statico Save per salvare l'immagine imgPreview.Image.Save(Save.FileName) End If End If End Sub Private Sub cmdConvert_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles cmdConvert.Click 'Prima si converte l'immagine in Bitmap, dato che Image ' una classe astratta Dim Image As Bitmap = imgPreview.Image 'Variabile ausiliaria per i calcoli Dim TempColor As Color 'Attenzione! 'Alcuni formati non supportano SetPixel, come il formato 'Gif. Controllare di passare immagini di formato adeguato 'Itera su ogni pixel, e lo cambia di colore 'Scorre le righe di pixel una alla volta For X As Int32 = 0 To Image.Width - 1 'Quindi ogni pixel nella riga For Y As Int32 = 0 To Image.Height - 1 'Converte il colore TempColor = Image.GetPixel(X, Y) TempColor = ToGreyScale(TempColor) Image.SetPixel(X, Y, TempColor) Next 'Imposta il valore della progressbar su una percentuale 'che esprime il numero di righe analizzate prgConvert.Value = X * 100 / Image.Width 'Evita di bloccare il programma. Per ulteriori 'informazioni su Application e il namespace My, 'vedere capitolo relativo Application.DoEvents() Next 'Reimposta l'immagine finale imgPreview.Image = Image End Sub 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 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 i file di testo "File di

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. 02. 03. 04. 05. 06. 07. 08. 09. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49.

Private Sub cmdOpen_Click(ByVal sender As Object, ByVal e As EventArgs)_ Handles cmdOpen.Click 'La funzione ShowDialog visualizza la finestra di dialogo e 'restituisce quale pulsante stato premuto 'Se il pulsante corrisponde con OK, procediamo If FOpen.ShowDialog = Windows.Forms.DialogResult.OK Then 'Apre un file in lettura 'Usa la propriet FileName di FOpen, che restituisce il 'path del file selezionato: sicuro che il file esista 'perch l'utente ha premuto Ok e non ha chiuso la 'finestra di dialogo Dim R As New IO.StreamReader(FOpen.FileName) 'Legge tutto il testo del file e lo deposita nella textbox txtFile.Text = R.ReadToEnd 'Chiude il file R.Close() End If End Sub Private Sub cmdSve_Click(ByVal sender As Object, ByVal e As EventArgs) _ Handles cmdSave.Click 'Viene visualizzata la finestra di dialogo If FSave.ShowDialog = Windows.Forms.DialogResult.OK Then 'Apre un file in scrittura, di ci si assicura che 'l'utente acconsenta alla sovrascrittura se gi esistente 'mediante la propriet OverwritePrompt Dim W As New IO.StreamWriter(FSave.FileName) 'Scrive tutto il contenuto della textbox nel file W.Write(txtFile.Text) 'Chiude il file W.Close() End If End Sub Private Sub cmdClose_Click(ByVal sender As Object, ByVal e As EventArgs) _ Handles cmdClose.Click If txtFile.Text <> "" And _ FSave.ShowDialog = Windows.Forms.DialogResult.OK Then Dim W As New IO.StreamWriter(FSave.FileName) W.Write(txtFile.Text) W.Close() End If 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 Dim Reader As New IO.StreamReader(File) 04. 'Una stringa che rappresenta ogni singola riga del file 05. 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 'dove far derivare i relativi valori. 09. 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 lblStatus.Text = "Apertura del file in corso..." 16. 17. 18. 'Finch non si raggiunge la fine del file si continua 'a leggere 19. While Not Reader.EndOfStream 20. 'Leggiamo una linea di file (S) 21. Line = Reader.ReadLine 22. 'Se la linea diversa da una riga vuota 23. If Line <> Nothing Then 24. 'Se la linea inizia per "[" (significa che 25. 'un campo) 26. If Line.StartsWith("[") Then 27. 'Si aumenta FieldCount, che indica quanti campi 28. 'si sono gi letti (in base 0) 29. FieldCount += 1 30. 'Rimuove il primo carattere, ossia "[" 31. Line = Line.Remove(0, 1) 32. 'Rimuove dalla linea l'ultimo carattere, 33. 'ossia "]" 34. Line = Line.Remove(Line.Length - 1, 1) 35. 'Aggiunge una radice alla TreeView 36. trwIni.Nodes.Add(Line) 37. Else 38. 'Altrimenti, se la linea non inzia per ";", 39. 'ossia non un commento 40. If Not Line.StartsWith(";") Then 41. 'Aggiunge la linea come sotto-nodo 42. 'dell'ultimo campo inserito. La linea 43. 'conterr il valore in forma 44. ' [nome]=[contenuto] 45. 'Attenzione! Possono esserci commenti in 46. 'riga, quindi si deve prima controllare 47. 'di eliminarli 48. 'Se l'indice del carattere ";" nella riga 49. ' positivo... 50. If Line.IndexOf(";") > 0 Then 51. 'Rimuove tutto quello che viene dopo 52. 'il commento 53. Line = Line.Remove(Line.IndexOf(";")) 54. End If 55. trwIni.Nodes(FieldCount).Nodes.Add(Line) 56. End If 57. End If 58. End If 59. End While 60. 61.

62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91. 92. 93. 94. 95. 96. End

'Chiude il file Reader.Close() lblStatus.Text = "File aperto" End Sub Private Sub strOpen_Click(ByVal sender As Object, _ ByVal e As EventArgs) Handles strOpen.Click 'Ecco un esempio di OpenFileDialog da codice Dim FOpen As New OpenFileDialog FOpen.Filter = "Impostazioni di configurazione|*.ini" If FOpen.ShowDialog = Windows.Forms.DialogResult.OK Then ReadFile(FOpen.FileName) End If End Sub Private Sub strExit_Click(ByVal sender As Object, _ ByVal e As EventArgs) Handles strExit.Click 'Esce dal programma, chiudendo il form corrente Me.Close() End Sub Private Sub strClearList_Click(ByVal sender As Object, _ ByVal e As EventArgs) Handles strClearList.Click 'Mostra un messaggio di conferma prima di procedere If MessageBox.Show("Eliminare tutti gli elementi dela lista?", _ "INI Reader", MessageBoxButtons.YesNo, MessageBoxIcon.Question) = _ Windows.Forms.DialogResult.No Then 'Se si risponde di no, esce dalla procedura Exit Sub End If 'Elimina tutti i nodi trwIni.Nodes.Clear() End Sub 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:

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.

15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. End 31. End Sub 32. End Class

'Calcola i giorni secondo il modo gi visto nelle prime 'lezioni sulle classi e le propriet .AppendFormat("{0} giorni{1}", Seconds \ (60 * 60 * 24), vbCrLf) Seconds -= (Seconds \ (60 * 60 * 24)) * (60 * 60 * 24) 'E cos anche ore, minuti e secondi .AppendFormat("{0} ore{1}", Seconds \ 3600, vbCrLf) Seconds -= (Seconds \ 3600) * 3600 .AppendFormat("{0} minuti{1}", Seconds \ 60, vbCrLf) Seconds -= (Seconds \ 60) * 60 .AppendFormat("{0:n0} secondi", Seconds) 'Quindi mette il risultato come testo della label lblAge.Text = .ToString With

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:\WINDOWS\System32 (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. 02. 03. 04. 05. 06. 07. 08. 09. 10. 'Nuovo gruppo 'Testo esplicativo' Dim G As New ListViewGroup("Testo esplicativo") 'Nuovo elemento 'Elemento' Dim L As New ListViewItem("Elemento") 'Aggiunge il gruppo alla listview ListView1.Groups.Add(G) 'Setta il gruppo a cui L apparterr L.Group = G 'Aggiunge l'elemento alla lista 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.

09. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. End

Dim Values() As String = _ {txtProduct.Text, nudPrice.Value, nudQuantity.Value} 'Inizializza Item sulla base dei valori dati Item = New ListViewItem(Values) 'E lo aggiunge alla lista lstProducts.Items.Add(Item) End Sub Private Sub cmdDelSelected_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles cmdDelSelected.Click 'Analizza tutti gli elementi selezionati For Each SelItem As ListViewItem In lstProducts.SelectedItems 'E li rimuove dalla lista lstProducts.Items.Remove(SelItem) Next End Sub Private Sub cmdCalculate_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles cmdCalculate.Click 'Totale spesa Dim Total As Single = 0 'Prezzo unitario Dim Price As Single 'Quantit? Dim Quantity As Int32 For Each Item As ListViewItem In lstProducts.Items 'Ottiene i valori da ogni elemento Price = CSng(Item.SubItems(1).Text) Quantity = CInt(Item.SubItems(2).Text) Total += Price * Quantity Next MessageBox.Show("Totale della spesa: " & Total & ".", _ "Spesa", MessageBoxButtons.OK, MessageBoxIcon.Information) End Sub 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.

24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91. 92. 93. 94. End

Private Index As Int32 'Price True se ci si riferisce al Prezzo (elemento 1) 'oppure False se ci si riferisce alla quantit (elemento 2) Sub New(ByVal Price As Boolean) If Price Then Index = 1 Else Index = 2 End If End Sub Public Function Compare(ByVal x As ListViewItem, _ ByVal y As ListViewItem) As Integer _ Implements IComparer(Of ListViewItem).Compare 'Qui bisogna ottenere il prezzo o la quantit: ci si basa 'sul parametro passato al costruttore Dim Val1 As Single = x.SubItems(Index).Text Dim Val2 As Single = y.SubItems(Index).Text Return Val1.CompareTo(Val2) End Function End Class 'Scambia due elementi in una lista: dato che ListViewItem sono 'variabili reference, bisognerebbe clonarli per spostarne il valore, 'come si faceva con i valori value, in questo modo: 'Dim Temp As ListViewItem = L1.Clone() 'L1 = L2.Clone() 'L2 = Temp 'Ma si userebbe troppa memoria. Perci la via pi facile 'usare i metodi della lista per scambiare gli elementi Private Sub SwapInList(ByVal List As ListView, ByVal Index As Int32) Dim Temp As ListViewItem = List.Items(Index + 1) List.Items.RemoveAt(Index + 1) List.Items.Insert(Index, Temp) End Sub 'Ordina gli elementi con l'algoritmo Bubble Sort gi 'descritto nell'ultima lezione teorica Private Sub SortListViewItems(ByVal List As ListView, _ ByVal Comparer As IComparer(Of ListViewItem)) Dim Occurrences As Int32 = 0 Occurrences = 0 For I As Int32 = 0 To List.Items.Count - 1 If I = List.Items.Count - 1 Then Continue For End If If Comparer.Compare(List.Items(I), List.Items(I + 1)) = 1 Then SwapInList(List, I) Occurrences += 1 End If Next Loop Until Occurrences = 0 End Sub Private Sub lstProducts_ColumnClick(ByVal sender As System.Object, _ ByVal e As ColumnClickEventArgs) Handles lstProducts.ColumnClick Select Case e.Column Case 0 'Nome Me.SortListViewItems(lstProducts, New ProductComparer()) Case 1 'Prezzo Me.SortListViewItems(lstProducts, New NumberComparer(True)) Case 2 'Quantit Me.SortListViewItems(lstProducts, New NumberComparer(False)) End Select End Sub Class Do

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. 2. 3. 4. 5. 6. 'Nasconde il form dalla barra delle applicazioni Me.ShowInTaskBar = False 'Rende il form invisibile Me.Visible = False 'Se l'icona non gi visibile, la rende visibile 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 r idimensionamento IsSplitter Fix ed : deter mina se lo splitter fisso o pu muover si due pannelli debba r estar e di dimensioni fisse dur ante l'atto di

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 End Get 024. Set(ByVal Value As Boolean) 025. 026. _SyntaxHighlight_CaseSensitive = Value 027.

028. 029. 030. 031. 032. 033. 034. 035. 036. 037. 038. 039. 040. 041. 042. 043. 044. 045. 046. 047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099.

End Set End Property 'Contiene costanti usate nell'inviare messaggi all'API 'di windows Private Enum EditMessages LineIndex = 187 LineFromChar = 201 GetFirstVisibleLine = 206 CharFromPos = 215 PosFromChar = 1062 End Enum 'OnTextChanged una procedura privata che ha il compito 'di generare l'evento TextChanged: prima di farlo, colora il 'testo, ma in questo caso l'evento non viene pi generato Protected Overrides Sub OnTextChanged(ByVal e As EventArgs) ColorVisibleLines() End Sub 'Colora tutta la RichTextBox Public Sub ColorRtb() Dim FirstVisibleChar As Integer Dim i As Integer = 0 While i < Me.Lines.Length FirstVisibleChar = GetCharFromLineIndex(i) ColorLineNumber(i, FirstVisibleChar) i += 1 End While End Sub 'Colora solo le linee visibili Public Sub ColorVisibleLines() Dim FirstLine As Integer = FirstVisibleLine() Dim LastLine As Integer = LastVisibleLine() Dim FirstVisibleChar As Integer If (FirstLine = 0) And (LastLine = 0) Then 'Non c' testo Exit Sub Else While FirstLine < LastLine FirstVisibleChar = GetCharFromLineIndex(FirstLine) ColorLineNumber(FirstLine, FirstVisibleChar) FirstLine += 1 End While End If End Sub 'Colora una linea all'indice LineIndex, a partire dal carattere 'lStart Public Sub ColorLineNumber(ByVal LineIndex As Integer, _ ByVal lStart As Integer) Dim i As Integer = 0 Dim SelectionAt As Integer = Me.SelectionStart Dim MyRow As DataRow Dim Line() As String, MyI As Integer, MyStr As String 'Blocca il refresh LockWindowUpdate(Me.Handle.ToInt32) MyI = lStart If CaseSensitive Then Line = Split(Me.Lines(LineIndex).ToString, " ") Else Line = Split(Me.Lines(LineIndex).ToLower, " ") End If For Each MyStr In Line

100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121. 122. 123. 124. 125. 126. 127. 128. 129. 130. 131. 132. 133. 134. 135. 136. 137. 138. 139. 140. 141. 142. 143. 144. 145. 146. 147. 148. 149. 150. 151. 152. 153. 154. 155. 156. 157. 158. 159. 160. 161. 162. 163. 164. 165. 166. 167. 168. 169. 170. 171.

'Seleziona i primi MyStr.Length caratteri della linea, 'ossia la prima parola Me.SelectionStart = MyI Me.SelectionLength = MyStr.Length 'Se la parola contenuta in una delle righe If Words.Rows.Contains(MyStr) Then 'Seleziona la riga MyRow = Words.Rows.Find(MyStr) 'Quindi colora la parola prelevando il colore da 'tale riga If (Not CaseSensitive) Or _ (CaseSensitive And MyRow("Word") = MyStr) Then Me.SelectionColor = Color.FromName(MyRow("Color")) End If Else 'Altrimenti lascia il testo in nero Me.SelectionColor = Color.Black End If 'Aumenta l'indice di un fattore pari alla lunghezza 'della parola pi uno (uno spazio) MyI += MyStr.Length + 1

Next

'Ripristina la selezione Me.SelectionStart = SelectionAt Me.SelectionLength = 0 'E il colore Me.SelectionColor = Color.Black 'Riprende il refresh LockWindowUpdate(0) End Sub 'Ottiene il primo carattere della linea LineIndex Public Function GetCharFromLineIndex(ByVal LineIndex As Integer) _ As Integer Return SendMessage(Me.Handle, EditMessages.LineIndex, LineIndex, 0) End Function 'Ottiene la prima linea visibile Public Function FirstVisibleLine() As Integer Return SendMessage(Me.Handle, EditMessages.GetFirstVisibleLine, 0, 0) End Function 'Ottiene l'ultima linea visibile Public Function LastVisibleLine() As Integer Dim LastLine As Integer = FirstVisibleLine() + _ (Me.Height / Me.Font.Height) If LastLine > Me.Lines.Length Or LastLine = 0 Then LastLine = Me.Lines.Length End If Return LastLine End Function Public Sub New() Dim MyRow As DataRow Dim arrKeyWords() As String, strKW As String Me.AcceptsTab = True 'Carica la colonna Word e Color Words.Columns.Add("Word") Words.PrimaryKey = New DataColumn() {Words.Columns(0)} Words.Columns.Add("Color") 'Aggiunge le keywords del linguaggio SQL all'array arrKeyWords = New String() {"select", "insert", "delete", _ "truncate", "from", "where", "into", "inner", "update", _

"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" _ (ByVal hWnd As IntPtr, ByVal wMsg As Integer, _ 005. 006. ByVal wParam As Integer, ByVal lParam As Integer) As Integer 007. Private Declare Function LockWindowUpdate Lib "user32" _ 008. (ByVal hWnd As Integer) As Integer 009. 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) 'Non colora tutte le linee visibili, bens solo la riga 020. 'dove si trova il cursorse: in questo modo l'applicazione 021. 022. 'risulta pi veloce. L'unico caso in cui questo 'approccio non funzione quando si copia un testo 023. 'all'interno della richtextbox. In quel caso ci sar 024. 'un pulsante apposito 025. Dim LineIndex As Int32 = Me.GetLineFromCharIndex(Me.SelectionStart) 026. 027. Me.ColorLineNumber(LineIndex) End Sub 028. 029. 'Colora tutta la RichTextBox 030. Public Sub ColorRtb() 031. For I As Int32 = 0 To Me.Lines.Length - 1 032. ColorLineNumber(I) 033. Next 034. 035.

036. 037. 038. 039. 040. 041. 042. 043. 044. 045. 046. 047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099. 100. 101. 102. 103. 104. 105. 106. 107.

End Sub 'Colora solo le linee visibili Public Sub ColorVisibleLines() Dim FirstLine As Integer = FirstVisibleLine() Dim LastLine As Integer = LastVisibleLine() If (FirstLine = 0) And (LastLine = 0) Then 'Non c' testo Exit Sub Else While FirstLine < LastLine ColorLineNumber(FirstLine) FirstLine += 1 End While End If End Sub 'Questa la nuova versione: nelle stesse condizioni sopra 'citate, impiega 50ms, quasi la met! L'algoritmo vecchio 'per SQL ne impiegava 10, ma non era in grado di supportare tag 'vicini come quelli dell'HTML Public Sub ColorLineNumber(ByVal LineIndex As Int32) Try If Me.Lines(LineIndex).Length = 0 Then Exit Sub End If Catch Ex As Exception Exit Sub End Try 'Indice del primo carattere della linea Dim FirstCharIndex As Int32 = _ Me.GetFirstCharIndexFromLine(LineIndex) 'Tiene traccia del cursore Dim SelectionAt As Integer = Me.SelectionStart 'Blocca il refresh LockWindowUpdate(Me.Handle.ToInt32) 'Tiene traccia se ci siano tag aperti Dim TagOpened As Boolean = False 'Indica se il tag ha degli attributi Dim Attribute As Boolean = False 'Indica se un attributo stato assegnato Dim Assigned As Boolean = False 'Indica, per gli attributi come [readonly], se le parentesi 'sono state aperte Dim AttributeOpened As Boolean = False 'Variabili locali che rappresentano Me.SelectionStart e 'Me.SelectionLength: usando la variable enregistration si 'guadagna qualche millisecondo Dim Start, Length As Int32 Dim Max As Int32 = _ (FirstCharIndex + Me.Lines(LineIndex).Length) - 1 Me.Select(FirstCharIndex, Max + 1) For Index As Int32 = FirstCharIndex To Max If Char.IsLetterOrDigit(Me.Text(Index)) Then Continue For End If 'Viene aperto un tag, inizia a selezionare 'Es.: <a If Me.Text(Index) = "<" Then Start = Index TagOpened = True Attribute = False Assigned = False ElseIf Me.Text(Index) = ">" Then 'Viene chiuso un tag: se sono stati definiti 'attributi, evidenzia solo la parentesi angolare,

108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121. 122. 123. 124. 125. 126. 127. 128. 129. 130. 131. 132. 133. 134. 135. 136. 137. 138. 139. 140. 141. 142. 143. 144. 145. 146. 147. 148. 149. 150. 151. 152. 153. 154. 155. 156. 157. 158. 159. 160. 161. 162. 163. 164. 165. 166. 167. 168. 169. 170. 171. 172. 173. 174. 175. 176. 177. 178. 179.

Next

'Es.: <a href='www.example.com'> 'altrimenti tutta la stringa da "<" a ">" 'Es.: <div> If Not Attribute Then Length = Index - Start Me.Select(Start, Length) Me.SelectionColor = Color.Blue End If Me.Select(Index, 1) Me.SelectionColor = Color.Blue Me.DeselectAll() TagOpened = False Attribute = False Assigned = False ElseIf TagOpened AndAlso Me.Text(Index) = " " Then 'Uno spazio: se un attributo gi stato impostato, 'si tratta di uno spazio che separa due attributi, 'quindi passa oltre, definendo solo 'Assigned = False; 'Es.: <div id='1' class='prova'> 'altrimenti uno spazio che precede qualsiasi 'attributo, che quindi viene dopo la dichiarazione 'del tag, che viene colorato in blu 'Es.: <div id='1'> If Assigned Then Assigned = False Else Length = Index - Start Me.Select(Start, Length) Me.SelectionColor = Color.Blue End If Me.DeselectAll() Start = Index + 1 ElseIf TagOpened AndAlso Me.Text(Index) = "=" Then 'Un uguale: a un attributo viene assegnato un 'valore, perci evidenzia l'attributo, 'dallo spazio precedente fino a = non compreso, 'e lo colore in rosso 'Es.: <table width='100'> Length = Index - Start Me.Select(Start, Length) Me.SelectionColor = Color.Red Me.DeselectAll() Attribute = True Assigned = True ElseIf Me.Text(Index) = "[" Then 'Apre un attributo Start = Index AttributeOpened = True ElseIf Me.Text(Index) = "]" And AttributeOpened Then 'Chiude un attributo 'Es.: <input type='text' [readonly]> Length = Index - Start Me.Select(Start, Length) Me.SelectionColor = Color.Red Me.DeselectAll() AttributeOpened = False End If

'Ripristina la selezione Me.SelectionStart = SelectionAt Me.SelectionLength = 0 'E il colore Me.SelectionColor = Color.Black 'Riprende il refresh LockWindowUpdate(0) End Sub 'Ottiene la prima linea visibile Public Function FirstVisibleLine() As Integer

180. 181. 182. 183. 184. 185. 186. 187. 188. 189. 190. 191. 192. 193. End

Return SendMessage(Me.Handle, EditMessages.GetFirstVisibleLine, 0, 0) End Function 'Ottiene l'ultima linea visibile Public Function LastVisibleLine() As Integer Dim LastLine As Integer = FirstVisibleLine() + _ (Me.Height / Me.Font.Height) If LastLine > Me.Lines.Length Or LastLine = 0 Then LastLine = Me.Lines.Length End If Return LastLine End Function 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, _ RichTextBoxFinds.None) 05. 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. 02. 03. 04. 05. 06. 07. 08. 09. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 'Questa classe ha la funzione di convertire Person in stringa Public Class PersonConverter '(Per convenzione, i convertitori di questo tipo, devono 'terminare con la parola "Converter" '... End Class Public Class Person Private _Name As String Private _Birthday As Date Private _Brother As Person '... 'Per la propriet Brother (fratello), si applica l'attributo 'TypeConverter, specificando quale sia la classe convertitore. 'Si utilizza solo il tipo perch la classe, come vedremo 'in seguito, espone solo metodi d'istanza, ma che possono 'essere utilizzati da soli semplicemente fornendo i parametri 'adeguati. Perci sar il programma stesso a creare, 'a runtime, un oggetto di questo tipo e ad usarne la funzioni <TypeConverter(GetType(PersonConverter))> _ Public Property Brother() As Person '... 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. 002. 003. 004. 005. 006. 007. 008. 009. 010. 011. 012. 013. 014. 015. 016. 017. 018. 019. 020. 021. 022. 023. 024. 025. 'Questo namespace contiene gli attributi necessari a 'impostare le propriet in modo che si interfaccino 'correttamente con PropertyGrid Imports System.ComponentModel 'Quando si usa uno statementes Imports, la prima voce 'si riferisce al nome del file *.dll in s?. Dato che si 'vuole BooksManager sia consierato come una namespace, non 'bisogna aggiungere un altro namespace BooksManager in questo file 'L'autore del libro, con eventuale biografia Public Class Author 'Il nome completo Private _Name As String 'Data di nascita e morte Private _Birth, _Death As Date 'Indica se l'autore ancora vivo Private _IsStillAlive As Boolean 'Una piccola biografia Private _Biography As String <DisplayName("Nome"), _ Description("Il nome dell'autore."), _ Browsable(True), _

026. 027. 028. 029. 030. 031. 032. 033. 034. 035. 036. 037. 038. 039. 040. 041. 042. 043. 044. 045. 046. 047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097.

Category("Generalita'")> _ Public Property Name() As String Get Return _Name End Get Set(ByVal Value As String) _Name = Value End Set End Property <DisplayName("Piccola biografia"), _ Description("Un riassunto delle parti pi significative della " & _ "vita dell'autore."), _ Browsable(True), _ Category("Dettagli")> _ Public Property Biography() As String Get Return _Biography End Get Set(ByVal Value As String) _Biography = Value End Set End Property <DisplayName("Data di nascita"), _ Description("La data di nascita dell'autore."), _ Browsable(True), _ Category("Generalita'")> _ Public Property Birth() As Date Get Return _Birth End Get Set(ByVal Value As Date) 'Nessun controllo: la data di nascita pu essere 'spostata a causa di uno sbaglio, che altrimenti 'potrebbe produrre un'eccezione _Birth = Value End Set End Property <DisplayName("Data di morte"), _ Description("Data di morte dell'autore."), _ Browsable(True), _ Category("Generalita'")> _ Public Property Death() As Date Get Return _Death End Get Set(ByVal Value As Date) 'Bisogna assicurarsi che la data di morte sia 'posteriore a quella di nascita If Value.CompareTo(Me.Birth) < 1 Then 'Genera un'eccezione Throw New ArgumentException("La data di morte deve " & _ "essere posteriore a quella di nascita!") Else 'Prosegue l'assegnazione _Death = Value 'Impostando la data di morte si suppone che l'autore 'non sia pi in vita... Me.IsStillAlive = False End If End Set End Property <DisplayName("Vive"), _ Description("Determina se l'autore ancora in vita."), _ Browsable(True), _ Category("Generalita'")> _ Public Property IsStillAlive() As Boolean Get Return _IsStillAlive

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 'si comunica al compilatore che questa classe usata per 120. 'convertire in stringa un valore rappresentabile in una 121. 'PropertyGrid. Cos facendo, sar possibile modificare 122. 'il codice agendo sulla stringa complessiva e non 123. 'obbligatoriamente sulle varie parti 124. Inherits ExpandableObjectConverter 125. 126. 'Determina se sia possibile convertire nel tipo dato 127. Public Overrides Function CanConvertTo(ByVal Context As ITypeDescriptorContext, _ 128. ByVal DestinationType As Type) As Boolean 129. 'Si pu convertire in Isbn, dato che questa classe 130. 'scritta apposta per questo 131. If (DestinationType Is GetType(Isbn)) Then 132. Return True 133. End If 134. Return MyBase.CanConvertFrom(Context, DestinationType) 135. End Function 136. 137. 'Determina se sia possibile convertire dal tipo dato 138. Public Overrides Function CanConvertFrom(ByVal Context As ITypeDescriptorContext, _ 139. ByVal SourceType As Type) As Boolean 140. 'Si pu convertire da String, dato che questa classe 141. 'scritta apposta per questo 142. If (SourceType Is GetType(String)) Then 143. Return True 144. End If 145. Return MyBase.CanConvertFrom(Context, SourceType) 146. End Function 147. 148. 'Converte da stringa a Isbn 149. Public Overrides Function ConvertFrom(ByVal Context As ITypeDescriptorContext, _ 150. ByVal Culture As Globalization.CultureInfo, _ 151. ByVal Value As Object) As Object 152. If TypeOf Value Is String Then 153. Dim Str As String = DirectCast(Value, String) 154. 'Cerca di creare un nuovo oggetto isbn 155. Try 156. Dim Obj As Isbn = Isbn.CreateNew(Str) 157. Return Obj 158. Catch ex As Exception 159. MessageBox.Show(ex.Message, "Books Manager", _ 160. MessageBoxButtons.OK, MessageBoxIcon.Exclamation) 161. Return New Isbn 162. End Try 163. End If 164. Return MyBase.ConvertFrom(Context, Culture, Value) 165. 166. End Function 167. 168. 'Converte da Isbn a stringa 169.

170. 171. 172. 173. 174. 175. 176. 177. 178. 179. 180. 181. 182. 183. 184. 185. 186. 187. 188. 189. 190. 191. 192. 193. 194. 195. 196. 197. 198. 199. 200. 201. 202. 203. 204. 205. 206. 207. 208. 209. 210. 211. 212. 213. 214. 215. 216. 217. 218. 219. 220. 221. 222. 223. 224. 225. 226. 227. 228. 229. 230. 231. 232. 233. 234. 235. 236. 237. 238. 239. 240. 241.

Public Overrides Function ConvertTo(ByVal Context As ITypeDescriptorContext, _ ByVal Culture As Globalization.CultureInfo, _ ByVal Value As Object, ByVal DestinationType As Type) As Object If DestinationType Is GetType(String) And _ TypeOf Value Is Isbn Then Dim Temp As Isbn = DirectCast(Value, Isbn) Return Temp.ToString End If Return MyBase.ConvertTo(Context, Culture, Value, DestinationType) End Function End Class 'Il codice ISBN, dal primo gennaio 2007, deve obbligatoriamente 'essere a tredici cifre. Per questo motivo metter solo 'questo tipo nel sorgente 'P.S.: per convenzione, gli acronimi con pi di due lettere devono 'essere scritti in Pascal Case Public Class Isbn 'Un codice formato da: 'Un prefisso (3 cifre) - solitamente 978 indica un libro in generale Private _Prefix As Int16 = 978 'Un identificativo linguistico (da 1 a 5 cifre): indica 'il paese di provenienza dell'autore - in Italia 88 Private _LanguageID As Int16 = 88 'Un prefisso editoriale (da 2 a 6 cifre): indica l'editore Private _PublisherID As Int64 = 89637 'Un identificatore del titolo Private _TitleID As Int32 = 15 'Un codice di controllo che pu variare da 0 a 10. 10 viene 'indicato con X, perci lo imposto come Char Private _ControlChar As Char = "9" <DisplayName("Prefisso"), _ Description("Prefisso del codice, costituito da tre cifre."), _ Browsable(True), _ Category("Isbn")> _ Public Property Prefix() As Int16 Get Return _Prefix End Get Set(ByVal Value As Int16) If Value = 978 Or Value = 979 Then _Prefix = Value Else Throw New ArgumentException("Prefisso non valido!") End If End Set End Property <DisplayName("ID Lingua"), _ Description("Identifica l'area da cui previene l'autore."), _ Browsable(True), _ Category("Isbn")> _ Public Property LanguageID() As Int16 Get Return _LanguageID End Get Set(ByVal Value As Int16) _LanguageID = Value End Set End Property <DisplayName("ID Editore"), _ Description("Identifica il marchio dell'editore."), _ Browsable(True), _ Category("Isbn")> _ Public Property PublisherID() As Int32 Get Return _PublisherID End Get Set(ByVal Value As Int32) _PublisherID = Value

242. 243. 244. 245. 246. 247. 248. 249. 250. 251. 252. 253. 254. 255. 256. 257. 258. 259. 260. 261. 262. 263. 264. 265. 266. 267. 268. 269. 270. 271. 272. 273. 274. 275. 276. 277. 278. 279. 280. 281. 282. 283. 284. 285. 286. 287. 288. 289. 290. 291. 292. 293. 294. 295. 296. 297. 298. 299. 300. 301. 302. 303. 304. 305. 306. 307. 308. 309. 310. 311. 312. End 313.

End Set End Property <DisplayName("ID Titolo"), _ Description("Identifica il titolo del libro."), _ Browsable(True), _ Category("Isbn")> _ Public Property TitleID() As Int32 Get Return _TitleID End Get Set(ByVal Value As Int32) _TitleID = Value End Set End Property <DisplayName("Carattere di controllo"), _ Description("Verifica la correttezza degli altri valori."), _ Browsable(True), _ Category("Isbn")> _ Public Property ControlChar() As Char Get Return _ControlChar End Get Set(ByVal Value As Char) _ControlChar = Value End Set End Property Public Sub New() End Sub 'Restituisce in forma di stringa il codice Public Overrides Function ToString() As String Return String.Format("{0}-{1}-{2}-{3}-{4}", _ Me.Prefix, Me.LanguageID, Me.PublisherID, _ Me.TitleID, Me.ControlChar) End Function 'Metodo statico factory per costruire un nuovo codice ISBN. Se 'si mettesse questo codice nel costruttore, l'oggetto verrebbe 'comunque creato anche se il codice inserito fosse errato. 'In questo modo, la creazione viene fermata e restituito 'Nothing in caso di errori Shared Function CreateNew(ByVal StringCode As String) As Isbn 'Con le espressioni regolari, ottiene le varie parti Dim Split As New System.Text.RegularExpressions.Regex( _ "(?<Prefix>\d{3})\-(?<Language>\d{1,5})" & _ "\-(?<Publisher>\d{2,6})\-(?<Title>\d+)\-(?<Control>\w)") Dim M As System.Text.RegularExpressions.Match = _ Split.Match(StringCode) 'Se la lunghezza del codice, senza trattini, di '13 caratteri e il controllo tramite espressioni regolari 'ha avuto successo, procede If StringCode.Length = 17 And M.Success Then Dim Result As New Isbn With Result .Prefix = M.Groups("Prefix").Value .LanguageID = M.Groups("Language").Value .PublisherID = M.Groups("Publisher").Value .TitleID = M.Groups("Title").Value .ControlChar = M.Groups("Control").Value End With Return Result Else Throw New ArgumentException("Il codice inserito errato!") End If End Function Class

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. <DisplayName("Titolo"), _ 337. Description("Il titolo del libro."), _ 338. Browsable(True), _ 339. Category("Editoria")> _ 340. Public Property Title() As String 341. Get 342. Return _Title 343. End Get 344. Set(ByVal Value As String) 345. _Title = Value 346. End Set 347. End Property 348. 349. <DisplayName("Collana"), _ 350. Description("La collana o la serie a cui il libro appartiene."), _ 351. Browsable(True), _ 352. Category("Editoria")> _ 353. Public Property Series() As String 354. Get 355. Return _Series 356. End Get 357. Set(ByVal Value As String) 358. _Series = Value 359. End Set 360. End Property 361. 362. <DisplayName("Editore"), _ 363. Description("La casa editrice."), _ 364. Browsable(True), _ 365. Category("Editoria")> _ 366. Public Property Publisher() As String 367. Get 368. Return _Publisher 369. End Get 370. Set(ByVal Value As String) 371. _Publisher = Value 372. End Set 373. End Property 374. 375. <DisplayName("Pagine"), _ 376. Description("Il numero di pagine da cui il libro composto."), _ 377. DefaultValue("100"), _ 378. Browsable(True), _ 379. Category("Dettagli")> _ 380. Public Property Pages() As Int16 381. 382. Get 383. Return _Pages 384. End Get 385.

386. 387. 388. 389. 390. 391. 392. 393. 394. 395. 396. 397. 398. 399. 400. 401. 402. 403. 404. 405. 406. 407. 408. 409. 410. 411. 412. 413. 414. 415. 416. 417. 418. 419. 420. 421. 422. 423. 424. 425. 426. 427. 428. 429. 430. 431. 432. 433. 434. 435. 436. 437. 438. 439. 440. 441. 442. 443. 444. 445. 446. 447. 448. 449. 450. 451. 452. 453. 454. 455. 456. 457.

Set(ByVal Value As Int16) If Value > 0 Then _Pages = Value Else Throw New ArgumentException("Numero di pagine insufficiente!") End If End Set End Property <DisplayName("Autore/i"), _ Description("L'autore o gli autori."), _ Browsable(True), _ Category("Editoria")> _ Public ReadOnly Property Authors() As List(Of Author) Get Return _Authors End Get End Property <DisplayName("Pubblicazione"), _ Description("La data di pubblicazione della prima edizione."), _ Browsable(True), _ Category("Dettagli")> _ Public Property PublicationDate() As Date Get Return _PublicationDate End Get Set(ByVal Value As Date) _PublicationDate = Value End Set End Property <DisplayName("Codice ISBN"), _ Description("Il codice ISBN conformato alla normativa di 13 cifre."), _ Browsable(True), _ Category("Editoria"), _ TypeConverter(GetType(IsbnConverter))> _ Public Property Isbn() As Isbn Get Return _Isbn End Get Set(ByVal Value As Isbn) _Isbn = Value End Set End Property <DisplayName("Ristampa"), _ Description("Il numero della ristampa."), _ DefaultValue(1), _ Browsable(True), _ Category("Dettagli")> _ Public Property Reprint() As Byte Get Return _Reprint End Get Set(ByVal Value As Byte) If Value > 0 Then _Reprint = Value Else Throw New ArgumentException("Ristampa: valore errato!") End If End Set End Property <DisplayName("Costo"), _ Description("Il costo del libro, in euro."), _ Browsable(True), _ Category("Editoria")> _ Public Property Cost() As Single Get Return _Cost End Get

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 Dim Title As String = _ 04. InputBox("Inserire il titolo del libro:", "Books Manager") 05. 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) End If 15. End Sub 16. 17. Private Sub lstBooks_SelectedIndexChanged(ByVal sender As Object, _ 18. ByVal e As EventArgs) Handles lstBooks.SelectedIndexChanged 19. 'Esce dalla procedura se non ci sono elementi selezionati 20. If lstBooks.SelectedIndices.Count = 0 Then 21. Exit Sub 22. End If 23. 24. 'Altrimenti procede 25. pgBook.SelectedObject = lstBooks.SelectedItems(0).Tag 26. End Sub 27.

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 Mar io Luigi LastName Rossi Bianchi ... Bir thDay 1/1/1965 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 Relazione Tupla Estensione della relazione Attributo Istanza di relazione Programmazione ad oggetti Classe Oggetto Lista di oggetti Propriet dell'oggetto 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 1 2 Fir stName Mar io Luigi LastName Rossi Bianchi ... Bir thDay 1/1/1965 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 1 2 3 Path C:\WINDOWS\notepad.ex e C:\Pr ogr ammi\Fir eFox \fir efox .ex e C:\Pr ogr ammi\Wor ld of War cr aft\WoW.ex e ... Descr iption Editor di testo Fir eFox w eb br ow ser Wor ld of War cr aft User ID 2 1 2

Relazione User User ID 1 2 Name Mar io Rossi Luigi Bianchi ... MainFolder C:\User s\MRossi C:\User s\Gigi

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 2 3

C:\WINDOWS\notepad.ex e C:\Pr ogr ammi\Fir eFox \fir efox .ex e C:\Pr ogr ammi\Wor ld of War cr aft\WoW.ex e ...

Editor di testo Fir eFox w eb br ow ser Wor ld of War cr aft

Relazione User User ID 1 1 Name Mar io Rossi Luigi Bianchi ... MainFolder C:\User s\MRossi C:\User s\Gigi

Relazione User sPr ogr ams User ID 1 1 2 2 ... Pr ogr amID 1 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.

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. 02. 03. 04. 05. 06. 07. 08. 09. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. Imports MySql.Data.MySqlClient '... 'Crea una nuova connessione all'host locale, 'accedendo al servizio come utente "root" e con 'password "root". Se non avete modificato le 'impostazioni di sicurezza, questa coppia di username 'e password quella predefinita. 'Naturalmente un'idiozia mantenere queste 'credenziali cos ovvie e dovrebbero essere cambiate 'subito, ma per i miei esempi user sempre root. Dim Conn As New MySqlConnection("Server=localhost; Uid=root; Pwd=root;") Try 'Avvia la connessione Conn.Close() Catch Ex As Exception '... Finally 'E la chiude. Ricordatevi che sempre bene 'chiudere la connessione anche quando si verifichino 'errori (anzi, soprattutto in queste situazioni). Conn.Close() 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. 02. 03. 04. 05. 06. 07. 08. 09. 10. 11. 12. 13. 14. 15. 16. 17. 'Potete porre questo sorgente sia 'in una Windows Application sia in una Console Application, 'anche perch lo useremo solo una volta. Dim Conn As New MySqlConnection("Server=localhost; Uid=root; Pwd=root;") Dim Cmd As New MySqlCommand() Try Conn.Open() Cmd.Connection = Conn 'Crea un nuovo database di nome "appdata" Cmd.CommandText = "CREATE DATABASE appdata;" Cmd.ExecuteNonQuery() Catch Ex As Exception Finally Conn.Close() 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 Conn.Open() 05. Cmd.Connection = Conn 06. 07. 'Crea una nuova tabella di nome Customers nel database 08. 'appdata. I suoi attributi (colonne) sono: ' - ID : identificativo numerico del record; non pu essere 09. 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) ' - LastName : cognome del cliente (max 150 caratteri) 14. 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));" Cmd.ExecuteNonQuery() 18. 19. Catch Ex As Exception 20. 21. Finally Conn.Close() 22. 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)

06. 07. 08. 09. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55.

Handles btnLoad.Click Dim Conn As New MySqlConnection("Server=localhost; Database=appdata; Uid=root; Pwd=root;") Dim Command As New MySqlCommand Try 'Seleziona tutti i record della tabella Customers, 'includendovi tutti gli attributi. Questa query 'equivalente a: ' SELECT ID, FirstName, LastName, Address, PhoneNumber FROM Customers Command.CommandText = "SELECT * FROM Customers;" Command.Connection = Conn Conn.Open() 'Esegue la query e restituisce il reader Dim Reader As MySqlDataReader = Command.ExecuteReader() lstRecords.Columns.Clear() 'Aggiunge tante colonne alla listview quanti sono 'gli attributi dei record selezionati. possibile 'ottenere il nome di ogni colonna con la funzione 'GetName di MySqlDataReader For I As Int32 = 0 To Reader.FieldCount - 1 lstRecords.Columns.Add(Reader.GetName(I)) Next Dim L As ListViewItem Dim S(Reader.FieldCount - 1) As String 'Fintanto che c' qualche record da leggere, 'lo aggiunge alla listview Do While Reader.Read() 'Riempie l'array S con i valori degli attributi 'della tupla corrente. Se una cella non contiene 'valori, mette una stringa vuota al suo posto For I As Int32 = 0 To S.Length - 1 If Not Reader.IsDBNull(I) Then 'GetString(I) ottiene il valore della cella 'I sottoforma di stringa S(I) = Reader.GetString(I) Else S(I) = "" End If Next L = New ListViewItem(S) lstRecords.Items.Add(L) Loop 'Chiude il Reader Reader.Close() Catch ex As Exception MessageBox.Show(ex.Message, Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Exclamation) Finally 'Chiude la connessione Conn.Clone() End Try End Sub

56. 57. 58. 59. 60. 61. 62. End Class

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 End If 020. 021. 022. Dim L As ListViewItem Dim S(Reader.FieldCount - 1) As String 023. 024. lstRecords.Items.Clear() 025. Do While Reader.Read() 026. 027. For I As Int32 = 0 To S.Length - 1 If Not Reader.IsDBNull(I) Then 028. 029.

030. 031. 032. 033. 034. 035. 036. 037. 038. 039. 040. 041. 042. 043. 044. 045. 046. 047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093.

Else

S(I) = Reader.GetString(I)

Loop

Next L = New ListViewItem(S) lstRecords.Items.Add(L)

S(I) = "" End If

Reader.Close() Command.Dispose() Command = Nothing End Sub Private Sub LoadData() Me.LoadData("SELECT * FROM Customers") End Sub 'Scorciatoia per eseguire una query velocemente Private Function ExecuteQuery(ByVal Query As String) As Int32 Dim Command As New MySqlCommand(Query, Conn) Dim Result As Int32 = Command.ExecuteNonQuery() Command.Dispose() Command = Nothing Return Result End Function Private Sub Form_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load Conn = New MySqlConnection("Server=localhost; Database=appdata; Uid=root; Pwd=root;") Try Conn.Open() Catch ex As Exception Conn.Close() MessageBox.Show(ex.Message, Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Exclamation) Me.Close() End Try LoadData() End Sub Private Sub btnAdd_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAdd.Click If ExecuteQuery(String.Format("INSERT INTO Customers VALUES(null, '{0}', '{1}', '{2}', '{3}');", txtFirstName.Text, txtLastName.Text, txtAddress.Text, txtPhoneNumber.Text)) Then MessageBox.Show("Cliente aggiunto!", Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Information) LoadData() End If End Sub Private Sub btnEdit_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEdit.Click If lstRecords.SelectedIndices.Count = 0 Then MessageBox.Show("Nessun record selezionato!", Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Exclamation) Exit Sub End If Dim ID As Int32 = CType(lstRecords.SelectedItems(0).SubItems(0).Text, Int32) Dim Query As New System.Text.StringBuilder() 'L'istruzione UPDATE aggiorna i campi della tabella 'specificata usando i valori posti dopo la clausola 'SET. Solo i record che rispettano i vincoli imposti 'dalla clausola WHERE vengono modificati Query.Append("UPDATE Customers SET")

094. 095. 096. 097. 098. 099. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121. 122. 123. 124. 125. 126. 127. 128. 129. 130. 131. 132. 133. 134. 135. 136. 137. 138. 139. 140. 141. 142. 143. 144. 145. 146.

For Each T As TextBox In New TextBox() {txtFirstName, txtLastName, txtAddress, txtPhoneNumber} Query.AppendFormat(" {0} = '{1}',", T.Tag.ToString(), T.Text) Next 'Rimuove l'ultima virgola... Query.Remove(Query.Length - 1, 1) Query.AppendFormat(" WHERE ID = {0};", ID) ExecuteQuery(Query.ToString()) Query = Nothing LoadData() End Sub Private Sub btnFilter_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnFilter.Click Dim Query As New System.Text.StringBuilder() Dim Conditions As New List(Of String) Query.Append("SELECT * FROM Customers") 'La scrittura: ' Field LIKE '%Something%' 'equivarrebbe teoricamente a: ' Field.Contains(Something) For Each T As TextBox In New TextBox() {txtFirstName, txtLastName, txtAddress, txtPhoneNumber} If Not String.IsNullOrEmpty(T.Text) Then Conditions.Add(String.Format("WHERE {0} LIKE '%{1}%'", T.Tag.ToString(), T.Text)) End If Next If Conditions.Count >= 1 Then Query.AppendFormat(" {0}", Conditions(0)) If Conditions.Count > 1 Then For I As Int32 = 1 To Conditions.Count - 1 Query.AppendFormat(" AND {0}", Conditions(I)) Next End If End If Query.Append(";") LoadData(Query.ToString()) Query = Nothing End Sub Private Sub Form1_FormClosing(ByVal sender As System.Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles MyBase.FormClosing If Conn.State <> ConnectionState.Closed Then Conn.Close() End If End Sub Private Sub btnReload_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnReload.Click LoadData() End Sub Private Sub lstRecords_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lstRecords.SelectedIndexChanged If lstRecords.SelectedItems.Count = 0 Then Exit Sub End If

147. 148. 149. 150. Dim Selected As ListViewItem = lstRecords.SelectedItems(0) 151. txtFirstName.Text = Selected.SubItems(1).Text 152. txtLastName.Text = Selected.SubItems(2).Text 153. txtAddress.Text = Selected.SubItems(3).Text 154. txtPhoneNumber.Text = Selected.SubItems(4).Text 155. 156. End Sub 157. End Class

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) End Sub 17. 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) Adapter.Fill(AppDataSet.Authors) 017. 018. 019.

020. 021. 022. 023. 024. 025. 026. 027. 028. 029. 030. 031. 032. 033. 034. 035. 036. 037. 038. 039. 040. 041. 042. 043. 044. 045. 046. 047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090.

Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Albums;", Conn) Adapter.Fill(AppDataSet.Albums) Conn.Clone() End Sub Private Sub lstSongs_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lstSongs.SelectedIndexChanged If lstSongs.SelectedIndex < 0 Then Exit Sub End If 'Trova la riga con ID specificato. Il tipo SongsRow stato 'definito dall'IDE durante la creazione del dataset tipizzato. Dim S As AppDataSet.SongsRow = AppDataSet.Songs.FindByID(lstSongs.SelectedValue) lblName.Text = S.Title tabAuthor.Tag = Nothing 'Anche il metodo IsAuthorNull stato definito dall'IDE. 'In generale, per ogni propriet per cui non stata 'specificata la clausola NOT NULL, l'IDE crea un metodo per 'verificare se quel dato attributo contiene un valore 'nullo. Equivale a chiamare S.IsNull(3). If Not S.IsAuthorNull() Then 'Ottiene un array che contiene tutte le righe della 'tabella Authors che soddisfano la relazione definita 'tra Songs e Authors. Dato che la chiave esterna della 'tabella figlio era un ID, la realzione, pur essendo 'teoricamente uno-a-molti, in realt 'uno-a-uno. Perci, se questo array ha almeno 'un elemento, ne avr solo uno. Dim Authors() As AppDataSet.AuthorsRow = S.GetAuthorsRows() 'Come IsNull, questo metodo equivale a chiamare 'S.GetChildRows("Songs_Authors") If Authors.Length > 0 Then Dim Author As AppDataSet.AuthorsRow = Authors(0) lblAuthorName.Text = Author.Name If Not Author.IsNicknameNull() Then lblAuthorName.Text &= vbCrLf & Chr(34) & Author.Nickname & Chr(34) End If If Not Author.IsImageNull() Then imgAuthor.Image = Image.FromFile(Author.Image) Else imgAuthor.Image = Nothing End If If Not Author.IsDescriptionNull() Then txtAuthorDescription.Text = Author.Description Else txtAuthorDescription.Text = "" End If tabAuthor.Tag = Author.ID End If End If tabAlbum.Tag = Nothing If Not S.IsAlbumNull() Then Dim Albums() As AppDataSet.AlbumsRow = S.GetAlbumsRows() If Albums.Length > 0 Then Dim Album As AppDataSet.AlbumsRow = Albums(0) lblAlbumName.Text = Album.Name If Not Album.IsYearNull() Then lblAlbumYear.Text = Album.Year Else lblAlbumYear.Text = "" End If If Not Album.IsImageNull() Then

091. 092. 093. 094. 095. 096. 097. 098. 099. 100. 101. 102. 103. 104.

imgAlbum.Image = Nothing End If If Not Album.IsDescriptionNull() Then txtAlbumDescription.Text = Album.Description Else txtAlbumDescription.Text = "" End If tabAlbum.Tag = Album.ID End If End If End Sub Private Sub btnSearch_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSearch.Click If Not String.IsNullOrEmpty(txtSearch.Text) Then 'La propriet Filter di un binding source come 'una condizione SQL. In questo caso cerchiamo tutte le 'canzoni il cui titolo contenga la stringa specificata. SongsBindingSource.Filter = String.Format("title like '%{0}%'", txtSearch.Text) Else SongsBindingSource.Filter = "" End If End Sub

Else

imgAlbum.Image = Image.FromFile(Album.Image)

105. 106. 107. 108. 109. 110. 111. 112. 113. 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. 'Evento generato quando un autore viene aggiunto. Questo 04. 'evento si verifica se l'utente salva dei cambiamenti 05. 'quando la propriet Author Nothing. 06. 'Non potendo modificare una riga esistente, quindi, ne 07. 'viene creata una nuova. Poich, tuttavia, questo 08. 'autore deve essere associato alla canzone, bisogna che 09. 'qualcuno ponga l'ID della nuova riga nel campo 10. 'Author della canzone opportuna e, dato che questo 11. 'controllo non pu n logicamente n 12. 13. 'praticamente arrivare a fare ci, bisogna che 'qualcun altro se ne occupi. 14. 15. Public Event AuthorAdded As EventHandler 16. 17. Public Property Author() As AppDataSet.AuthorsRow 18. Get Return _Author 19. End Get 20. Set(ByVal value As AppDataSet.AuthorsRow) 21. If value IsNot Nothing Then 22. _Author = value 23. With value 24. lblName.Text = .Name 25. If Not .IsImageNull() Then 26. imgAuthor.ImageLocation = .Image 27. Else 28. imgAuthor.ImageLocation = Nothing 29. End If 30. If Not .IsDescriptionNull() Then 31. txtDescription.Text = .Description 32. Else 33. txtDescription.Text = "" 34. End If 35. End With 36. Else 37. lblName.Text = "Nessun nome" 38. imgAuthor.ImageLocation = Nothing 39. txtDescription.Text = "" 40. End If 41. imgSave.Visible = False 42. End Set 43. End Property 44. 45. Private Sub imgAuthor_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) 46. Handles imgAuthor.Click 'FOpen un OpenFileDialog dichiarato nel designer. 47. 'Questo codice serve per caricare un'immagine da disco 48. 'fisso 49. If FOpen.ShowDialog = DialogResult.OK Then 50. imgAuthor.ImageLocation = FOpen.FileName 51. imgSave.Visible = True 52. End If 53. 54.

55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91. 92.

End Sub 'L'immagine del floppy diventa visibile solo quando c' stata 'una modifica, ossia stato cambiato uno di questi 'parametri: nome, immagine, descrizione. Private Sub txtDescription_TextChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles txtDescription.TextChanged imgSave.Visible = True End Sub Private Sub lblName_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lblName.Click Dim NewName As String = InputBox("Inserire nome:") If Not String.IsNullOrEmpty(NewName) Then lblName.Text = NewName imgSave.Visible = True End If End Sub Private Sub imgSave_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles imgSave.Click If _Author Is Nothing Then 'Crea la nuova riga e la inserisce nel dataset 'principale. Notare che questo approccio non 'il migliore possibile, poich sempre 'consigliabile rendere il codice il pi generale 'possibile, e limitare i riferimenti agli altri form. 'Sarebbe stato pi utile rendere AppDataSet 'visibile all'intero progetto mediante un 'modulo pubblico. _Author = My.Forms.Form1.AppDataSet.Authors.AddAuthorsRow(lblName.Text, "", txtDescription.Text, imgAuthor.ImageLocation) 'Genera l'evento AuthorAdded RaiseEvent AuthorAdded(Me, EventArgs.Empty) Else _Author.Name = lblName.Text _Author.Description = txtDescription.Text _Author.Image = imgAuthor.ImageLocation End If imgSave.Visible = False End Sub 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. Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Albums;", Conn) 017. 018. Adapter.Fill(AppDataSet.Albums) 019. Conn.Clone() 020. End Sub 021. 022. Private Sub lstSongs_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As 023. System.EventArgs) Handles lstSongs.SelectedIndexChanged If lstSongs.SelectedIndex < 0 Then 024. Exit Sub 025. End If 026. 027. Dim S As AppDataSet.SongsRow = AppDataSet.Songs.FindByID(lstSongs.SelectedValue) 028. 029. lblName.Text = S.Title 030. 031. tabAuthor.Tag = Nothing 032. If Not S.IsAuthorNull() Then 033. Dim Authors() As AppDataSet.AuthorsRow = S.GetAuthorsRows() 034. 035. 'Imposta la propriet Author del controllo avAuthor, 036. 'che non altro che un'istanza di AuthorViewer, 037. 'il controllo utente creato poco fa 038. If Authors.Length > 0 Then 039. Dim Author As AppDataSet.AuthorsRow = Authors(0) 040. avAuthor.Author = Author 041. Else 042. avAuthor.Author = Nothing 043. End If 044. End If 045. 046. tabAlbum.Tag = Nothing 047. If Not S.IsAlbumNull() Then 048. Dim Albums() As AppDataSet.AlbumsRow = S.GetAlbumsRows() 049. 050. If Albums.Length > 0 Then 051. Dim Album As AppDataSet.AlbumsRow = Albums(0) 052. 053. lblAlbumName.Text = Album.Name 054. If Not Album.IsYearNull() Then 055. lblAlbumYear.Text = Album.Year 056. Else 057. lblAlbumYear.Text = "" 058. 059.

060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094.

End If If Not Album.IsImageNull() Then imgAlbum.Image = Image.FromFile(Album.Image) Else imgAlbum.Image = Nothing End If If Not Album.IsDescriptionNull() Then txtAlbumDescription.Text = Album.Description Else txtAlbumDescription.Text = "" End If tabAlbum.Tag = Album.ID End If End If End Sub Private Sub btnSearch_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSearch.Click If Not String.IsNullOrEmpty(txtSearch.Text) Then SongsBindingSource.Filter = String.Format("title like '%{0}%'", txtSearch.Text) Else SongsBindingSource.Filter = "" End If End Sub Private Sub avAuthor_AuthorAdded(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles avAuthor.AuthorAdded If lstSongs.SelectedIndex < 0 Then Exit Sub End If Dim S As AppDataSet.SongsRow = AppDataSet.Songs.FindByID(lstSongs.SelectedValue) 'Imposta il campo Author della canzone corrente S.Author = avAuthor.Author.ID End Sub Private Sub Form1_FormClosing(ByVal sender As System.Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles MyBase.FormClosing Dim Conn As New MySqlConnection("Server=localhost; Database=appdata; Uid=root; Pwd=root;") Dim Adapter As New MySqlDataAdapter

095. 096. 'Un oggetto di tipo CommandBuilder genera automaticamente 097. 'tutti le istruzioni update, insert e delete che servono 098. 'a un adapter per funzionare. Tali istruzioni vengono 099. 'generate relativamente alla tabella dalla quale si stanno 100. 'caricando i dati 101. Dim Builder As MySqlCommandBuilder 102. 103. Conn.Open() 104. 105. Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Songs;", Conn) 106. Builder = New MySqlCommandBuilder(Adapter) 107. Adapter.Update(AppDataSet.Songs) 108. 109. Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Authors;", Conn) 110. Builder = New MySqlCommandBuilder(Adapter) 111. Adapter.Update(AppDataSet.Authors) 112. 113. Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Albums;", Conn) 114. Builder = New MySqlCommandBuilder(Adapter) 115. Adapter.Update(AppDataSet.Albums) 116. 117. Conn.Clone() 118. End Sub 119. 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 Private _Works As List(Of String) 05. 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) End Sub 24. 25. 26. Public Sub New(ByVal Name As String, ByVal ParamArray WorksNames() As String) Me.New() 27. Me.Name = Name 28. Me.Works.AddRange(WorksNames) 29. End Sub 30. 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 Private _Name As String 04. Private _Works As List(Of String) 05. 06. Public Property Name() As String 07. Get 08. Return _Name 09. End Get 10. Set(ByVal value As String) 11. _Name = value 12. End Set 13. End Property 14. 15. Public ReadOnly Property Works() As List(Of String) 16. Get 17. Return _Works 18. End Get 19. End Property 20. 21. Public Sub New() 22. _Works = New List(Of String) 23. End Sub 24. 25. Public Sub New(ByVal Name As String, ByVal ParamArray WorksNames() As String) 26. Me.New() 27. Me.Name = Name 28. Me.Works.AddRange(WorksNames) 29. End Sub 30. End Class 31. 32. Public Authors As New List(Of Author) 33. 34. Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles 35. MyBase.Load 'Aggiungie alcuni elementi iniziali alla lista 36. Authors.Add(New Author("Dante Alighieri", "Comeda", "Vita Nova", "De vulgari 37. eloquentia", "De Monarchia")) Authors.Add(New Author("Luigi Pirandello", "Il fu Mattia Pascal", "Uno, nessuno, 38. centomila", "Il gioco delle parti")) 'Imposta la sorgente di dati del bindingsource 39. bsAuthors.DataSource = Authors 40. 41. 'Aggiunge un binding alla textbox. Ci significa 42. 'che la propriet Text di txtName sar da 43. 'ora in poi sempre legata alla propriet Name 44. 'dell'elemento corrente della sorgente di dati di 45. 'bsAuthors. Dato che quest'ultima una lista di 46. 'Author, ogni suo elemento espone la propriet 47. 'Name. 48. txtName.DataBindings.Add("Text", bsAuthors, "Name") 49. 50.

51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87.

'Non possiamo fare la stessa cosa con lstWorks.Items, 'poich Items una propriet readonly. 'Questo capita abbastanza spesso: si ha bisogno di 'visualizzare una lista per ogni elemento dell'insieme. 'La soluzione consiste nel caricare gli items della 'lista quando viene caricato un nuovo elemento End Sub Private Sub bsAuthors_CurrentChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles bsAuthors.CurrentChanged 'L'evento CurrentChanged si verifica quando la propriet 'Current del binding source viene modificata. Ci significa 'che l'utente si spostato tramite il binding 'navigator. Ottenendo l'oggetto Current, possiamo risalire alla 'lista di stringhe che esso contiene Dim Author As Author = bsAuthors.Current lstWorks.Items.Clear() lstWorks.Items.AddRange(Author.Works.ToArray()) End Sub Private Sub btnAdd_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAdd.Click 'Aggiunge alla listbox e alla lista Works un nuovo 'titolo aggiunto dall'utente If Not String.IsNullOrEmpty(txtAdd.Text) Then lstWorks.Items.Add(txtAdd.Text) DirectCast(bsAuthors.Current, Author).Works.Add(txtAdd.Text) txtAdd.Text = "" End If End Sub Private Sub btnDelete_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnDelete.Click 'Elimina una delle opere visualizzate If lstWorks.SelectedIndex >= 0 Then DirectCast(bsAuthors.Current, Author).Works.RemoveAt(lstWorks.SelectedIndex) lstWorks.Items.RemoveAt(lstWorks.SelectedIndex) End If End Sub 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. 004. 005. 006. 007. 008. 009. 010. 011. 012. 013. 014. 015. 016. 017. 018. 019. 020. 021. 022. 023. 024. 025. 026. 027. 028. 029. 030. 031. 032. 033. 034. 035. 036. 037. 038. 039. 040. 041. 042. 043. 044. 045. 046. 047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071.

'Prima di iniziare: ' - MainDatabase un'istanza di AppDataSet, ossia il ' dataset tipizzato creato mediante l'editor che contiene le ' due tabelle. ' - bsOrders il BindingSource che useremo come sorgente ' dati per dgvOrders. Ricordatevi che: ' bsOrders.DataSource = MainDatabase ' bsOrders.DataMember = "Orders" o MainDatabase.Orders ' - AllowUserToAddRows = False per entrambi i datagridview Public Class Form1 Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load Dim Connection As New MySqlConnection("Server=localhost; Database=appdata; Uid=root; Pwd=root;") Dim Adapter As New MySqlDataAdapter() Try Connection.Open() Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Customers;", Connection) Adapter.Fill(MainDatabase.Customers) Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Orders;", Connection) Adapter.Fill(MainDatabase.Orders) Catch Ex As Exception MessageBox.Show("Impossibile connettersi al database!", Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Error) Finally Connection.Close() End Try 'Imposta la sorgente dati di dgvCustomers. Quando questa 'propriet viene impostata, crea automaticamente tutte 'le colonne necessarie, ne imposta il tipo e 'l'intestazione. Per questo motivo, tutte le colonne che 'andremo ad usare iniziano ad esistere solo dopo questa 'riga di codice. Tutte quelle che erano state definite 'prima vengono eliminate. dgvCustomers.DataSource = MainDatabase.Customers 'Nasconde la prima e la settima colonna, ossia l'ID e 'l'AccountType. La prima non deve essere visibile poich 'contiene dati che non riguardano l'utente, mentre 'l'ultima la nascondiamo perch avevamo detto di 'voler visualizzare il tipo di account con un'icona dgvCustomers.Columns(0).Visible = False dgvCustomers.Columns(6).Visible = False 'Crea una nuova colonna di datagridview adatta a contenere 'immagini. Esistono molti tipi di colonna (button, combobox, 'linklabel, image, eccetera...), di cui la pi comune ' una che contiene solo testo. Il tipo di una 'colonna indica il tipo di dati che tutte le celle ad essa 'sottostanti devono contenere. In questo caso vogliamo 'che l'ultima colonna contenga una piccola immagine 'indicante il livello dell'account (ci saranno tre livelli) Dim Img As New DataGridViewImageColumn 'Imposta l'immagine di default Img.Image = imgAccountTypes.Images(0) 'E l'intestazione della colonna Img.HeaderText = "AccountLevel" 'Quindi la aggiunge a quelle esistenti dgvCustomers.Columns.Add(Img) 'Poi cicla attraverso tutte le righe, controllando il 'contenuto della settima cella di ogni riga (ossia il 'valore corrdispondente ad AccountType, un numero intero), 'e imposta il contenuto dell'ottava prelevando la 'rispettiva immagine dall'imagelist. 'Ricordate che la colonna di indice 6, pur essendo 'nascosta, esiste comunque For Each Row As DataGridViewRow In dgvCustomers.Rows

072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121. 122. 123. 124. 125. 126. 127. 128. 129. 130. 131. 132. 133. 134. 135. 136. 137. 138.

Next

Row.Cells(7).Value = imgAccountTypes.Images(CInt(Row.Cells(6).Value))

'Imposta come sorgente di dati di dgvOrders il binding 'source bsOrders, specificato in precedenza dgvOrders.DataSource = bsOrders 'E nasconde le prime due colonne, ossia CustomerID e ID dgvOrders.Columns(0).Visible = False dgvOrders.Columns(1).Visible = False 'Impone di visualizzare solo le tuple di ID pari a -1, 'ossia nessuna bsOrders.Filter = "ID=-1" End Sub Private Sub dgvCustomers_KeyDown(ByVal sender As System.Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles dgvCustomers.KeyDown 'Intercettiamo la pressione di un pulsante quando l'utente 'si trova nell'ultima colonna (quella dell'icona) If dgvCustomers.CurrentCell.ColumnIndex = 7 Then 'Ottiene il valore di AccountType Dim CurValue As Int32 = CInt(dgvCustomers.CurrentRow.Cells(6).Value) 'Se stato premuto +, aumenta il valore di 1 'Se stato premuto -, lo decrementa di 1 If e.KeyCode = Keys.Oemplus Then CurValue += 1 ElseIf e.KeyCode = Keys.OemMinus Then CurValue -= 1 End If 'Fa in modo di non andare oltre i limit imposti If CurValue < 0 Then CurValue = 0 ElseIf CurValue > 2 Then CurValue = 2 End If 'Quindi imposta il nuovo valore di AccountType dgvCustomers.CurrentRow.Cells(6).Value = CurValue 'E la corrispondente nuova immagine dgvCustomers.CurrentCell.Value = imgAccountTypes.Images(CurValue) End If End Sub 'L'evento DataError viene generato ogniqualvolta l'utente 'non rispetti i vincoli imposti dal database. Ad esempio, 'viene generato se un campo marcato come NOT NULL viene 'lasciato vuoto, o se una data non valida, o 'se un numero troppo grande o troppo piccolo, 'oppure ancora se non viene soddisfatto il vincolo di 'unicit, eccetera... 'In questi casi, se il programmatore non gestisce l'evento, 'appare una finestra di default che riporta tutto il testo 'dell'eccezione e vi assicuro che non una bella cosa Private Sub dgvCustomers_DataError(ByVal sender As System.Object, ByVal e As System.Windows.Forms.DataGridViewDataErrorEventArgs) Handles dgvCustomers.DataError Dim Result As DialogResult 'Riporta l'errore all'utente, e lascia scegliere se 'modificare i dati incompatibili oppure annullare 'le modifiche e cancellare la riga Result = MessageBox.Show("Si verificato un errore di compatibilit dei dati immessi. Messaggio:" & _ Environment.NewLine & e.Exception.Message & Environment.NewLine & _ "E' possibile che dei dati mancanti compromettano il database. Premere S per modificare opportunamente " & _ "tali valori, o No per cancellare la riga.", Me.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation) If Result = Windows.Forms.DialogResult.Yes Then 'Annulla questo evento: non viene generata la 'finestra di errore

139. 140. 141. 142. 143. 144. 145. 146. 147. 148. 149. 150. 151. 152. 153. 154. 155. 156. 157. 158. 159. 160. 161. 162. 163. 164. 165. 166. 167. 168. 169. 170. 171. 172. 173. 174. 175. 176. 177. 178. 179. 180. 181. 182. 183. 184. 185. 186. 187. 188. 189. 190. 191. 192. 193. 194. 195. 196. 197. 198. 199. 200. 201. 202. 203. 204. 205. 206. 207. 208. 209. 210.

'Annulla l'eccezione e l'evento, quindi cancella 'la riga corrente e.ThrowException = False e.Cancel = True 'Le righe "nuove", ossia quelle in cui non 'stato salvato ancora nessun dato, non possono essere 'eliminate (cos dice il datagridview...) If Not dgvCustomers.CurrentRow.IsNewRow Then dgvCustomers.Rows.Remove(dgvCustomers.CurrentRow) End If End If End Sub 'Questa procedura aggiorna la ListView con alcuni dettagli 'sullo stati dei pagamenti Private Sub RefreshPreview(ByVal CustomerID As Int32) lstPreview.Items.Clear() 'Cerca il cliente Dim Customer As AppDataSet.CustomersRow = _ MainDatabase.Customers.FindByID(CustomerID) 'Se non esiste, esce If Customer Is Nothing Then Exit Sub End If Dim TotalPaid, TotalUnpaid As Single Dim Delay As TimeSpan 'Notate che qui agiamo direttamente sul dataset, 'perch contiene campi tipizzati, e ci consente di 'utilizzare meno operatori di cast For Each Order As AppDataSet.OrdersRow In MainDatabase.Orders 'Conta solo gli ordini associati a un cliente If Order.CustomerID <> CustomerID Then Continue For End If 'Se l'ordine stato pagato, aggiunge il 'totale alla variabile TotalPaid, altrimenti a 'TotalUnpaid. 'Se l'ordine non stato pagato, inoltre, 'calcola il ritardo maggiore nel pagamento If Order.Settled Then TotalPaid += Order.ItemPrice * Order.ItemCount Else TotalUnpaid += Order.ItemPrice * Order.ItemCount If Date.Now - Order.CreationDate > Delay Then Delay = Date.Now - Order.CreationDate End If End If

Else

e.Cancel = True 'Pone il cursore sulla casella corrente e obbliga 'ad iniziare l'edit mode. Il valore booleano tra 'parentesi indica di selezionare l'intero contenuto 'della cella corrente dgvCustomers.BeginEdit(True)

Next

Dim Item1 As New ListViewItem Item1.Text = "Ammontare pagato" Item1.SubItems.Add(String.Format("{0:N2}", TotalPaid)) Dim Item2 As New ListViewItem Item2.Text = "Oneri futuri" Item2.SubItems.Add(String.Format("{0:N2}", TotalUnpaid)) Dim Item3 As New ListViewItem Item3.Text = "Ritardo pagamento" Item3.SubItems.Add(CInt(Delay.TotalDays) & " giorni")

211. 212. 213. 214. 215. 216. 217. 218. 219. 220. 221. 222. 223. 224. 225. 226. 227. 228. 229. 230. 231. 232. 233. 234. 235. 236. 237. 238. 239. 240. 241. 242. 243. 244. 245. 246. 247. 248. 249. 250. 251.

Dim DaysLimit As Int32 'Un diverso tipo di account permette un maggior ritardo 'nei pagamenti... Select Case Customer.AccountType Case 0 DaysLimit = 60 Case 1 DaysLimit = 90 Case 2 DaysLimit = 120 Case Else DaysLimit = 60 End Select 'Se il cliente ha superato il limite con almeno uno 'dei suoi ordini, la riga viene colorata in rosso If Delay.TotalDays > DaysLimit Then Item3.ForeColor = Color.Red End If lstPreview.Items.Add(Item1) lstPreview.Items.Add(Item2) lstPreview.Items.Add(Item3) End Sub 'Evento generato quando l'utente si posizione su una riga Private Sub dgvCustomers_RowEnter(ByVal sender As System.Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles dgvCustomers.RowEnter 'Se si tratta di una riga valida... If e.RowIndex < dgvCustomers.Rows.Count And e.RowIndex >= 0 Then Dim CurID As Int32 = CInt(dgvCustomers.Rows(e.RowIndex).Cells(0).Value) 'Aggiorna il filtro di bsOrders, per visualizzare solo gli 'ordini di quel dato cliente bsOrders.Filter = "CustomerID=" & CurID 'E aggiorna la listview RefreshPreview(CurID) End If End Sub 'Quando una cella di dgvOrders viene modificata, aggiorna 'la listview... Private Sub dgvOrders_CellEndEdit(ByVal sender As System.Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles dgvOrders.CellEndEdit Try RefreshPreview(CInt(dgvCustomers.CurrentRow.Cells(0).Value)) Catch Ex As Exception

252. 253. 254. 255. End Try 256. End Sub 257. 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. Private Sub OK_Button_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) 24. Handles OK_Button.Click 25. If String.IsNullOrEmpty(txtItemName.Text) Then MessageBox.Show("Specificare una descrizione valida del prodotto!", Me.Text, 26. MessageBoxButtons.OK, MessageBoxIcon.Exclamation) Exit Sub 27. End If 28. 29. If nudItemPrice.Value = 0.0F Then 30. 31. MessageBox.Show("Prezzo non valido!", Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Exclamation) Exit Sub 32. 33. End If 34. 35.

36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49.

_NewOrder = My.Forms.Form1.MainDatabase.Orders.NewOrdersRow() With _NewOrder .CustomerID = Me.CustomerID .ItemName = txtItemName.Text .ItemPrice = nudItemPrice.Value .ItemCount = nudItemCount.Value .CreationDate = dtpCreationDate.Value .Settled = chbSettled.Checked End With Me.DialogResult = System.Windows.Forms.DialogResult.OK Me.Close() End Sub Private Sub Cancel_Button_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Cancel_Button.Click Me.DialogResult = System.Windows.Forms.DialogResult.Cancel Me.Close() End Sub

50. 51. 52. 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. If OrderDialog.ShowDialog(CID) = Windows.Forms.DialogResult.OK Then 18. MainDatabase.Orders.AddOrdersRow(OrderDialog.NewOrder) 19. RefreshPreview(CID) 20. End If 21. End Sub 22. 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.

17. 18. 19. 20.

Builder = New MySqlCommandBuilder(Adapter) Adapter.Update(MainDatabase.Orders) Catch Ex As Exception 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 e.Cancel = True End If Finally Connection.Close() End Try End Sub

21. 22. 23. 24. 25. 26. 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 If FirstPage Then 022. For Each Column As DataGridViewColumn In dgvCustomers.Columns 023. 024. 'Stampa solo gli header dei campi contemplati 025. If Array.IndexOf(PrintingFields, Column.Index) < 0 Then 026. Continue For End If 027. 028. 029. e.Graphics.DrawString(Column.HeaderText, dgvCustomers.ColumnHeadersDefaultCellStyle.Font, New SolidBrush(dgvCustomers.ColumnHeadersDefaultCellStyle.ForeColor), CellOffset, Y) CellOffset += Column.Width 030. TotalWidth += Column.Width 031. Next 032. Y += dgvCustomers.ColumnHeadersHeight 033. 034.

035. 036. 037. 038. 039. 040. 041. 042. 043. 044. 045. 046. 047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099.

End If 'Stampa le righe For I As Int32 = RowIndex To dgvCustomers.RowCount - 1 Dim Row As DataGridViewRow = dgvCustomers.Rows(I) CellOffset = e.MarginBounds.X 'Ottiene lo stile delle celle Dim Style As DataGridViewCellStyle 'Distingue fra quelle pari e dispari If Row.Index Mod 2 = 0 Then Style = dgvCustomers.DefaultCellStyle Else Style = dgvCustomers.AlternatingRowsDefaultCellStyle End If 'Se nessun font particolare specificato, prende 'quello del controllo If Style.Font Is Nothing Then Style.Font = dgvCustomers.Font End If 'Se nessun colore particolare specificato (di 'default valore argb=(0,0,0,0)) prende quello del 'controllo If Style.ForeColor.A = 0 Then Style.ForeColor = dgvCustomers.ForeColor End If 'Disegna una striscia del colore di sfondo della riga e.Graphics.FillRectangle(New SolidBrush(Style.BackColor), CellOffset, Y, TotalWidth, dgvCustomers.RowTemplate.Height) 'E la contorna con un tratto nero e.Graphics.DrawRectangle(Pens.Black, CellOffset, Y, TotalWidth, dgvCustomers.RowTemplate.Height) For Each Cell As DataGridViewCell In Row.Cells 'Stampa solo gli attributi contemplati If Array.IndexOf(PrintingFields, Cell.ColumnIndex) < 0 Then Continue For End If 'Se la cella contiene un'immagime, la stampa If Cell.ValueType Is GetType(Image) Then Dim Img As Image = Cell.Value e.Graphics.DrawImage(Img, CellOffset + dgvCustomers.Columns(Cell.ColumnIndex).Width \ 2 - Cell.Value.Width \ 2, Y + dgvCustomers.CurrentRow.Height \ 2 - Img.Height \ 2) Else Dim Height As Int32 Dim StrVal As String 'Formatta la data in forma compatta If Cell.ValueType Is GetType(Date) Then StrVal = CType(Cell.Value, Date).ToShortDateString() Else StrVal = Cell.Value.ToString() End If 'Calcola l'altezza del testo per centrarlo Height = Cell.MeasureTextHeight(e.Graphics, StrVal, Style.Font, 500, TextFormatFlags.Default) 'Stampa il testo e.Graphics.DrawString(StrVal, Style.Font, New SolidBrush(Style.ForeColor), CellOffset, Y + dgvCustomers.RowTemplate.Height \ 2 - Height \ 2) End If 'Si sposta alla prossima ascissa CellOffset += dgvCustomers.Columns(Cell.ColumnIndex).Width 'Per tutte le celle tranne l'ultima, disegna una linea 'verticale di separazione dalla cella successiva If Array.IndexOf(PrintingFields, Cell.ColumnIndex) < PrintingFields.Length - 1

100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. Next

Then e.Graphics.DrawLine(Pens.Black, CellOffset - 4, Y, CellOffset - 4, Y + dgvCustomers.RowTemplate.Height) End If

Next

'Aumenta l'ordinata Y += dgvCustomers.RowTemplate.Height

If e.HasMorePages Then FirstPage = False Else FirstPage = True RowIndex = 0 End If End Sub 'strPrint un sottoelemento del context menu Private Sub strPrint_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles strPrint.Click Dim PDialog As New PrintDialog

118. 119. PDialog.Document = PrintDoc 120. If PDialog.ShowDialog = Windows.Forms.DialogResult.OK Then 121. PrintDoc.PrinterSettings = PDialog.PrinterSettings 122. 'Ridimensiona le colonne per far stare i testi in modo 123. 'corretto. N.B.: ho impostato la larghezza minima della 124. 'colonna Address a 190 pixel 125. dgvCustomers.AutoResizeColumns() 126. PrintDoc.Print() 127. End If 128. End Sub 129. 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.

12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48.

End If End Sub Private Sub btnCancel_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCancel.Click 'Ferma l'attivit del WebBrowser wbBrowser.Stop() btnCancel.Enabled = False btnRefresh.Enabled = True End Sub Private Sub wbBrowser_Navigating(ByVal sender As System.Object, ByVal e As System.Windows.Forms.WebBrowserNavigatingEventArgs) Handles wbBrowser.Navigating 'L'evento Navigating si genera prima della navigazione btnCancel.Enabled = True End Sub Private Sub wbBrowser_DocumentCompleted(ByVal sender As System.Object, ByVal e As System.Windows.Forms.WebBrowserDocumentCompletedEventArgs) Handles wbBrowser.DocumentCompleted 'L'evento DocumentCompleted si verifica quando una pagina ' stata completamente caricata. Se anche una sola 'delle parti della pagina non completa, l'evento 'non viene generato. Per evitare brutte soprese, potete 'utilizzare l'evento Navigated, che si verifica dopo la 'navigazione (indipendentemente dal successo o meno 'dell'operazione) btnCancel.Enabled = False btnBack.Enabled = wbBrowser.CanGoBack btnForward.Enabled = wbBrowser.CanGoForward btnRefresh.Enabled = True End Sub Private Sub btnRefresh_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnRefresh.Click wbBrowser.Refresh() End Sub Private Sub btnBack_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnBack.Click wbBrowser.GoBack() End Sub Private Sub btnForward_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnForward.Click wbBrowser.GoForward() End Sub

49. 50. 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. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43.

Private Sub btnCancel_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCancel.Click wbBrowser.Stop() btnCancel.Enabled = False btnRefresh.Enabled = True End Sub Private Sub wbBrowser_Navigating(ByVal sender As System.Object, ByVal e As System.Windows.Forms.WebBrowserNavigatingEventArgs) Handles wbBrowser.Navigating btnCancel.Enabled = True 'La propriet StatusText contiene in forma leggibile 'un resoconto dell'operazione che il controllo sta svolgendo lblStatus.Text = wbBrowser.StatusText End Sub Private Sub wbBrowser_Navigated(ByVal sender As System.Object, ByVal e As System.Windows.Forms.WebBrowserNavigatedEventArgs) Handles wbBrowser.Navigated btnCancel.Enabled = False btnBack.Enabled = wbBrowser.CanGoBack btnForward.Enabled = wbBrowser.CanGoForward btnRefresh.Enabled = True lblStatus.Text = "Pagina caricata" End Sub Private Sub btnRefresh_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnRefresh.Click wbBrowser.Refresh() End Sub Private Sub btnBack_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnBack.Click wbBrowser.GoBack() End Sub Private Sub btnForward_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnForward.Click wbBrowser.GoForward() End Sub Private Sub wbBrowser_ProgressChanged(ByVal sender As System.Object, ByVal e As System.Windows.Forms.WebBrowserProgressChangedEventArgs) Handles wbBrowser.ProgressChanged prgProgress.Value = e.CurrentProgress / e.MaximumProgress * 100 lblStatus.Text = wbBrowser.StatusText End Sub

44. 45. 46. 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. 'Ecco il dizionario che contiene tutto 05. Dim LoginInfo As New Dictionary(Of String, Dictionary(Of String, String)) 06. 07. Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles 08. MyBase.Load 'Per semplicit, in questo esempio carichiamo dei 09. 'dati di prova al caricamento del form 10. 11. 12. Dim TInfo As New Dictionary(Of String, String) With TInfo 13. 14. 'ID del form di login .Add("form-id", "totemlogin") 15. 16. 'ID della textbox per l'username 17. .Add("username-field", "lname") 'ID della textbox per la password 18. .Add("password-field", "lpassw") 19. 'Username e password 20. .Add("username", "prova") 21. .Add("password", "prova") 22. End With 23. 24. 'Associa alla pagina il suo login 25. LoginInfo.Add("http://totem.altervista.org/guida/versione3/login.php", TInfo) 26. End Sub 27. 28. 29. Private Sub btnAction_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) 30. Handles btnAction.Click 'Quando viene premuto il pulsante, ricava il dizionario 31. 'dei dati dall'url della pagina 32. Dim Info As Dictionary(Of String, String) = LoginInfo(wbBrowser.Url.ToString()) 33. 'Quindi compila i campi e invia la richiesta di login 34. With wbBrowser.Document 35. .GetElementById(Info("username-field")).SetAttribute("value", Info("username")) 36. .GetElementById(Info("password-field")).SetAttribute("value", Info("password")) 37. 'InvokeMember invoca un metodo usabile da un 38. 'certo elemento. I metodi sono gli stessi che si 39. 'usano in javascript 40. .GetElementById(Info("form-id")).InvokeMember("submit") 41. End With 42. End Sub 43. 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. Private Sub btnAction_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) 05. Handles btnAction.Click 06. With wbBrowser.Document 'Scorre tutte le immagini nella pagina e ad ognuna 07. 'aggiunge un nuovo gestore d'evento per l'evento OnClick. 08. 'Il metodo AttachEventHandler pu essere usato 09. 'da qualsiasi HtmlElement, ed accetta come primo 10. 11. 'parametro il nome dell'evento da gestire (vedere la 'documentazione ufficiale W3C) e come secondo un 12. 'delegate che punta al sottoscrittore. 13. For Each img As HtmlElement In .Images 14. .AttachEventHandler("onclick", AddressOf ImageToLink) 15. 16. Next End With 17. End Sub 18. 19. 'Questo il nuovo gestore d'evento. Nonostante i 20. 'parametri, sender sempre Nothing 21. Private Sub ImageToLink(ByVal sender As Object, ByVal e As EventArgs) 22. 'Ottiene un riferimento all'immagine con il metodo 23. 'GetElementFromPoint, sfruttando il fatto che questo 24. 'codice viene eseguito subito dopo un click. 25. 'MousePosition indica la posizione del mouse sullo schermo, 26. 'Me.Location determina la posizione del form sullo schermo 27. 'e wbBrowser.Location la posizione del browser sul form. 28. 'La differenza tra questi punti la posizione 29. 'del mouse rispetto al browser. Anche se un po' grezzo, 30. 'questo metodo dovrebbe funzionare abbastanza 31. Dim Img As HtmlElement = wbBrowser.Document.GetElementFromPoint(MousePosition 32. Me.Location - wbBrowser.Location) 'Crea un nuovo link mediante il metodo CreateElement 33. 'di HtmlDocument 34. Dim Link As HtmlElement = wbBrowser.Document.CreateElement("a") 35. 36. 'Imposta l'attributo href dell'immagine 37. Link.SetAttribute("href", Img.GetAttribute("src")) 38. 'Imposta il testo del link 39. If Not String.IsNullOrEmpty(Img.GetAttribute("longdesc")) Then 40. Link.InnerText = Img.GetAttribute("longdesc") 41. ElseIf Not String.IsNullOrEmpty(Img.GetAttribute("alt")) Then 42. Link.InnerText = Img.GetAttribute("alt") 43. Else 44. Link.InnerText = "Immagine" 45. End If 46. 47. 'Aggiunge il link prima dell'immagine 48. Img.InsertAdjacentElement(HtmlElementInsertionOrientation.BeforeBegin, Link) 49. 'Dato che non possibile eliminare elementi, 50. 'impone all'immagine larghezza 0 51. Img.SetAttribute("width", "0") 52. 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. 02. 03. 04. 05. 06. 07. 08. 09. 10. 11. 12. 13. 14. Dim W As New Net.WebClient 'Scarica l'home page del sito e la salva in C: W.DownloadFile("http://totem.altervista.org/index.php", "C:\index.php") Dim S As String 'Scarica il contenuto del file Capitoli.txt e lo salva 'nella stringa S S = W.DownloadString("http://totem.altervista.org/guida/versione3/Capitoli.txt") 'Aggiunge una coppia nome-valore alla query W.QueryString.Add("name", "twaveeditor") 'La prossima richiesta sar quindi equivalente a: ' http://totem.altervista.org/download/details.php?name=twaveeditor

'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}%", _ e.BytesReceived, e.TotalBytesToReceive, _ 17. 18. e.ProgressPercentage, Environment.NewLine) 19. End Sub 20. Private Sub W_DownloadFileCompleted(ByVal sender As Object, ByVal e As 21. System.ComponentModel.AsyncCompletedEventArgs) Handles W.DownloadFileCompleted 'e.Cancelled vale True se il download stato annullato. 22. 'e.Error di tipo Exception e contiene l'eccezione 23. 24. ' generata nel caso si sia verificato un errore. If e.Cancelled Then 25. MessageBox.Show("Il download stato cancellato!", Me.Text, MessageBoxButtons.OK, 26. MessageBoxIcon.Exclamation) ElseIf e.Error IsNot Nothing Then 27. MessageBox.Show("Si verificato un errore: " & e.Error.Message, Me.Text, 28. MessageBoxButtons.OK, MessageBoxIcon.Exclamation) Else 29. MessageBox.Show("Download completato con successo!", Me.Text, 30. MessageBoxButtons.OK, MessageBoxIcon.Information) End If 31. btnDownload.Enabled = True 32. btnCancel.Enabled = False 33. End Sub 34. 35. Private Sub btnCancel_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) 36. Handles btnCancel.Click 'Il metodo CancelAsync cancella il download asincrono 37. W.CancelAsync() 38. 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. 'Address pu anche contenere una query string 03. 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 'potr leggere il file richiesto. 08. 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 'dallo stream. La lettura a blocchi pi 13. 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 Dim BytesRead As Int32 18. 19. 20. 'La funzione Read, vi ricordo, restituisce come risultato 'il numero di bytes effettivamente letti dallo stream 21. BytesRead = Reader.Read(Buffer, 0, Buffer.Length) 22. 23. Do While BytesRead > 0 Writer.Write(Buffer, 0, BytesRead) 24. BytesRead = Reader.Read(Buffer, 0, Buffer.Length) 25. 26. Loop 27. Reader.Close() 28. Writer.Close() 29. Response = Nothing 30. 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. 03. 04. 05. 06. 07. 08. 09. 10. '... 'Indica al server che vogliamo iniziare la lettura 'dall'offset n Request.AddRange(n) 'oppure 'Indica al server che vogliamo iniziare la lettura dalla 'posizione n, ma solo fino alla posizione q 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. 024. 025. 026. 027. 028. 029. 030. 031. 032. 033. 034. 035. 036. 037. 038. 039. 040. 041. 042. 043. 044. 045. 046. 047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088.

If Connected Then tmrData.Start() Else tmrData.Stop() End If End Sub Private Sub btnListen_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnListen.Click 'Inizializza il listener e inizia l'ascolto sulla porta '5000. Inoltre, attiva il timer per controllare se ci 'sono connessioni in arrivo. Il timer scatta ogni 100ms Listener = New TcpListener(5000) Listener.Start() tmrConnections.Start() btnListen.Enabled = False btnConnect.Enabled = False txtLog.AppendText("Server - in ascolto..." & Environment.NewLine) End Sub Private Sub tmrConnections_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles tmrConnections.Tick 'Se ci sono connessioni... If Listener.Pending() Then 'Ferma un attimo il timer tmrConnections.Stop() 'Chiede all'utente se confermare la connessione If MessageBox.Show("Rilevato un tentativo di connessione. Accettare?", Me.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Question) = Windows.Forms.DialogResult.Yes Then 'Ottiene l'oggetto TcpClient collegato al client Client = Listener.AcceptTcpClient() 'Ferma il listener Listener.Stop() 'Ottiene il network stream NetStream = Client.GetStream() 'E attiva/disattiva i controlli per quando si connessi EnableControls(True) Else Listener.Stop() Listener.Start() tmrConnections.Start() End If End If End Sub Private Sub btnConnect_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnConnect.Click Dim IP As Net.IPAddress 'Prima esegue un controllo sull'indirizzo IP per 'controllare che sia valido If Not Net.IPAddress.TryParse(txtIP.Text, IP) Then MessageBox.Show("IP non valido!", Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Exclamation) Exit Sub End If 'Quindi inizializza un client e tenta la connessione 'al dato IP sulla porta 5000 Client = New TcpClient() txtLog.AppendText("Client - tentativo di connessione..." & vbCrLf) Try Application.DoEvents() Client.Connect(IP, 5000) Catch Ex As Exception End Try 'Se la connessione ha avuto successo, ottiene il network

089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121. 122. 123. 124. 125. 126. 127. 128. 129. 130. 131. 132.

'stream e agisce sui controlli come nel codice precedente If Client.Connected Then txtLog.AppendText("Tentativo di connessione riuscito!" & vbCrLf) NetStream = Client.GetStream() EnableControls(True) Else txtLog.AppendText("Tentativo di connessione fallito..." & vbCrLf) End If End Sub Private Sub tmrData_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles tmrData.Tick 'Se ci sono dati disponibili If Client.Available > 0 Then 'Li legge dallo stream Dim Buffer(Client.Available - 1) As Byte NetStream.Read(Buffer, 0, Buffer.Length) 'Li trasforma in una stringa Dim Msg As String = UTF8.GetString(Buffer) 'Se il messaggio inizia con questa stringa 'particolare signifia che l'altro utente ha chiuso 'la connessione, quindi disconnette anche questo If Msg.StartsWith("\\\close\\\") Then btnDisconnect_Click(Me, EventArgs.Empty) Exit Sub End If 'Altrimenti lo accoda alla textbox grande txtLog.AppendText("Ricevuto: ") txtLog.AppendText(Msg) txtLog.AppendText(Environment.NewLine) End If End Sub Private Sub btnSend_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSend.Click If Not String.IsNullOrEmpty(txtMessage.Text) Then Dim Buffer() As Byte = UTF8.GetBytes(txtMessage.Text) txtLog.AppendText("Inviato: " & txtMessage.Text & Environment.NewLine) NetStream.Write(Buffer, 0, Buffer.Length) txtMessage.Text = "" End If End Sub Private Sub btnDisconnect_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnDisconnect.Click tmrData.Stop()

133. 134. Dim Buffer() As Byte = UTF8.GetBytes("\\\close\\\") 135. NetStream.Write(Buffer, 0, Buffer.Length) 136. 137. Client.Client.Close() 138. Client.Close() 139. Client = Nothing 140. 141. If Listener IsNot Nothing Then 142. Listener.Server.Close() 143. Listener = Nothing 144. End If 145. 146. EnableControls(False) 147. txtLog.AppendText("Disconnesso" & Environment.NewLine) 148. End Sub 149. 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. 'Dimensione del file 025. Private FileSize As Int64 026. 027. 'I seguenti metodi semplificano le operazioni di invio e 028. 'ricezione di stringhe 029. 030. 'Invia un messaggio su uno stream di rete 031. Private Sub Send(ByVal Msg As String, ByVal Stream As NetworkStream) 032. 'Se si pu scrivere 033. 034. If Stream.CanWrite Then 035. 'Converte il messaggio in binario 036. Dim Bytes() As Byte = ASCII.GetBytes(Msg) 037. 'E lo scrive sul network stream 038. 039. Stream.Write(Bytes, 0, Bytes.Length) 040. End If 041. End Sub 042. 043. 'Ottiene un messaggio dallo stream di rete 044. Private Function GetMessage(ByVal Stream As NetworkStream) As String 045. 046. 'Se si pu leggere 047. If Stream.CanRead Then 048. Dim Bytes(Client.ReceiveBufferSize) As Byte 049. 050. Dim Msg As String 051. 'Legge i bytes arrivati 052. Stream.Read(Bytes, 0, Bytes.Length) 053. 'Li converte in una stringa leggibile 054. Msg = ASCII.GetString(Bytes) 055. 'E restituisce la stringa 056. 057. Return Msg.Normalize 058. Else 059. Return Nothing 060. End If 061. End Function 062. 063. Private Sub cmdListen_Click(ByVal sender As Object, _ 064. ByVal e As EventArgs) Handles cmdListen.Click 065. If cmdListen.Text = "Ascolta" Then 066. 067. 'Inizia ad ascoltare sulla porta 25 068. Listener = New TcpListener(25) 069. 070. Listener.Start() 071. 'Attiva il timer per controllare le richieste di connesione 072. tmrControlConnection.Start() 073.

074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121. 122. 123. 124. 125. 126. 127. 128. 129. 130. 131. 132. 133. 134. 135. 136. 137. 138. 139. 140. 141. 142. 143. 144. 145.

Else

'Cambia il testo e la funzione del pulsante cmdListen.Text = "Stop"

'Ferma l'operazione di ascolto Listener.Stop() 'Ripristina il testo cmdListen.Text = "Ascolta" End If End Sub Private Sub tmrControlConnection_Tick(ByVal sender As Object, _ ByVal e As EventArgs) Handles tmrControlConnection.Tick 'Se ci sono connessioni in attesa... If Listener.Pending Then 'Ferma il timer per eseguire le operazioni tmrControlConnection.Stop() lblStatus.Text = " stata ricevuta una richiesta" 'Richiede all'utente se accettare la connessione If MessageBox.Show(" stata ricevuta una richiesta di connessione. Accettare?", _ Me.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Question) = _ Windows.Forms.DialogResult.Yes Then 'Acceta la connessione Client = Listener.AcceptTcpClient 'Apre lo stream di rete condiviso NetStream = Client.GetStream 'Termina l'ascolto Listener.Stop() 'Rende il pulsante cmdListen inutilizzabile, poich 'una connessione gi stata aperta cmdListen.Enabled = False 'Inizia la ricezione di messaggi tmrGetData.Start() lblStatus.Text = "Connessione riuscita!"

Else

'Altrimenti si rimette in attesa per altre connessioni tmrControlConnection.Start() lblStatus.Text = "In attesa di connessioni..." End If End If End Sub Private Sub tmrControlFile_Tick(ByVal sender As Object, _ ByVal e As EventArgs) Handles tmrControlFile.Tick 'Se c' una richiesta, l'accetta subito If FileListener.Pending Then tmrControlFile.Stop() FileReceiver = FileListener.AcceptTcpClient NetFile = FileReceiver.GetStream 'Ferma il listener FileListener.Stop() lblStatus.Text = "Flusso di informazioni aperto" 'Attiva la ricezione di dati attraverso un background worker bgReceiveFile.RunWorkerAsync() End If End Sub Private Sub tmrGetData_Tick(ByVal sender As Object, _ ByVal e As EventArgs) Handles tmrGetData.Tick If Client.Connected And Client.Available Then 'Ferma il timer mentre si eseguono le operazioni tmrGetData.Stop() 'Legge il messaggio Dim Msg As String = GetMessage(NetStream)

146. 147. 148. 149. 150. 151. 152. 153. 154. 155. 156. 157. 158. 159. 160. 161. 162. 163. 164. 165. 166. 167. 168. 169. 170. 171. 172. 173. 174. 175. 176. 177. 178. 179. 180. 181. 182. 183. 184. 185. 186. 187. 188. 189. 190. 191. 192. 193. 194. 195. 196. 197. 198. 199. 200. 201. 202. 203. 204. 205. 206. 207. 208. 209. 210. 211. 212. 213. 214. 215. 216.

If Msg.StartsWith("ConfirmTransfer") Then 'Divide il messagio in parti in base al carattere pipe Dim Parts() As String = Msg.Split("|") 'La prima parte "ConfirmTransfer" 'La seconda il percorso del file sull'altro computer Dim File As String = Parts(1) 'La terza la dimensione Dim Size As Int64 = CType(Parts(2), Int64) 'Ottiene solo il nome del file, senza percorso File = IO.Path.GetFileName(File) 'Costruisce il percorso del file su questo computer, 'salvandolo nella cartella del progetto (bin\Debug) FileName = Application.StartupPath & "\" & File 'Imposta Size come variabile globale FileSize = Size 'Richiede se accettare il trasferimento If MessageBox.Show(String.Format( _ " stata ricevuta una richiesta di trasferimento di {0} ({1} bytes). Acettare?", _ File, Size), Me.Text, MessageBoxButtons.YesNo, _ MessageBoxIcon.Question) = Windows.Forms.DialogResult.Yes Then 'Manda OK al client Send("OK", NetStream) 'Intanto si mette in attesa sulla porta 1001 per 'l'invio dei bytes del file FileListener = New TcpListener(1001) FileListener.Start() 'E attiva il timer di controllo Else tmrControlFile.Start()

'Altrimenti, risponde di no Send("NO", NetStream) End If End If 'Riprende il controllo tmrGetData.Start() End If End Sub Private Sub bgReceiveFile_DoWork(ByVal sender As Object, _ ByVal e As DoWorkEventArgs) Handles bgReceiveFile.DoWork 'Apre un nuovo stream in base al percorso costruito 'nella procedura precedente Dim Stream As New IO.FileStream(FileName, IO.FileMode.Create) 'Crea un indice che indica il progresso Dim Index As Int64 = 0 lblStatus.Text = "In ricezione..." Do If FileReceiver.Available Then 'Riceve i bytes necessari Dim Bytes(4096) As Byte Dim Msg As String = ASCII.GetString(Bytes) 'Se i bytes sono un messaggio stringa e contengono '"END", oppure la dimensione giusta gi stata 'raggiunta, allora si ferma If Msg.Contains("END") Or Index >= FileSize Then Exit Do End If 'Preleva i bytes dallo stream di rete

217. 218. 219. 220. 221. 222. 223. 224. 225. 226. 227. 228. 229. 230. 231. 232. 233. 234. 235. 236. 237. 238. 239. End

NetFile.Read(Bytes, 0, 4096) 'E li scrive sul file fisico Stream.Write(Bytes, 0, 4096) 'Incrementa l'indice di 4096 Index += 4096 'E notifica il progresso bgReceiveFile.ReportProgress(Index * 100 / FileSize) End If

Loop

lblStatus.Text = "File ricevuto!" Stream.Close() MessageBox.Show("File ricevuto con successo!", Me.Text, _ MessageBoxButtons.OK, MessageBoxIcon.Information) End Sub Private Sub bgReceiveFile_ProgressChanged(ByVal sender As Object, _ ByVal e As ProgressChangedEventArgs) _ Handles bgReceiveFile.ProgressChanged prgProgress.Value = e.ProgressPercentage End Sub 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. 002. 003. 004. 005. 006. 007. 008. 009. 010. 011. 012. 013. 014. 015. 016. 017. 018. 019. 020. 021. 022. 023. 024. 025. 026. 027. 028. 029. 030. 031. 032. 033. 034. 035. 036. 037. 038. 039. 040. 041. 042. 043. 044. 045. 046. 047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. Imports System.Net.Sockets Imports System.Text.ASCIIEncoding Imports System.ComponentModel Public Class Form1 'Client: il client che si dovr connettere al server 'FileSender: il client che ha il compito di trasferire i ' pacchetti di informazioni al server Private Client, FileSender As TcpClient 'NetStream: lo stream su cui scrivere i dati di comunicazione 'NetFile: lo stream per inviare i dati da scrivere sul file Private NetStream, NetFile As NetworkStream 'L'IP del server a cui connettersi Private IP As String 'I seguenti metodi semplificano le operazioni di invio e 'ricezione di stringhe 'Invia un messaggio su uno stream di rete Private Sub Send(ByVal Msg As String, ByVal Stream As NetworkStream) 'Se si pu scrivere If Stream.CanWrite Then 'Converte il messaggio in binario Dim Bytes() As Byte = ASCII.GetBytes(Msg) 'E lo scrive sul network stream Stream.Write(Bytes, 0, Bytes.Length) End If End Sub 'Ottiene un messaggio dallo stream di rete Private Function GetMessage(ByVal Stream As NetworkStream) As String 'Se si pu leggere If Stream.CanRead Then Dim Bytes(Client.ReceiveBufferSize) As Byte Dim Msg As String 'Legge i bytes arrivati Stream.Read(Bytes, 0, Bytes.Length) 'Li converte in una stringa leggibile Msg = ASCII.GetString(Bytes) 'E restituisce la stringa Else Return Msg.Normalize

Return Nothing End If End Function Private Sub cmdConnect_Click(ByVal sender As Object, _ ByVal e As EventArgs) Handles cmdConnect.Click 'Ottiene l'IP del server IP = InputBox("Inserire l'IP del server:", Me.Text) 'Controlla che l'IP non sia nullo o vuoto If String.IsNullOrEmpty(IP) Then MessageBox.Show("Connessiona annullata!", Me.Text, _ MessageBoxButtons.OK, MessageBoxIcon.Exclamation) Exit Sub End If 'Inizializza un nuovo client

070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121. 122. 123. 124. 125. 126. 127. 128. 129. 130. 131. 132. 133. 134. 135. 136. 137. 138. 139. 140. 141.

Client = New TcpClient 'E tenta la connessione all'IP dato, sulla porta 25 lblStatus.Text = "Connessione in corso..." Try Client.Connect(IP, 25) Catch SE As SocketException MessageBox.Show("Impossibile stabilire una connessione!", _ Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Exclamation) Exit Sub End Try 'Se la connessione riuscita, ottiene lo 'stream condiviso di rete direttamente collegato con 'il networkstream del server If Client.Connected Then 'Ora si sicuri di essere connessi: 'sblocca i comandi per il trasferimento NetStream = Client.GetStream grpTransfer.Enabled = True lblStatus.Text = "Connessione riuscita!" End If End Sub Private Sub cmdBrowse_Click(ByVal sender As Object, _ ByVal e As EventArgs) Handles cmdBrowse.Click Dim Open As New OpenFileDialog Open.Filter = "Tutti i file|*.*" If Open.ShowDialog = Windows.Forms.DialogResult.OK Then txtFile.Text = Open.FileName End If End Sub Private Sub cmdSend_Click(ByVal sender As Object, _ ByVal e As EventArgs) Handles cmdSend.Click 'Controlla che il file esista If Not IO.File.Exists(txtFile.Text) Then MessageBox.Show("Il file non esiste!", Me.Text, _ MessageBoxButtons.OK, MessageBoxIcon.Exclamation) Exit Sub End If 'Se si connessi e si pu scrivere 'sullo stream di rete... If Client.Connected AndAlso NetStream.CanWrite Then 'Manda un messaggio al server, chiedendo 'conferma del trasferimento. Nel messaggio immette anche 'alcune informazioni riguardo il nome e la 'dimensione del file Dim Msg As String = _ String.Format("ConfirmTransfer|{0}|{1}", txtFile.Text, _ FileLen(txtFile.Text)) 'Invia il messaggio con la procedura scritta sopra Send(Msg, NetStream) 'Attiva il timer per controllare i dati arrivati tmrGetData.Start() 'Disattiva il pulsante per evitare pi azioni 'contemporanee indesiderate cmdSend.Enabled = False lblStatus.Text = "In attesa di conferma dal server..." End If End Sub Private Sub tmrGetData_Tick(ByVal sender As Object, _

142. 143. 144. 145. 146. 147. 148. 149. 150. 151. 152. 153. 154. 155. 156. 157. 158. 159. 160. 161. 162. 163. 164. 165. 166. 167. 168. 169. 170. 171. 172. 173. 174. 175. 176. 177. 178. 179. 180. 181. 182. 183. 184. 185. 186. 187. 188. 189. 190. 191. 192. 193. 194. 195. 196. 197. 198. 199. 200. 201. 202. 203. 204. 205. 206. 207. 208. 209. 210. 211. 212. 213.

ByVal e As EventArgs) Handles tmrGetData.Tick If Client.Connected AndAlso Client.Available Then 'Ferma il timer mentre si eseguono le operazioni tmrGetData.Stop() 'Legge il messaggio Dim Msg As String = GetMessage(NetStream) 'Uso Contains per un semplice motivo. Quando si converte 'un array di bytes in una stringa, ci possono essere 'caratteri speciali successivi a questa, come ad esempio 'il NULL terminator (carattere 00), che ne compromettono 'la struttura. If Msg.Contains("OK") Then 'Termina questa connessione e si connette 'alla porta deputata alla ricezione dei file FileSender = New TcpClient FileSender.Connect(IP, 1001) If FileSender.Connected Then 'Ottiene lo stream associato a questa operaizone NetFile = FileSender.GetStream 'E inizia la trasmissione dei dati bgSendFile.RunWorkerAsync(txtFile.Text) End If ElseIf Msg.Contains("NO") Then MessageBox.Show("Il server ha rifiutato il trasferimento!", _ Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Exclamation) cmdSend.Enabled = True End If 'Riprende il controllo dei dati tmrGetData.Start() End If End Sub Private Sub bgSendFile_DoWork(ByVal sender As Object, _ ByVal e As DoWorkEventArgs) Handles bgSendFile.DoWork 'Ottiene il nome del file dall'argomento passato al metodo 'RunWorkerAsync nella procedura precedente Dim FileName As String = e.Argument 'Crea un nuovo lettore del file a basso livello, cos 'da poter ottenere bytes di informazione anzich caratteri 'come nello StreamReader Dim Reader As New IO.FileStream(FileName, IO.FileMode.Open) 'Calcola la grandezza del file, per poter poi tenere 'l'utente al corrente della percentuale di completamento Dim Size As Int64 = FileLen(FileName) 'Un blocco di bytes da 4096 posti. Il file viene spedito in '"pacchettini" per evitare di sovraccaricare la connessione Dim Bytes(4095) As Byte 'Se il file pi grande di 4KiB, lo divide 'in blocchi di dati da 4096 bytes If Size > 4096 Then For Block As Int64 = 0 To Size Step 4096 'Se i bytes rimanenti sono pi di 4096, 'ne legge un blocco intero If Size - Block >= 4096 Then Reader.Read(Bytes, 0, 4096) Else 'Altrimenti un blocco pi piccolo

214. 215. 216. 217. 218. 219. 220. 221. 222. 223. 224. 225. 226. 227. 228. 229. 230. 231. 232. 233. 234. 235. 236. 237. 238. 239. 240. 241. 242. 243. 244. 245. 246. 247. 248. 249. 250. 251. 252. 253. End

Reader.Read(Bytes, 0, Size - Block) End If 'Scrive i dati prelevati sullo stream di rete, 'inviandoli cos al server NetFile.Write(Bytes, 0, 4096) 'Riporta la percentuale all'utente bgSendFile.ReportProgress(Block * 100 / Size) 'Smette per 30ms, cos da dare tempo dal 'server di poter processare i pacchetti uno per 'uno, evitando confusione Threading.Thread.Sleep(30)

Next Else

'Se il file minore di 4KiB, lo invia tutto 'direttamente dal server Reader.Read(Bytes, 0, Size) NetFile.Write(Bytes, 0, Size) End If Reader.Close() 'Percentuale massima: lavoro terminato bgSendFile.ReportProgress(100) Threading.Thread.Sleep(100) 'Comunica la fine delle operazioni NetFile.Write(ASCII.GetBytes("END"), 0, 3) MessageBox.Show("File inviato con successo!", Me.Text, _ MessageBoxButtons.OK, MessageBoxIcon.Information) cmdSend.Enabled = True End Sub Private Sub bgSendFile_ProgressChanged(ByVal sender As Object, _ ByVal e As ProgressChangedEventArgs) _ Handles bgSendFile.ProgressChanged 'Aggiorna la progressbar prgProgress.Value = e.ProgressPercentage End Sub Class

E1. Il Filesystem - Gestire files e cartelle

Il .NET Fr amew or k offr e una vastissima gamma di classi e metodi per oper ar e qualsiasi oper azioni di Input/Output e analisi di file e car telle. Pr opr io per il suo scopo, il namespace che contiene tutto questo chiamato System.IO. Sar ebbe un'impr esa immane quella di documentar e il funzionamento di ogni membr o di ogni classe del namespace in questione, quindi scr iver solamente delle dir ettive gener ali sui tipi da utilizzar e in ciascuna situazione. IO.Dir ecto r y : espone solo metodi statici che per mettono la modifica, l'analisi e la cr eazione di car telle sul computer . I metodi pi fr equentemente utilizzati sono Cr eateDir ector y (for nito un per cor so come unico par ametr o, cr ea tutte le car telle ancor a inesistenti), Delete (come pr imo par ametr o accetta il per cor so dell'unica car tella da eliminar e: se questa non vuota, bisogna specificar e Tr ue come secondo par ametr o per indicar e di eseguir e una pulizia r icor siva), Ex ists (contr olla l'esistenza di una car tella), GetDir ector ies (r estituisce un ar r ay di str inghe contenente tutte le sottocar telle di pr imo livello) e GetFiles (r estituisce un ar r ay di str inghe contenente tutti i files pr esenti nella dir ector y data; opzionalmente si pu specificar e un patter n di r icer ca di for mato simile a quello della pr opr iet Filter di OpenFileDialog; ad esempio "*.dll;*.ex e"). IO.Dir ecto r yIndo : oggetto che espone metodi d'istanza simili a quelli sopr a citati. Il costr uttor e accetta come par ametr o il per cor so della car tella IO.Dr iv eInfo : oggetto che espone metodi d'istanza per ottener e infor mazioni su un par ticolar e dr ive. Il costr uttor e accetta come par ametr o il nome del dr ive nella for ma "[Letter a]:\" (faccio pr esente che possibile ottener e una lista dei dr ivder logici con la funzione IO.Dir ector y.GetLogicalDr iver s). I membr i esposti sono per lo pi pr opr iet, come AvaiableFr eeSpace (lo spazio liber o), Dr iveFor mat (il for mato del dr ivder , ad esempio FAT32), Dr iveType (il tipo di dr iver , ad esempio se r appr esenta un lettor e CD o un disco fisso o r emovibile), TotalSize (la dimensione totale) o VolumeLabel (l'etichetta associata). IO.File : espone solo metodi statici che per mettono la modifica, l'analisi, la cr eazione e l'eliminazione di file sul computer . I metodi pi fr equentemente utilizzati sono Copy, Cr eate, Delete, Ex ists, Move, Replace (r ispettivamente copia, cr ea, elimina, contr olla l'esistenza, sposta o sovr ascr ive un file: in tutti il pr imo par ametr o il per cor so del file). Molto utili sono anche i metodi ReadAllTex t, che legge e r estituisce tutto il testo di un file, Wr iteAllTex t, che scr ive un dato testo in un dato file, e AppendAllTex t, che aggiunge una dato testo a un dato file; di questi metodi esistono anche le var ianti Bytes e Lines, che oper ano su ar r ay di bytes o di str inghe (linee di testo) anzich su una str inga unica. Degne di nota sono infine anche le pr ocedur e Encr ypt e Decr ypt, che cr iptano e decr iptano un file cosicch solo l'utente che li ha codificati li possa legger e, e la funzione GetAttr ibutes insieme con SetAttr ibute, che per mettono di modificar e gli attr ibuti di un file. Da r icor dar e che gli attr ibuti sono posti in un enumer ator e codificato a bit. IO.FileInfo : oggetto che espone metodi d'istanza simili a quelli sopr a citati. Il costr uttor e accetta come par ametr o il per cor so del file. IO.Path : espone solo metodi statici per lavor ar e con nomi di files e dir ector y e inter agir e cone i files tempor anei. Molto usati sono GetFileName e GetFileNameWithoutEx tension, che r estituiscono r ispettivamente il nome di file con o senza estensione di un per cor so completo dato: alla stessa str egua lavor ano i metodi GetDir ector yName e GetEx tension che estr aggono l'estensione o la dir ector y da un per cor so di file completo. Ad esempio: 1. 2. 3. 4. 5. S = C:\Cartella\Sottocartella\file.txt GetFileName(S) = file.txt GetFileNameWithoutExtension(S) = file GetDirectoryName = C:\Cartella\Sottocartella\ GetExtension = .txt

GetTempFileName, invece, cr ea e r estituisce un file tempor aneo con un nome stabilito dal sistema oper ativo, mentr e GetTempPath r estituisce il nome della car tella tempor anea usata cor r entemente. Una featur e inter essante GetRandomFileName, che r estituisce un nome del tutto casuale adatto per file e car telle.

Esempio: File Brow ser


Questo pr ogr amma di esempio per mette di navigar e nelle car telle del computer e di ottener e infor mazioni sui files, usando un sistema di liste simile (ma non uguale) a quello del vecchio Visual Basic 6. L'inter faccia del pr ogr amma sar pi o meno cos:

E questo il codice: 001. Class Form1 002. 'Tiene traccia del drive e della cartella corrente 003. Private CurrentDrive, CurrentDir As String 004. 005. 'Questa funzione permetter di formattare le date 006. 'in poco spazio 007. Private Function FormatDate(ByVal D As Date) As String 008. 'Ad esempio 009. '"luned 26 novembre 2007, ore 19:97" 010. Return D.ToString("dddd dd MMMM yyyy, ore HH:mm") 011. End Function 012. 013. Private Sub lstDrive_SelectedIndexChanged(ByVal sender As Object, _ 014. ByVal e As EventArgs) Handles lstDrive.SelectedIndexChanged 015. If lstDrive.SelectedIndex = -1 Then 016. Exit Sub 017. End If 018. 019.

020. 021. 022. 023. 024. 025. 026. 027. 028. 029. 030. 031. 032. 033. 034. 035. 036. 037. 038. 039. 040. 041. 042. 043. 044. 045. 046. 047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091.

'Procede solo se la periferica pronta If Not (New IO.DriveInfo(lstDrive.SelectedItem).IsReady) Then MessageBox.Show("La periferica non pronta!", "File Browser", _ MessageBoxButtons.OK, MessageBoxIcon.Exclamation) Exit Sub End If 'Memorizza il drive selezionato CurrentDrive = lstDrive.SelectedItem 'Quando si cambia driver, si resetta la lista delle 'cartelle quindi la cartella iniziale uguale 'al drive CurrentDir = CurrentDrive.Clone 'Pulisce la lista delle cartelle lstDir.Items.Clear() 'Quando si seleziona un drive, carica le cartelle 'al suo interno For Each Dir As String In _ IO.Directory.GetDirectories(lstDrive.SelectedItem) lstDir.Items.Add(Dir) Next End Sub Private Sub lstDir_SelectedIndexChanged(ByVal sender As Object, _ ByVal e As EventArgs) Handles lstDir.SelectedIndexChanged 'Solo se eramente selezionato un elemento procede If lstDir.SelectedIndex = -1 Then Exit Sub End If 'Memorizza la cartella selezionata CurrentDir = IO.Path.Combine(CurrentDir, lstDir.SelectedItem) 'Pulisce la lista delle cartelle e dei files lstDir.Items.Clear() lstFiles.Items.Clear() 'Carica le sottocartelle, solo con il nome For Each SubDir As String In IO.Directory.GetDirectories(CurrentDir) 'Si pu fare anche con le cartelle, poich le funzione 'considera solamente il formato della stringa lstDir.Items.Add(IO.Path.GetFileName(SubDir)) Next 'Carica i files interni alla cartella, solo con il nome For Each File As String In IO.Directory.GetFiles(CurrentDir) lstFiles.Items.Add(IO.Path.GetFileName(File)) Next End Sub Private Sub lstFiles_SelectedIndexChanged(ByVal sender As Object, _ ByVal e As EventArgs) Handles lstFiles.SelectedIndexChanged If lstFiles.SelectedIndex = -1 Then Exit Sub End If 'Ottiene le informazioni relative al file 'Path.Combine combina due directory o un file e 'una directory 'per ottenere un percorso completo Dim Info As New IO.FileInfo( _ IO.Path.Combine(CurrentDir, lstFiles.SelectedItem)) lblInfo.Text = String.Format( _ "Nome: {1}{0}Data creazione: {2}{0}Ultimo accesso: {3}{0}" & _ "Ultima modifica: {4}{0}Dimensione totale: {5:N0} bytes", vbCrLf, _ Info.Name, FormatDate(Info.CreationTime), _ FormatDate(Info.LastAccessTime), FormatDate(Info.LastWriteTime), _ Info.Length) End Sub Private Sub cmdParent_Click(ByVal sender As Object, _ ByVal e As EventArgs) Handles cmdParent.Click

Try 092. 'Si reca alla directory precedente nell'ordine 093. 'gerarchico 094. CurrentDir = IO.Directory.GetParent(CurrentDir).FullName 095. 096. lstDir.Items.Clear() 097. For Each SubDir As String In IO.Directory.GetDirectories(CurrentDir) 098. lstDir.Items.Add(IO.Path.GetFileName(SubDir)) 099. Next 100. Catch Ex As Exception 101. 'Se le directory sono le prime in ordine, si 102. 'genera un errore 103. MessageBox.Show("Non possibile risalire pi indietro!", _ 104. "File Browser", MessageBoxButtons.OK, MessageBoxIcon.Exclamation) 105. End Try 106. End Sub 107. End Class

E2. Manipolare il registro di sistema

Il r egistr o di sistema un po' il totum continens delle infor mazioni: navigando fr a le sue chiavi e i suoi valor i, ci si pu tr ovar e qualunque cosa, ed infatti uno dei ber sagli pi agognati dagli spyw ar e. Il sistema oper ativo lo usa per immagazzinar e qualsiasi dato utile al suo funzionamento, dai pr ogr ammi associati ai tipi di file, agli assembly installati, alle estensioni del pannello di contr ollo, fino ai tempi che l'inter faccia impiega per aggior nar si. Con un par agone un p iper bolico, si potr ebbe par agonar e il r egistr o di sistema all'insieme delle var iabili di Window s stesso. Saper lo modificar e oppor tunamente por ta gr andi vantaggi alle applicazioni che si scr ivono. Di contr o, un'azione azzar data potr ebbe causar e molti danni. Se non avete una minima conoscenza di come agir e in questo contesto, saltate il capitolo (oppur e andate a documentar vi e tor nate pi tar di), altr imenti, pr oseguite senza indugio.

Mic rosoft.W in32.Registry Key


Per iniziar e a utilizzar e le classi che agiscono sul r egistr o, bisogna pr ima impor tar e il namespace Micr osoft.Win32. Una volta fatto ci diventa disponibile il tipo Registr yKey, i cui membr i per mettono di eseguir e ogni azione possibile e immaginabile. Una var iabile di questo tipo contiene le infor mazioni r elative alla chiave su cui impostata, e i metodi che apr ono altr e chiavi si r ifer iscono s olo alle sottochiavi di quella consider ata. In vir t di ci, esistono due modi per apr ir e una chiave par tendo da una r oot. Il pr imo consiste nell'utilizzar e i campi pubblici gi impostati della classe Registr y: 1. 2. 3. 4. 5. 6. 'Da notare che il tipo non espone costruttori Dim RegKey As RegistryKey 'Apre la chiave HKEY_CLASSES_ROOT RegKey = Registry.ClassesRoot 'Apre la sottochiave .zip RegKey = RegKey.OpenSubKey(".zip")

Il secondo consiste nell'usar e la funzione OpenRemoteBaseKey. Dopo aver aper to una chiave, si possono usar e i suoi metodi. Eccone un elenco: Close : chiude la chiave e attua le modifiche appor tate Cr eateSubKey(S) : cr ea una sottochiave di nome S. S pu anche esser e un per cor so, come ad esempio "Pr ova\command\shell": in questo caso ver r anno cr eate tutte le chiavi non ancor a esistenti DeleteSubKey(S) : r imuove la sottochiave di nome S DeleteValue(V) : elimina un valor e di nome V all'inter no della chiave Flush : attua tutte le modifiche GetSubKeyNames : r estituisce un ar r ay di str inghe contenente il nome di tutte le sottochiavi. Molto utile per le enumer azioni GetValue(V, D) : ottiene i dati contenuti nel valor e di nome V. Opzionalmente si pu specificar e un secondo par ametr o che costituisce il r isultato dell'oper azione nel caso non esista alcun valor e V nella chiave GetValueKind(V) : r estituisce il tipo di dati contenuto nel valor e V GetValueNames : ottiene un ar r ay di str inghe contenente il nome di tutti i valor i della chiave Name : il nome della chiave OpenRemoteBaseKey(Base, M) : apr e la chiave r oot specificata dall'enumer ator e Base sulla macchina M OpenSubKey(S, W) : apr e la sottochiave S. W un valor e booleano che specifica se si possano eseguir e modifiche sulla sottochiave aper ta. Molte volte causa di er r or i a r untime l'esser si dimenticati di impostar e il secondo par ametr o a Tr ue. La funzione r estituisce Nothing nel caso non sia stata tr ovata la data sottochiave

SetValue(V, A) : imposta ad A il contenuto della chiave V SubKeyCount : il numer o delle sottochiavi SubValueCount : il numer o dei valor i Con il codice pr oposto nel pr ossimo esempio possibile r isalir e all'applicazione usata per apr ir e un cer to for mato di file: 01. Imports Microsoft.Win32 02. 03. Module Module1 04. Sub Main() 05. 06. Dim RegKey As RegistryKey 07. Dim Extension As String 08. Console.Write("Inserire un'estesione valida: ") 09. Extension = Console.ReadLine() 10. 11. 12. RegKey = Registry.ClassesRoot.OpenSubKey(Extension) 13. If RegKey Is Nothing Then Console.WriteLine("Questa estensione non associata a nessuna applicazione!") 14. 15. Console.ReadKey() Exit Sub 16. End If 17. 18. Dim AppKey As String = RegKey.GetValue("") 19. 20. RegKey = Registry.ClassesRoot.OpenSubKey(AppKey) 21. 22. If RegKey Is Nothing Then 23. Console.WriteLine("Non possibile risalire all'applicazione. Dati mancanti.") 24. Console.ReadKey() 25. Exit Sub 26. End If 27. 28. RegKey = RegKey.OpenSubKey("shell\open\command") 29. 30. If RegKey Is Nothing Then 31. Console.WriteLine("Non possibile aprire il file direttamente con 32. un'applicazione.") Console.ReadKey() 33. Exit Sub 34. End If 35. 36. Console.WriteLine("Applicazione usata:") 37. Console.WriteLine(RegKey.GetValue("")) 38. 39. Console.ReadKey() 40. End Sub 41. 42. 43. End Module

E3. Lavorare con i processi

Pr emessa: Se non vi r icor date, o se non sapete, cos' un pr ocesso, vi r imando alla pr ima lezione sulla Reflection.

La c lasse Proc ess


La classe che contiene tutte le infor mazioni su un pr ocesso Pr ocess. Ecco una lista dei suoi membr i pi significiativi: BasePr ior ity : r estituisce un numer o inter o che identifica la pr ior it di un pr ocesso. I valor i che pu assumer e sono: 4 (solo per il ciclo Idle del sistema), 8 (pr ior it nor male), 13 (pr ior it alta) e 24 (molto alta, usata per le applicazioni in tempo r eale). La pr ior it di ciuascun pr ocesso influenza la quantit di tempo macchina che il pr ocessor e gli concede Close() : chiude il pr ocesso e r ilascia le r isor se ad esso associate CloseMainWindow () : chiude il pr ocesso comunicando alla sua finestr a pr incipale di chiuder si. Funziona solo se tale pr ocesso dispone di un'inter faccia gr afica qualsiasi EnableRaisingEvent : deter mina se l'oggetto Pr ocess debba gener ar e un evento quando viene chiuso. In questo modo si pu monitor ar e un pr ocesso e visualizzar e infor mazioni e messaggi alla sua chiusur a Ex itCode : r estituisce il codice di chiusur a del pr ocesso (questa infor mazione, ad esempio, pu esser e chiamata quando il pr ocesso gener a l'evento Ex ited per saper e se ha dato buoni r isultati oppur e no). Si pu pensar e al pr ocesso anche come a una funzione: il sistema oper ativo gli passa degli ar gomenti (da linea di comando) e il pr ocesso r estituisce un codice inter o che deter mina il r isultato delle sue oper azioni. Questo codice viene deter minato dallo stesso pr ogr ammator e che ha scr itto il pr ogr amma, ed gener almente 0 quando il pr ocesso ha dato esiti positivi (chi sa il C conosce bene il r etur n 0 finale di Main). Se si tenta di acceder e a questa pr opr iet pr ima della chiusur a, ver r gener ato un er r or e Ex itTime : r estituisce la data e l'or a esatta della chiusur a del pr ocesso. Anche questa pr opr iet molto utile per monitor ar e i pr ocessi Handle : r estituisce l'indir izzo di memor ia del pr ocesso, sottofor ma di puntator e a inter o (IntPtr ) HandleCount : ottiene il numer o di handles aper ti dal pr ocesso (questo numer o potr ebbe anche cor r isponder e al numer o di finestr e aper te, dato che ogni finestr a ha un pr opr io handle HasEx ited : r estituisce Tr ue se il pr ocesso stato chiuso, altr imenti False Get... : analizzer le funzioni che iniziano per "Get" a par te Id : ottiene l'identificativo univoco del pr ocesso, che non altr o che un Int32. Questo numer o pu esser e utile nell'uso di altr e funzioni, come ad esempio GetPr ocessesById Kill() : fer ma immediatamente il pr ocesso. pi br utale di Close MachineName : nome della macchina sulla quale il pr ocesso in esecuzione. Gener almente r estituisce il nome del computer stesso, ma pu cambiar e ad esempio se si usa una macchina vir tuale MainModule : r estituisce un oggetto System.Diagnostics.Pr ocessModule che identifica il modulo pr incipale del pr ocesso. Con modulo si intende l'assembly dal quale il pr ogr amma stato fatto par tir e. I membr i che questo oggetto espone sono: BaseAddr ess : r estituisce l'indir izzo di memor ia (IntPtr ) all'inizio del quale il modulo stato car icato. Ad esempio, in un'applicazione console, questa pr opr iet r estituisce l'indir izzo di memor ia di Module1 Entr yPointAddr ess : r estituisce l'indir izzo di memor ia in cui collocato l'entr y point, ossia il metodo pr incipale, da cui il pr ogr amma stato avviato. Ad esempio, in un'applicazione console, questa pr opr iet r estituisce l'indir izzo della pr ocedur a Main

FileName : r estituisce il nome del file dal quale stato car icato il modulo, ossia il per cor so su disco del pr ogr amma FileVer sionInfo : r estituisce una cater va di infor mazioni sulla ver sione dell'eseguibile. Non sto neanche ad analizzar e tutti i suoi membr i ModuleMemor ySize : r estituisce la quantit di memor ia, in bytes, che il modulo occupa ModuleName : nome del modulo MainWindow Handle : handle della finestr a pr incipale. Si tr atta dello stesso handle ottenuto con EnumDesktopWindow s nel capitolo pr ecedente MainWindow Title : titolo della finestr a pr incipale. Si tr atta dello stesso titolo ottenuto con GetWindow Tex t nel capitolo pr ecedente Modules : r estituisce una collezione di tutti i moduli car icati dal pr ocesso Pr ior ityClass : come BasePr ior ity r estituisce la pr ior it del pr ocesso, ma attr aver so un enumer ator e. Pu assumer e i seguenti valor i: Idle (ciclo Idle del sistema), Below Nor mal (bassa), Nor mal (nor male), AboveNor mal (alta), High (altissima), RealTime (r appr esenta il maggior valor e di pr ior it possibile: un pr ocesso con pr ior it RealTime ha quasi il 100% di tempo macchina) Pr ivilegedPr ocessor Time : r estituisce un oggetto TimeSpan che indica quanto tempo il pr ocesso ha passato ad eseguir e del codice nel sistema oper ativo Pr ocessName : nome del pr ocesso Responding : r estituisce False se il pr ocesso non r isponde ai comandi, altr imenti Tr ue Star t / Star tInfo : analizzer questi due membr i in seguito Thr eads : r estituisce una collezione di Pr ocessThr ead che r appr esentano tutti i thr ead aper ti dal pr ocesso. Ogni oggetto della collezione espone i seguenti membr i: BasePr ior ity : pr ior it di base del thr ead, espr essa tr amite un numer o Cur r entPr ior ity : pr ior it cor r ente del thr ead. Questa pr opr iet esiste poich il thr ead pu cambiar e la sua pr ior it Id : r estituisce l'identificator e univoco del thr ead (simile a Pr ocess.Id) IdealPr ocess : deter mina l'indice del pr ocessor e sul quale il thr ead ver r ebbe eseguito in manier a ottimale. Vale solo per computer multipr ocessor e Pr ior ityBoostEnabled : deter mina se il thr ead pu r icever e un aumento di pr ior it quanto la sua finestr a r iceve il focus, ossia viene selezionata dall'utente Pr ivilegedPr ocessor Time : r estituisce un oggetto TimeSpan che indica quanto tempo il thr ead ha passato ad eseguir e del codice nel sistema oper ativo Star tAddr ess : r estituisce l'indir izzo di membr ia del metodo pr incipale di questo thr ead Star tTime : r estituisce la data e l'or a esatta in cui il thr ead stato avviato Thr eadState : stato del thr ead TotalPr ocessor Time : r estituisce un oggetto TimeSpan che indica da quanto tempo il thr ead attivo User Pr ocessor Time : r estituisce un oggetto TimeSpan che indica quanto tempo il thr ead ha passato ad eseguir e del codice all'inter no dell'applicazione TotalPr ocessor Time : r estituisce un oggetto TimeSpan che indica da quanto tempo il pr ocesso attivo User Pr ocessor Time : r estituisce un oggetto TimeSpan che indica quanto tempo il pr ocesso ha passato ad eseguir e del codice all'inter no dell'applicazione

Ottenere i proc essi in esec uzione


La classe pr ocess espone anche quattr o funzioni statiche che per mettono di ottener e un ar r ay dei pr ocessi in esecuzione, basandosi su deter minati par ametr i. Ad esempio, Pr ocess.GetCur r entPr ocess r estituisce il pr ocesso attualmente in esecuzione, e per ci quello associato dir ettamente all'applicazione. Pr ocess.GetPr ocess ottiene invece un

ar r ay contenente tutti i pr ocessi attivi sul computer . Invece, Pr ocess.GetPr ocessesByName("nome") r estituisce un ar r ay di tutti i pr ocessi con dato nome, dove tale nome non alter o che il nome dell'eseguibile dal quale sono par titi, ma senza l'estensione. che Ad esempio il Pr ocess.GetPr ocessesByName("ex plor er ") pr ogr amma pr incipale dell'inter faccia r estituisce di un solo In pr ocesso, ultimo, "ex plor er .ex e", costituisce w indow s.

Pr ocess.GetPr ocessById(id) ottiene un pr ocesso con dato id. Nell'esempio che segue, si chiede all'utente di immetter e il nome di un pr ocesso e, se ne esiste un con quel nome, il pr ogr amma visualizza tutte le infor mazioni possibili su di esso, usando le pr opr iet spiegate nel par agr afo pr ecedente: 01. Module Module1 02. 'Ottiene tutte le informazioni possibili su un modulo 03. Public Sub ScanModule(ByVal M As ProcessModule) Console.WriteLine(" Nome modulo: {0}", M.ModuleName) 04. Console.WriteLine(" Handle: {0:X8}", M.BaseAddress.ToInt32) 05. Console.WriteLine(" 06. Handle del metodo principale: {0:X8}", _ 07. M.EntryPointAddress.ToInt32) 08. Console.WriteLine(" Memoria occupata: {0:N0} bytes", _ M.ModuleMemorySize) 09. Console.WriteLine(" Informazioni versione del programma:") 10. 11. With M.FileVersionInfo 12. Console.WriteLine(" Nome file: {0}", .FileName) 13. Console.WriteLine(" Versione file: {0}", .FileVersion) Console.WriteLine(" Descrizione file: {0}", _ 14. .FileDescription) 15. Console.WriteLine(" Versione prodotto: {0}", _ 16. .ProductVersion) 17. 18. Dim Rel As String = "Nessuna" If .IsDebug Then 19. Rel = "Debug" 20. ElseIf .IsPatched Then 21. Rel = "Patch" 22. ElseIf .IsPreRelease Then 23. Rel = "Beta" 24. ElseIf .IsPrivateBuild Then 25. Rel = "Release privata" 26. ElseIf .IsSpecialBuild Then 27. Rel = "Release speciale" 28. End If 29. Console.WriteLine(" Caratteristiche: {0}", Rel) 30. Console.WriteLine(" Copyright: {0}", .LegalCopyright) 31. Console.WriteLine(" Trademark: {0}", .LegalTrademarks) 32. Console.WriteLine(" Commenti: {0}", .Comments) 33. Console.WriteLine(" Nome compagnia: {0}", .CompanyName) 34. End With 35. End Sub 36. 37. 'Formatta un valore timespan 38. Public Function FormatTime(ByVal T As TimeSpan) As String 39. Return String.Format("{0}h {1}m {2}s", T.Hours, T.Minutes, T.Seconds) 40. End Function 41. 42. 'Ottiene tutte le informazioni possibili su un processo 43. Public Sub ScanProcess(ByVal P As Process) 44. Console.WriteLine("Nome processo: " & P.ProcessName) 45. Console.WriteLine(" Handle: {0:X8}", P.Handle.ToInt32) 46. Console.WriteLine(" Handles usati: {0}", P.HandleCount) 47. Console.WriteLine(" Id: {0}", P.Id) 48. Console.WriteLine(" Nome macchina: {0}", P.MachineName) 49. Console.WriteLine(" Moduli:") 50. For Each M As ProcessModule In P.Modules 51. Console.WriteLine() 52. ScanModule(M) 53. Next 54. Console.WriteLine() 55. Console.WriteLine(" Handle finestra principale: {0:X8}", _ 56. P.MainWindowHandle.ToInt32) 57. Console.WriteLine(" Titolo finestra principale: {0}", _ 58. P.MainWindowTitle) 59. Console.WriteLine(" Threads: {0}", P.Threads.Count) 60. Console.WriteLine(" Tempo di esecuzione su OS: {0}", _ 61. 62.

63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91. 92. 93. End

FormatTime(P.PrivilegedProcessorTime)) Console.WriteLine(" Tempo di esecuzione user: {0}", _ FormatTime(P.UserProcessorTime)) Console.WriteLine(" Tempo di esecuzione totale: {0}", _ FormatTime(P.TotalProcessorTime)) End Sub Sub Main() Dim Name As String Dim Processes() As Process 'Fa inserire il nome del processo Console.WriteLine("Inserire il nome di un processo:") Name = Console.ReadLine 'Inizializza la collezione Processes = Process.GetProcessesByName(Name) 'Se l'array vuoto, non c' nessun processo 'aperto con quel nome If Processes.Length = 0 Then Console.WriteLine("Non esiste alcun processo con questo nome!") Else 'Altrimenti enumera tutti i processi aperti For Each P As Process In Processes Console.WriteLine() ScanProcess(P) Next End If Console.ReadKey() End Sub Module

Avviare nuovi proc essi


Per avviar e un nuovo pr ocesso, possibile sceglier e due str ade diver se. La pr ima compor ta l'uso di un nuovo oggetto Pr ocess e della sua pr opr iet Star tInfo, mentr e la seconda usa solamente il metodo statico Pr ocess.Star t. Cominciamo a descr iver e la pr ima. Dopo aver inizializzato un nuovo oggetto Pr ocess: 1. Dim P As New Process Si accede alla pr opr iet Star tInfo e tr amite questa si specificano tutte le infor mazioni necessar ie all'avvio. I membr i di Star tInfo sono: Ar guments : deter mina gli ar gomenti passati a linea di comando. una semplice str inga. Per saper ne di pi sui par ametr i passati da linea di comando, veder e il tutor ial associato nella sezione Appunti Cr eateNoWindow : deter mina se il pr ocesso debba esser e avviato in una nuova finestr a Envir onmentVar iables : una collezione di str inghe che r appr esenta tutte le var iabili d'ambiente. Queste ultime sono speciali tipi di var iabili globali, che non vengono definite dall'applicazione ma dal sistema oper ativo (o dal pr ogr amma che r ichiama l'applicazione stessa) e possono esser e utilizzate in qualsiasi punto del codice. possibile ottener e una var iabile d'ambiente pr ecedentemente impostata con la funzione Envir onment.GetEnvir onmentVar iable("nome var iabile"). Di queste ne esistono alcune pr edefinite, che sono valide per ogni pr ocesso avviato sul computer : per saper le, possiamo usar e questo br eve codice: 01. Module Module1 02. Sub Main() 03. Console.WriteLine("Variabili d'ambiente:") 04. Console.WriteLine() 05. 06. For Each Name As String In Environment.GetEnvironmentVariables.Keys 07. Console.WriteLine("{0} = {1}", Name, _ 08. Environment.GetEnvironmentVariable(Name)) 09. Next 10.

11. Console.ReadKey() 12. End Sub 13. End Module Sul mio computer , questo il r isultato:

P.S.: io non mi chiamo Nicola, eh: quando il tecnico ha fatto backup e for mattazione ha capito male il mio nome, e non mi sono ancor a pr eso la br iga di cambiar e le impostazioni FileName : nome del file da avviar e. Se si tr atta di un eseguibile, ver r avviato il pr ogr amma r elativo. Se si tr atta, invece, di un qualsiasi altr o tipo di file, questo ver r aper to con il pr ogr amma associato, se esiste (ad esempio, un'immagine sar aper ta con Paint). Se l'estensione del file non associata a nessun applicativo, ver r r estituito un er r or e User Name / Passw or d : se il pr ocesso deve esser e avviato in un cer to account, potr ebber o esser e r ichieste delle cr edenziali. Passw or d di tipo Secur eStr ing (vedi capitolo "Lavor ar e con le str inghe") Ver b : se il file non eseguibile, questa str inga deter mina quale azione si debba usar e per apr ir lo. Ad esempio, se FileName un file *.tx t, si potr ebbe usar e "pr int" in questa pr opr iet per stampar lo. Le azioni disponibili per un cer to tipo di file var iano sulla base dei pr ogr ammi associati e sono r eper ibili nel r egistr o di sistema Window Style : deter mina come visualizzar e la finestr a aper ta dal pr ocesso, se Massimizzata, Minimizzata, Nor male con focus o Nor male senza focus Wor kingDir ector y : deter mina la dir ector y di lavor o del pr ocesso Dopo aver impostato le adeguate pr opr iet, si r ichiama semplicemente Star t: 1. Dim P As New Process 2. With P.StartInfo 3. .FileName = "C:\programma.exe" 4. .Arguments = "-t" 5. .WorkingDirectory = "C:\cartella" 6. .WindowStyle = ProcessWindowStyle.Maximized 7. End With 8. P.Start() Questo codice equivale ad apr ir e il pr ompt dei comandi di w indow s e scr iver e: 1. CD "C:\cartella" 2.

C:\programma.exe -t La seconda possibilit consiste nell'usar e la ver sione statica di Star t: 1. Process.Start("nome file") Essa accetta anche altr i par ametr i, che per mettono di impostar e i dati come si far ebbe con Star tInfo.

E4. Multithreading - Parte I

I thr ead sono le ver e unit dinamiche di esecuzione: il computer assegna, infatti, il tem po m acchina (noto anche con il nome di tem po di CPU o tim eslice) a ogni singolo thr ead per volta anzich a un inter o pr ocesso. Ognuno di essi in gr ado di eseguir e un codice pr opr io indipendentemente dagli altr i, la cui esecuzione appar e all'occhio dell'utente simultanea. La macchina, infatti, passa cos velocemente da un thr ead all'altr o che i sensi umani non r iescono a distinguer li. Il par ticolar e tipo di meccanismo usato su Window s detto m ultitask ing pr eem ptiv e, che consente la sospensione di un thr ead in qualsiasi momento: in ver sioni pr ecedenti del sistema oper ativo, er a invece necessar io r ichieder ne esplicitamente la chiusur a (e ci pu ben far intuir e come il cr ash di un solo thr ead causasse la sospesione dell'inter o sistema). Ciascun thr ead conser va una pr opr ia autonomia, pr opr ie var iabili, pr opr i gestor i d'eccezioni, ecceter a... possibile anche assegnar vi una diver sa pr io r ita', a seconda di quanto sia impor tante il compito che esso svolge: thr ead con pr ior it pi alta godr anno di un timeslice maggior e e quindi di maggior tempo e spazio per completar e le pr opr ie oper azioni. Inoltr e, dato che tutti i thr ead consumano memor ia e r ichiedono un cer to tempo di CPU, maggior e la quantit di thr ead aper ti, maggior e sar l'utilizzo di memor ia e il tempo impiegato. Per questo motivo, pr ima di pr ogettar e un'applicazione che implementi questa car atter istica sar ebbe oppor tuno valutar e se non ci siano altr e possibilit o alter native meno complesse. Di solito, il multitasking viene impiegato in oper azioni che r ichiedono un lungo per iodo di esecuzione e che impiegano r isor se complesse come file o connessioni. Poich le r isor se possono esser e condivise tr a pi thr ead, necessar io monitor ar ne l'uso e contr ollar e che non ci siano due o pi tentativi di accesso simultanei, il che potr ebbe condur r e a un loop e di conseguenza a un cr ash dell'applicazione. Ma or a veniamo alla pr atica.

Uso dei Thread


Tutti i metodi e i tipi utilizzati nel multithr eading vengono r aggr uppati sono un unico namespace di nome System.Thr eading. L'oper azione pi r udimentale che si possa eseguir e Star t, che fa par tir e un nuovo thr ead con un cer to metodo. Il costr uttor e accetta un delegate di tipo Thr eadStar t senza par ametr i: questo delegate punta al metodo che dovr esser e eseguito dal thr ead. Un thr ead ter mina quando ha finito il pr opr io compito, quando viene r ichiamato il metodo Stop oppur e quando viene abor tito da se stesso o da un'altr a par te del pr ogr amma con Abor t. Altr a pr ocedur a molto comune Sleep(X), che attende X millisecondi pr ima di eseguir e altr o. Ecco un esempio: 01. Module Module1 02. 'Il metodo da far eseguire al Thread: 03. Sub WriteNumbers() 04. 'Scrive 100 volte il numero 0 sullo schermo 05. For I As Byte = 1 To 100 06. Console.Write("0") 07. 'Aspetta 0.1 secondi prima di continuare. La classe 08. 'Thread espone anche metodi statici come questo, che 09. 'vengono eseguiti dal thread chiamante, in questo 10. 'caso quello che eseguir questa procedura 11. Threading.Thread.Sleep(100) 12. Next 13. End Sub 14. 15. Sub Main() 16. 'Un nuovo thread 17. Dim T As New Threading.Thread(AddressOf WriteNumbers) 18. 19. 'Fa partire il thread 20. 'Una volta avviato, il programma passa alle istruzioni 21.

'successive, poich, come gi detto, il thread in grado 22. 'di gestirsi da solo 23. T.Start() 24. 25. 'A prova di ci, esegue questa routine nel thread 26. 'principale, ossia in Sub Main: 27. For I As Byte = 1 To 100 28. Console.Write("1") 29. Threading.Thread.Sleep(100) 30. Next 31. 32. 'Curiosit 33. 'Se a Thread.Sleep viene passato il valore 0, il thread 34. 'associato ceder il proprio tempo di CPU al 35. 'thread successivo, mentre il valore -1 indica di attendere 36. 'all'infinito (o almento finch non verr abortito) 37. 38. 'Cosa appare alla fine? 39. Console.ReadKey() 40. End Sub 41. End Module Sullo scher mo appar e una sequenza gr osso modo r egolar e di 0 e 1: questi numer i vengono alter nati quasi per fettamente, ma ci sono delle r ipetizioni ogni tanto. Questo mostr a come il thr ead pr incipale che esegue il ciclo degli 1 sia indipendente da quello secondar io che fa cor r er e il ciclo degli 0 (e vicever sa); il timeslice di ognuno viene alter nato cos che eseguano oper azioni quasi contempor anee, ma legger mente sfasate. I metodi del tipo di Star t, ossia che por tano a ter mine una r outine in un thr ead separ ato, vengono detti asincr oni: un esempio il metodo WebClient.Dow nloadFileAsync (scar ica un file da inter net), che si gi analizzato. Or a sar ebbe quanto meno utile poter usar e i meccanismi impar ati in modo un p pi ver satile: bisogna tr ovar e il modo di passar e degli ar gomenti a una pr ocedur a delegate del costr uttor e, poich cos facendo si acquisisce pi multifor mit e il codice meno r igido. Per nostr a for tuna, il costr uttor e suppor ta un over load in cui l'unico par ametr o deve esser e un delegate la cui signatur e accetta un ar gomento di tipo object. Mediante il tipo Object, infatti, possibile tr asmetter e quasliasi tipo di dato. Non da consider ar e limitante il fatto dell'aver e oper azioni di box ing/unbox ing: pr imo per ch non c' altr o modo, secondo per ch, definendo nuove classi, possibile passar e dati anche complessi attr aver so un solo par ametr o. Ecco un esempio: 01. Module Module2 02. 'Il metodo da far eseguire al Thread: 03. Sub WriteNumbers(ByVal Data As Object) 04. 'Scrive Times volte il numero Number sullo schermo 05. For I As Byte = 1 To Data.Times 06. Console.Write(Data.Number) 07. Threading.Thread.Sleep(100) 08. Next 09. End Sub 10. 11. Sub Main() 12. 'Un nuovo thread 13. Dim T As New Threading.Thread(AddressOf WriteNumbers) 14. 15. 'Fa partire il thread 16. 'Questo overload si Start accetta un parametro Object, che, 17. 'prima dell'avvio verr passato come argomento 18. 'della procedura delegate dichiarata nel costruttore, 19. 'in questo caso WriteNumbers 20. T.Start(New ThreadData(8, 120)) 21. 22. For I As Byte = 1 To 100 Console.Write("1") 23. Threading.Thread.Sleep(100) 24. 25. Next 26. 27. Console.ReadKey() End Sub 28. 29. End Module

da r icor dar e che l'applicazione ter mina solo quando vengono por tati a ter mine tutti i suoi thr ead. Un'altr a funzionalit dei thr ead , come gi accennato in pr ecedenza, la pr ocedur a Abor t. Essa speciale in quanto costituisce un modo "sicur o" (per quanto possa esser e sicur o ter minar e br utalmente un thr ead) per metter e fine all'esecuzione di un thr ead. Tuttavia pr esenta alcune par ticolar it: non fer ma immediatamente il codice in eseuzione, ma attende finch non si sia r aggiunto un safe po int, un punto sicur o nel quale possa esser e lanciata senza pr oblemi un'oper azione di Gar bage Collection (un esempio di safe point lo statement Retur n o End all'inter no di un metodo). Ogniqualvolta viene invocato Abor t, il thr ead da abor tir e lancia un'eccezione speciale, di tipo Thr eadAbor tEx ception, che non pu esser e inter cettata dall'applicazione; nonostante ci, se tale thr ead stato fatto par tir e all'inter o di un blocco Tr y, il codice associato alla calusola Finally ver r comunque eseguito. In occasioni eccezionali, esso potr ebbe anche inter venir e per evitar e la pr opr ia "mor te". Altr i metodi meno usati sono: BeginCr iticalRegion : notifica al gestor e di thr ead che il codice sta per intr odur si in una oper azione di vitale impor tanza per il pr ogr amma, nella quale un Abor t o anche una semplice eccezione potr ebber o compr ometter e tutta l'applicazione. In questi casi l'abor t viene posticipato come sopr a descr itto AllocateDataSlot : alloca una slot di memor ia per tutti i thr ead in modo da poter passar e facilmente dati tr a un thr ead e l'altr o. Restituisce un oggetto LocalDataStor eSlot che contiene le infor mazioni necessar ie a r ichiamar e le infor mazioni salvate, pur non esponendo alcun membr o AllocateNamedDataSlot : come sopr a, ma assegna allo slot anche un nome Cur r entCultur e : la cultur a del thr ead Cur r entThr ead : il thr ead che cor r entemente in esecuzione EndCr iticalRegion : notifica al gestor e di thr ead che la zona a r ischio elevato ter minata Fr eeNamedDataSlot : liber a la memor ia associata a uno slot nominale, il cui nome viene passato come pr imo ar gomento del metodo GetData(D) : ottiene i dati associati allo slot di memor ia D GetDomain : r estituisce un oggetto AppDomain che r appr esenta il dominio applicativo nel quale viene eseguito il thr ead GetDomainID : r estituisce l'ID dell'AppDomain in cui viene eseguito il thr ead GetNamedDataSlot(N) : passando il nome N, r estituisce l'oggetto LocalDataStor eSlot associato IsAlive : deter mina se il thr ead in esecuzione IsBackgr ound : deter mina se il thr ead in backgr ound, oppur e lo imposta come tale. Si dicono "in backgr ound" thr ead con bassa pr ior ita' Join : blocca il thr ead chiamante fino a quanto non ter mina il thr ead dal quale stata invocata la pr ocedur a. Bisogna far e attenzione a distinguer e bene i r uoli che inter cor r ono in questo meccanismo, poich se un thr ead r ichiama Join su se stesso, l'applicazione andr in loop. Per questo motivo sono assolutamente da ev itar e istr uzioni come queste: 1. Thread.Join() 2. 'Oppure 3. Thread.CurrentThread.Join Mentr e codici simili a questo sono del tutto cor r etti: 1. 2. 3. 4. 5. 6. Dim T As New Thread(AddressOf Something) T.Start() '... 'Il thread chiamante (ossia quello principale) attende che 'T termini. T il bersaglio della chiamata T.Join()

I due over load del metodo pr evedono la possibilit di specificar e un timeout super ato il quale non pi necessar io attender e oltr e: il pr imo accetta un par ametr o di tipo Int32 che r appr esenta il numer o di

millisecondi di attesa massimo; il secondo r ichiede solo un par ametr o di tipo TimeSpan ManagedThr eadID : r estituisce un identificativo univoco per il thr ead managed Name : il nome (opzionale) del thr ead Pr ior ity : pr opr iet enumar ata che deter mina quale sia la pr ior it del thr ead. Pu assumer e cinque valor i: Nor mal, Below Nor mal (infer ior e alla nor ma), AboveNor mal (super ior e alla nor ma), Highest (massimo) e Low est (minimo) ResetAbor t : annulla l'Abor t di un thr ead SetData(D, O) : imposta il contenuto dello slot di memor ia D sull'oggetto O Sleep : gi analizzato SpinWait(N) : simile a Sleep, solo che N indica il numer o di iter azioni di attesa pr ima di continuar e. Ogni volta che il thr ead pr osegue le sue oper azioni dopo aver r icevuto il timeslice oppor tuno dal gestor e dei thr ead, l'indice di iter azioni aumenta di 1 Thr eadState : definisce lo stato del thr ead. La pr opr iet enumer ata pu assumer e questi valor i: Abor ted (abor tito), Abor tRequested ( in cor so la r icezione della r ichiesta di abor to), Backgr ound (come IsBackgr ound), Running (come IsAlive), Stopped (inter r otto: un thr ead in questo stato non pu mai r ipr ender e), StopRequested (si sta per inter r omper e il thr ead), Suspended (sospeso), SuspendRequested (si sta per sospeder e il thr ead), Unstar ted (non ancor a avviato) e WaitSleepJoin (in attesa a causa del metodo Join o Wait)

Condivisione di dati
Di default, tutti i possibili tipi di var iabili vengono condivisi tr a i thr ead in esecuzione. L'unica eccezione a questa r egola costituita dalle var iabili locali dinamiche, ossia quelle pr esenti all'inter no di metodi o pr opr iet (compr esa anche Sub Main): questo si ver ifica sempr e, anche nel caso in cui i thr ead siano in esecuzione all'inter no del suddetto metodo. Per quanto r iguar da le var iabili Shar ed, ogni thr ead condivide la stessa copia del valor e associato. Se si volesse far e in modo di r ender e tali var iabili r elative al thr ead , ossia thr ead-r elativ e, per cui il lor o valor e si conser vi solo all'inter o di ciascuno, si dovr ebbe usar e l'attr ibuto Thr eadStatic: 01. Module Module3 02. Public Class StaticVar 03. 'La variabile accessibile da ogni membro e da ogni 04. 'istanza della classe, ma assume valori differenti a 05. 'seconda che sia eseguita in un thread piuttosto che 06. 'in un altro 07. 'Una dimostrazione? continuate a leggere 08. <ThreadStatic()> _ 09. Public Shared Value As Int32 10. End Class 11. 12. 'Aumenta la variabile 13. Sub Test(ByVal Increment As Object) 14. 'StaticVar.Value statica, perci ogni thread la dovrebbe 15. 'vedere con lo stesso valore, ma in questo caso ci non 16. 'accade a causa dell'attribut ThreadStatic 17. For I As Byte = 1 To 10 18. Console.WriteLine("Thread {0} -> Value = {1}", _ 19. Thread.CurrentThread.ManagedThreadId, StaticVar.Value) 20. Thread.Sleep(100) 21. StaticVar.Value += CInt(Increment) 22. Next 23. End Sub 24. Sub Main() 25. 26. Dim T(2) As Thread 27. 'Inizia 3 thread diversi con un diverso incremento 28. For I As Byte = 0 To 2 29. 30.

T(I) = New Thread(AddressOf Test) 31. T(I).Start(I + 1) 32. Next 33. 34. Console.ReadKey() 35. End Sub 36. End Module Nell'output si vedr che il thr ead con ID minor e visualizzer tutti i numer i da 0 a 9, quello con ID inter medio solo i par i da 0 a 18, mentr e quello con ID massimo solo i multipli di 3 da 0 a 27. Altr a cir costanza possibile quella in cui il computer su cui gir a l'applicazione sia multipr ocessor e. In questo caso, i r egistr i CPU non sono condivisi e ogni r egistr o associato a un diver so pr ocessor e mantiene una pr opr ia autonomia: i valor i delle var iabili, per ci, se sono modificati da un thr ead che lavor a su un dato pr ocessor e non vengono letti da uno che sia in esecuzione su un pr ocessor e differ ente. Per evitar e er r or i di sor ta, il .Net Fr amew or k mette a disposizione i metodi VolatileWr ite e VolatileRead che per mettono di scr iver e valor i di var iabili in un r egistr o condiviso; inoltr e Memor yBar r ier aggior na tutti i r egistr i al valor e pi r ecente.

E5. Multithreading - Parte II

Avendo a che far e con i thr ead, diventa difficoltoso sincr onizzar e l'accesso alle r isor se. Mi spiego meglio. Si ponga di aver e questo codice: 1. If Str = "Ciao" Then 2. I += 1 3. Str = Nothing 4. End If Or a, per ipotesi Str una var iabile condivisa fr a thr ead, cos come anche I; sempr e per ipotesi, Str ha assunto il valor e "Ciao" pr ima di entr ar e nel blocco If. Il thr ead A contr olla la var iabile e tr ova, giustamente, che Str uguale a "Ciao": nessun pr oblema, pr osegue all'inter no della str uttur a e incr ementa I di uno. Pr opr io dopo il ter mine di quest'ultima oper azione, scade il suo timeslice, e il gestor e dei thr ead concede al thr ead B la sua par te di tempo macchina. Quest'ultimo thr ead vede che Str ancor a uguale alla costante str inga specificata dal pr ogr ammator e, in quanto A si er a inter r otto subito pr ima di passar e all'istr uzione successiva, ossia Str = Nothing: per logica, a sua volta incr ementa I di un'altr a unit e poi pr osegue nor malmente annullando Str . Al ter mine del blocco si ha che I stato incr ementato di due anzich di uno. Pr oblemi del gener e sono in gener e r ar i, ma si possono comunque ver ificar e e la pr obabilit di incontr ar li aumenta par allelamente all'impiego del meccanismo di thr eading. Per r isolver e er r or i come questi si deve sincr o nizzar e l'accesso alle r isor se e si fa uso dello statement SyncLock. Esso ha il compito di r acchiuder e un'ar ea di codice in un blocco unico, in modo che il thr ead che lo sta eseguendo finisca tutte le oper azioni ivi contenute senza esser e distur bato da altr i thr ead, i quali a lor o volta attender anno di poter vi acceder e. La sintassi usata per dichiar ar e SyncLock : 1. SyncLock [Oggetto di lock] 2. 'Istruzioni sincronizzate 3. End SyncLock L'og g etto di lo ck pu esser e un qualsiasi oggetto r efer ence non nullo condiviso tr a i thr ead (ad esempio una var iabile di modulo o di classe o una var iabile statica a cui non sia stato applicato l'attr ibuto Thr eadStatic): una volta entr ati nel blocco SyncLock, l'oggetto viene, per cos dir e, "segnato", in modo che qualsiasi altr o thr ead che cer chi di acceder vi sapr che attualmente in uso e attender il pr opr io tur no. Non impor tante quale sia l'oggetto di lock, n lo il suo tipo: basta che soddisfi i r equisiti sopr a esposti. In una classe si pu benissimo usar e Me al suo posto. Ad esempio: 01. 02. 03. 04. 05. 06. 07. 08. 09. 10. 11. 12. 13. 14. 'Volendo riprendere l'esempio di prima: 'LockObject condivisa (campo di classe, in pi shared), non nulla '(viene usato il costruttore New) e reference (ovviamente, 'Object reference) Private Shared LockObject As New Object() '... 'Questo blocco ora correttamente sincronizzato SyncLock LockObject If Str = "Ciao" Then I += 1 End If End SyncLock

Tuttavia, la sincr onizzazione mediata dal costr utto SyncLock da utilizzar si solo se ver amente indispensabile, poich r acchiuder e tutti i campi o tutti i metodi in un blocco del gener e r ischia di r ender e il codice sia illeggibile sia pi lento e meno economico. Un'altr a par ticolar it di SyncLock che, dietr o le quinte, il compilator e lo implementa inser endovi all'inter no uno statement Tr y, per evitar e di non poter r ilasciar e il lock qualor a si ver ifichino eccezioni, r agion per cui

non si pu saltar vi all'inter no con l'utilizzo di GoTo (vedi capitolo r elativo).

Altri metodi di sinc ronizzazione


Se un inter o oggetto viene esposto alla possibilit di poter venir e manipolato da pi thr ead contempor aneamente, sar ebbe utile applicar vi un attr ibuto speciale che sincr onizza automaticamente l'accesso a tutti i membr i d'istanza: tale attr ibuto si chiama Synchr onization, non espone alcun costr uttor e usato fr equentemente e appar tiene al namespace System.Runtime.Remoting.Contex ts: 1. <System.Runtime.Remoting.Contexts.Synchronization()> _ 2. Public Class AnObject 3. Inherits ContextBoundObject 'Altro dettaglio: l'oggetto deve ereditare da ContextBoundObject 4. '... 5. 6. End Class

Come alter nativa a SyncLock, esiste l'oggetto Monitor , che espone metodi statici per la sincr onizzazione. Enter accetta un ar gomento, che costituisce l'oggetto di lock, e incr ementa il contator e di lock di 1, cosicch gli altr i thr ead che tentino di acceder e al codice successivo a Enter debbano attender e (esattamente come accade con SyncLock). Ex it esce dal blocco sincr onizzato, mentr e Tr yEnter cer ca di entr ar e e r estituisce False se non possibile acceder e al blocco monitor ato entr o un timeout specificato come pr imo ar gomento. Dato che essenziale r ilasciar e sempr e il lock, se il sor gente ha la possibilit di lanciar e un'eccezione, bisogna necessar iamente usar e un costr utto Tr y nella cui clausola Finally si r ichiama Ex it. Ad esempio: 01. 02. 03. 04. 05. 06. 07. 08. 09. 10. 11. 12. Private Shared LockObject As New Object() '... Try 'Entra nel codice sincronizzato Monitor.Enter(LockObject) '... Catch Ex As Exception 'Cattura eccezioni se ce ne sono Finally 'Ma rilascia sempre il lock Monitor.Exit() End Try

Il tipo Mutex , invece, pi ver satile: intanto pu esser e istanziato, e inoltr e espone metodi d'istanza in gr ado di gestir e pi lock contempor aneamente. Eccone un elenco: WaitOne : attende di poter entr ar e nella sezione sincr onizzata e, una volta entr ato, ottiene il lock per il thr ead cor r ente WaitAny(M()) : accetta un ar r ay di Mutex M() e attende di poter acquisir e il lock di almeno uno di essi. Questo metodo pu esser e usato ad esempio quando si dispone di un numer o limitato di r isor se (file, connessioni, database...) , ognuna manipolata da un thr ead differ ente WaitAll(M()) : accetta un ar r ay di Mutex M() e attende di poter acquisir e il lock di tutti. Questo metodo pu esser e usato ad esempio quando si devono compier e pi oper azioni contempor aneamente e aspettar e che tutte siano state por tate a ter mine ReleaseMutex : r ilascia il lock SignalAndWait(M1, M2) : tenta di acquisir e il lock di M1 e, una volta acquisito, aspetta che anche M2 venga lockato possibile r ichiamar e WaitOne pi volte, a patto che si r ichiamai lo stesso numer o di volte ReleaseMutex .

Il tipo Semaphor e, invece, contr olla che un deter minato numer o di thr ead possa eseguir e un dato blocco di codice sincr onizzato. Il suo costr uttor e accetta come pr imo par ametr o un inter o che indica il valor e di default dei thr ead che lo stanno eseguendo e come secondo par ametr o il conteggio massimo. Al suo inter no, ogni volta che un thr ead ottiene il lock della sezione contr ollata, il contator e viene decr em entato di 1, fino al r aggiungimento del valor e di default; ogni volta che si r ilascia il lock, esso viene incr em entato di 1, fino al r aggiungimaneto del valor e massimo. WaitOne() ser ve per acquisir e il lock e Release per r ilasciar lo. N.B.: Tutti i tipi fin'or a esposti (Monitor , Mutex e Semaphor e) devono sempr e esser e inclusi in un blocco Tr y, per assicur ar si che anche se si ver ificasser o delle eccezioni, il lock venga comunque r ilasciato.

Delegate asinc roni


Altr a car atter istica che r ende ancor pi ver satili i delegate costituita dalla possibilit di invocar e metodi asincor ni. In questi casi, il metodo puntato dal delegate viene eseguito in un thr ead differ ente, senza quindi bloccar e il nor male cor so di istr uzioni del pr ogr amma, come d'altr onde, sono solite far e tutte le dir ettive asincr one. Il pr imo passo da effettuar e per cr ear e una pr ocedur a del gener e , ovviamente, dichiar ar e il delegate cor r ispondente, ad esempio: 1. 2. 3. 4. 5. 'Questo delegate accetta i parametri adatti a svolgere una ricerca 'di files in pi sottodirectory, esempio gi citato in molte 'lezioni precedenti Public Delegate Function GetFileRecursive(ByVal Dir As String, _ ByVal Pattern As String) As List(Of String)

Il compilator e cr ea automaticamente due metodi speciali per ogni nuovo delegate dichiar ato dal pr ogr ammator e: essi sono BeginInvoke ed EndInvoke. Il pr imo accetta come ar gomenti gli stessi definiti nella signatur e del delegate (in questo caso Dir As Str ing e Patter n As Str ing); inoltr e, la lista dei par ametr i pr osegue con altr i due slot che spiegher in seguito e che per or a imposter semplicemente a Nothing. Bisogna poi specificar e che una funzione, quindi r estituisce un valor e: tale valor e no n il r isultato dell'oper azione, ma un oggetto di tipo IAsyncResult (ossia che implementa l'inter faccia IAsyncResult) che ser ve a for nir e infor mazioni sul pr ogr esso del metodo. Tr a le sue quattr o pr opr iet, una in par ticolar e, IsCompleted, deter mina quando il thr ead che esegue l'oper azione ha por tato a ter mine il suo compito. Il secondo accetta semplicemente lo stesso oggetto IAsyncResult ottenuto in pr ecedenza e, una volta sicur i di aver ter minato il tutto, r estituisce il ver o r isultato della funzione (se c'e'). Ecco un esempio: 01. Module Module1 02. Public Delegate Function GetFileRecursive(ByVal Dir As String, _ 03. ByVal Pattern As String) As List(Of String) 04. 05. Public Function FindFiles(ByVal Dir As String, _ 06. ByVal Pattern As String) As List(Of String) 07. Dim Result As New List(Of String) 08. 09. 'Aggiunge in un solo colpo tutti i files trovati con 10. 'GetFiles 11. Result.AddRange(IO.Directory.GetFiles(Dir, Pattern)) 12. 'Analizza le altre directory 13. For Each SubDir As String In IO.Directory.GetDirectories(Dir) 14. Result.AddRange(FindFiles(SubDir, Pattern)) 15. Next 16. 17. Return Result 18. End Function 19. 20. Sub Main() 21. 'Nuovo oggetto di tipo delegate GetFileRecursive 22. Dim Find As New GetFileRecursive(AddressOf FindFiles) 23. 'Con questo oggetto, monitoreremo lo stato del metodo, per 'sapere se stata completato o se ancora 24. 'in esecuzione. 25. 26. 'Si cercano tutti i files *.dll in una cartella di sistema Dim AsyncRes As IAsyncResult = _ 27. Find.BeginInvoke("C:\WINDOWS\system32", "*.dll", _ 28. 29.

Nothing, Nothing) 30. 'Risultato della ricerca 31. Dim Files As List(Of String) 32. Console.WriteLine("Ricerca di tutti i files *.dll in System32") 33. 34. 'Finch non si completato, scrive a schermo 35. '"Ricerca in corso..." 36. Do Until AsyncRes.IsCompleted 37. Console.WriteLine("Ricerca in corso...") 38. Thread.Sleep(2000) 39. Loop 40. 41. 'Ottiene il risultato 42. Files = Find.EndInvoke(AsyncRes) 43. 'Usa il metodo ForEach di Array per eseguire una stessa 44. 'operazione per ogni elemento di un array. Dato che 45. 'Files una lista tipizzata, la converte in array 46. 'di stringhe, quindi richiama su ogni elemento il metodo 47. 'Console.WriteLine per scriverlo a schermo 48. Array.ForEach(Files.ToArray, AddressOf Console.WriteLine) 49. 50. Console.ReadKey() 51. End Sub 52. End Module All'inter o di IAsyncResult definita anche un'altr a pr opr iet, AsyncWaitHandle, che r estituisce un oggetto WaitHandle: dato che da questo der iva Mutex , lo si pu tr attar e come un comunissimo Mutex , appunto, usando i metodi WaitOne, WaitAny o WaitAll sopr a esposti. Analizziamo or a il penultimo par ametr o di BeginInvoke. un delegate di tipo System.AsyncCallback e costituisce il metodo di callback . Questi tipi di metodi vengono automaticamente r ichiamati dal pr ogr amma alla fine delle oper azioni nel thr ead separ ato: cos facendo non si deve continuamente contr ollar ne il completamento con IAsyncResult.IsCompleted. La sua signatur e deve r ispecchiar e quella di AsyncCallback, ossia deve accettar e un unico par ametr o di tipo IAsyncResult. Ecco lo stesso esempio di pr ima r iscr itto usando questa tecnica: 01. Module Module1 02. Public Delegate Function GetFileRecursive(ByVal Dir As String, _ ByVal Pattern As String) As List(Of String) 03. 04. Public Function FindFiles(ByVal Dir As String, _ 05. 06. ByVal Pattern As String) As List(Of String) 07. Dim Result As New List(Of String) 08. 09. Result.AddRange(IO.Directory.GetFiles(Dir, Pattern)) 10. For Each SubDir As String In IO.Directory.GetDirectories(Dir) 11. Result.AddRange(FindFiles(SubDir, Pattern)) 12. Next 13. 14. Return Result 15. End Function 16. 17. Public Sub DisplayFiles(ByVal AsyncRes As IAsyncResult) 18. Dim Files As List(Of String) = Find.EndInvoke(AsyncRes) 19. Array.ForEach(Files.ToArray, AddressOf Console.WriteLine) End Sub 20. 21. 22. 'Nuovo oggetto di tipo delegate GetFileRecursive 'Notare che dichiarato come variabile globale di modulo per essere 23. 'accessibile anche alla procedura callback 24. Dim Find As New GetFileRecursive(AddressOf FindFiles) 25. 26. 27. Sub Main() 'Il terzo argomento l'indirizzo del metodo callback 28. Dim AsyncRes As IAsyncResult = _ 29. Find.BeginInvoke("C:\WINDOWS\system32", _ 30. "*.dll", AddressOf DisplayFiles, Nothing) 31. 32. Console.WriteLine("Ricerca di tutti i files *.dll in System32") 33. 34.

Do Until AsyncRes.IsCompleted 35. Console.WriteLine("Ricerca in corso...") 36. Thread.Sleep(2000) 37. Loop 38. 39. Console.ReadKey() 40. End Sub 41. End Module L'ultimo par ametr o specifica solamente delle infor mazioni aggiuntive r ichiamabili con IAsyncResult.AsyncState. In gener ale, tutti i metodi che vengono r esi asincr oni, dispongono di due ver sioni, una che inizia per "Begin", l'altr a che inizia per "End", con le stesse car atter istiche sopr a esposte. Anche i metodi BeginWr ite e EndWr ite di IO.FileStr eam sono ottimi esempi di metodi asincr oni.

E6. BackgroundWorker e FileSystemWatcher

Bac kgroundW orker


Dopo aver analizzato nel dettaglio la str uttur a e il funzionamento del sistema di thr eading, veniamo or a a veder e alcuni contr olli che implementano questo meccanismo "dietr o le quinte". Il pr imo di questi Backgr oundWor ker , un contr ollo senza inter faccia gr afica, che nelle Window s Application r ende molto pi facile l'utilizzo di thr ead separ ati per compiti diver si: infatti for nisce metodi e pr opr iet che fanno da w r apper a un thr ead separ ato. Ecco una lista dei membr i pi usati: CancelAsync : annulla le oper azioni che il contr ollo sta svolgendo. Al par i di Thr ead.Abor t, l'azione non immediata e possono anche esser e eseguite istr uzioni che evitino l'abor to del thr ead CancellationPending : deter mina se stato r ichiesto l'annullamento delle oper azioni con CancelAsync IsBusy : deter mina se il contr ollo in fase di esecuzione Repor tPr ogr ess(I) : se r ichiamato della pr ocedur a pr incipale del thr ead separ ato, gener a un evento Pr ogr essChanged contenente infor mazioni sullo stato dell'oper azione. I la per centuale di completamento del lavor o RunWor ker Async : d inizio alle oper azioni tr amite il contr ollo Backgr oundWor ker . Il suo over load accetta un solo par ametr o di tipo Object contenente i par ametr i che opzionalmente si devono passar e alla pr ocedur a pr incipale del thr ead separ ato Wor ker Repor tPr ogr ess : deter mina se il contr ollo possa gener ar e eventi Pr ogr essChanged Wor ker Suppor tCancellation : deter mina se il contr ollo suppor ta la cancellazione delle oper azioni Or a, il Backgr oundWor ker lavor a come descr itto di seguito. La pr ocedur a pr incipale che deve esser e eseguita nel thr ead separ ato va posta in un evento speciale del contr ollo, chiamato DoWor k: attr aver so il par ametr o "e" possibile anche ottener e altr i dati necessar i alle oper azioni da svolger e. Una volta che tutto il codice in DoWor k stato completato, viene lanciato l'evento RunWor ker Completed. Tale evento viene comunque gener ato anche nel caso in cui il sor gente abbia dato esito negativo (ad esempio a causa del ver ificar si di eccezioni gestite e non), oppur e si sia r ichiamata la pr ocedur a CancelAsync. Sempr e all'inter no di DoWor k si pu usar e il metodo Repor tPr ogr ess per comunicar e all'applicazione pr incipale un avanzamento del livello di completamento del lavor o. Chi ha familiar it con i thr ead, sapr che se si tenta di acceder e a qualsiasi contr ollo dell'applicazione pr incipale da un thr ead separ ato, viene gener ata un'eccezione di tipo Cr ossThr eadEx ception. Anche sotto questo punto di vista Backgr oundWor ker for nisce un aiuto non di poco conto poich i suoi eventi sono pr edisposti in modo tale da evitar e er r or i di questo tipo. Infatti DoWor k viene effettivamente eseguito in un diver so contesto, ma gli eventi sono pr odotti nel thr ead pr incipale, in modo da poter acceder e a qualsiasi contr ollo senza pr oblemi. Ecco un esempio commentato. L'inter faccia dovr ebbe pr esentar si come quella che segue. I nomi sono facilmente intuibili dal sor gente. Bisogna invece aggiunger e, ovviamente, il contr ollo Backgr oundWor ker (che non ha inter faccia), con Wor ker Repor tPr ogr ess = Tr ue e Wor ker Suppor tCancellation = Tr ue. Io l'ho chiamato bgw Scan.

E il codice: 001. Imports System.ComponentModel 002. 'In System.ComponentModel 003. Class Form1 004. Private Sub txtDir_Click(ByVal sender As Object, _ 005. ByVal e As EventArgs) Handles txtDir.Click 006. Dim Open As New FolderBrowserDialog 007. Open.Description = "Scegliere la cartella da analizzare:" 008. If Open.ShowDialog = Windows.Forms.DialogResult.OK Then 009. txtDir.Text = Open.SelectedPath 010. End If 011. End Sub 012. 013. Private Sub cmdAnalyze_Click(ByVal sender As Object, _ 014. ByVal e As EventArgs) Handles cmdAnalyze.Click 015. If cmdAnalyze.Text = "Analizza" Then 'Controlla che la cartella esista 016. If Not IO.Directory.Exists(txtDir.Text) Then 017. 018. MessageBox.Show("Cartella inesistente!", "Sizing", _ MessageBoxButtons.OK, MessageBoxIcon.Exclamation) 019. End If 020. 021. 'Fa partire il BackgroundWorker, passandogli come unico 022. 'argomento il percorso della cartella da analizzare 023. bgwScan.RunWorkerAsync(txtDir.Text) 024. 'Modifica il testo del pulsante, cos da potergli 025. 'assegnare anche un altro compito 026. cmdAnalyze.Text = "Ferma" 027. Else 028. 'Il testo non "Analizza". Deve per forza essere 029. '"Ferma", quindi termina l'operazione forzatamente 030. bgwScan.CancelAsync() 031. cmdAnalyze.Text = "Analizza" 032. End If 033. End Sub 034. 035. Private Sub bgwScan_DoWork(ByVal sender As Object, _ 036. ByVal e As DoWorkEventArgs) Handles bgwScan.DoWork 037. 'Ottiene tutti i files presenti nella cartella: 038. '- e.Argument ottiene lo stesso valore passato a 039. ' RunWorkerAsync: in questo caso contiene una stringa 040. '- il pattern *.* specifica di cercare files di ogni 041. ' estensione 042. '- l'ultimo argomento comunica di eseguire una ricerca 043. ' ricorsiva analizzando anche tutte le sottocartelle 044. Dim Files() As String = _ 045. IO.Directory.GetFiles(e.Argument, "*.*", _ 046. IO.SearchOption.AllDirectories) 047. 'Dimensione totale 048. Dim Size As Double = 0 049. 'Files analizzati 050. Dim Index As Int32 = 0 051. 052. 'Calcola la dimensiona totale della cartella sommando tutte 053. 'le dimensioni parziali 054. For Each File As String In Files 055. 'FileLen una funzione di VB6, ma il VB.NET 056. 'implicherebbe di creare un nuovo oggetto FileInfo e 057. 'quindi richiamarne la propriet Length. In 058. 'questo modo molto pi comodo, anche 059. 'se non proprio conforme alle direttive .NET 060. Size += FileLen(File) 061. 062.

063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. End

Next

Index += 1 'Riporta la percentuale e genera un evento 'ProgressChanged bgwScan.ReportProgress(Index * 100 / Files.Length) 'Controlla se ci sono richieste di cancellazione. 'Se ce ne sono, termina qui la procedura If bgwScan.CancellationPending Then e.Cancel = True Exit Sub End If

'Il valore Result di e rappresenta il valore da restituire. 'In questo caso come se DoWork fosse una funzione. 'Dato che si pu passare solo un valore Object, 'mettiamo in quel valore un array di Double contenente 'il numero di files trovati e la loro dimensione complessiva e.Result = New Double() {Files.Length, Size} End Sub Private Sub bgwScan_ProgressChanged(ByVal sender As Object, _ ByVal e As ProgressChangedEventArgs) Handles bgwScan.ProgressChanged 'Visualizza la percentuale sulla barra prgProgress.Value = e.ProgressPercentage End Sub Private Sub bgwScan_RunWorkerCompleted(ByVal sender As Object, _ ByVal e As RunWorkerCompletedEventArgs) Handles bgwScan.RunWorkerCompleted 'Controlla la causa che ha scatenato questo evento If e.Cancelled Then 'Una cancellazione? MessageBox.Show("Operazione annullata!", "Sizing", _ MessageBoxButtons.OK, MessageBoxIcon.Exclamation) ElseIf e.Error IsNot Nothing Then 'Un'eccezione? MessageBox.Show("Si verificato un errore!", "Sizing", _ MessageBoxButtons.OK, MessageBoxIcon.Error) Else 'O semplicemente la fine delle operazioni 'COnverte il risultato ancora in un array di Double Dim Values() As Double = e.Result lblInfo.Text = String.Format("Sono stati trovati {0} files." & _ "{1}La dimensione totale della cartella {2:N0} bytes.", _ Values(0), vbCrLf, Values(1)) End If 'Per sicurezza, reimposta il testo del pulsante cmdAnalyze.Text = "Analizza" End Sub Class

FileSy stemW atc her


L'oggetto FileSystemWatcher ser ve per monitor ar e files o car telle in modo da saper e in tempo r eale quando vengono modificati, cancellati, aper ti o spostati, ed eseguir e azioni di conseguenza. Si potr ebbe, ad esempio, contr ollar e un file speciale e visualizzar e un messaggio di w ar ning se l'utente cer ca di apr ir lo, o chieder e una passw or d, oppur e addir ittur a spegner e il computer (!!). Questo contr ollo, come si pu&ogr av; intuir e, non ha inter faccia gr afica. Le sue pr opr iet inter essanti sono: EnableRisingEvent : deter mina se il contr ollo gener i gli eventi; dato che il suo utilizzo basato su questi, impostar e a Tr ue False tale valor e equivale ad "accender e" o "spegner e" il contr ollo Filter : specifica il filtr o di monitor aggio e si stanno contr ollando dei files. Non altr o che l'estensione dei files IncludeSubdir ector ies : deter mina se includer e nel monitor aggio anche le sottocar telle NotifyFilter : pr opr iet enumer ata codificata a bit che descr ive cosa monitor ar e. Pu assumer e questi valor i:

Dir ector yName (cambiamenti nel nome della/delle car tella/e), FileName (cambiamenti nel nome dei files), Attr ibutes (cambiamenti degli attr ibuti di un file), Size (dimensione di file o car telle), LastAccess (ultimo accesso), LastWr ite (ultima modifica), Cr eationTime (data di cr eazione) e Secur ity (par ametr i di sicur ezza). I valor i possono esser e sommati con l'oper ator e su bit Or Path : il per cor so del file/car tella da monitor ar e Bisogna anche dir e, per , che nel 99% dei casi questo contr ollo fa cilecca... infatti non gener a nessun evento anche quando dovr ebbe. Mister i del .NET Fr amew or k!

E7. Il Platform Invoke

Il platfor m invoke una tecnica che per mette all'applicazione di usar e metodi definiti in libr er ie ester ne. Ovviamente, queste libr er ie non sono scr itte in linguaggi .NET, altr imenti sar ebbe bastato impor tar le come r ifer imento nel pr ogetto. L'utilit pi dir etta che consegue dall'uso del platfor m invoke la possibilit di inter agir e con l'Application Pr ogr amming Inter face (API) di Window s, ossia l'insieme delle libr er ie di sistema. Facendo questo possibile inter venir e a basso livello nel sistema oper ativo e acceder e e manipolar e infor mazioni che non sar ebbe nor malmente consentito conoscer e.

Estrarre un metodo dalle librerie


Una cosa impor tante il fatto che non si pu utilizzar e tutta la libr er ia nel suo insieme, ma si pu solo estr ar ne un metodo alla volta - e nella maggior anza dei casi basta quello. Con i ter mini "estr ar r e un metodo" voglio dir e che nel nostr o pr ogr amma dichiar iamo un metodo nor malmente (con nome, par ametr i, ecceter a...) ma non ne specifichiamo il cor po: quando tale metodo ver r r ichiamato, sar invece usato il metodo scr itto nella libr er ia. Per impor tar e un metodo da una dll di sistema si possono usar e due differ enti modalit. La pr ima - quella miglior e per .NET - consiste nell'utilizzar e l'attr ibuto DllImpor t: 1. <System.Runtime.InteropServices.DllImport("NomeLibreria.dll")> _ 2. Public Sub/Function [Nome]([Parametri]) 3. 4. End Sub/Function L'attr ibuto DllImpor t pr esenta anche moltissime altr e pr opr iet, che per or a non ci ser vono. Se si usa l'attr ibuto DllImpor t, il nome del metodo deve coincider e esattamente con il nome del metodo che si sta impor tando dalla libr er ia (altr imenti il pr ogr amma non sapr ebbe quale sceglier e). Il secondo modo esattamente r ipr eso tale e quale dal Visual Basic 6: 1. Declare Auto Sub [Nome] Lib "NomeLibreria.dll" Alias "VeroNome" ([Parametri]) In questo caso, non occor r e che il nome del metodo coincida con quello della libr er ia, per ch si pu specificar e il ver o nome nella clausola Alias. La keyw or d Auto, invece, opzionale e indica quale set di car atter ei usar e per il passaggio di str inghe: in questo caso, si usa quello pr edefinito. Le due alter native sono Ansi (ascii) e Unicode. Ma questi sono solo dettagli. Nell'esempio che segue user l'attr ibuto DllImpor t, per ch la scelta pi cor r etto in ambito .NET. Questo semplice pr ogr amma tr ova tutte le finestr e aper te sullo scher mo e ne comunica all'utente titolo e indir izzo di memor ia (handle). 001. Imports System.Runtime.InteropServices 002. 003. Module Module1 004. 'Le funzioni che risiedono nelle librerie di sistema lavorano 005. 'a basso livello, come gi detto, perci i tipi 006. 'pi frequentemente incontrati sono IntPtr (puntatore 007. 'intero) e Int32 (intero a 32 bit). Nonostante ci, esse 008. 'possono anche richiedere tipi di dato molto pi complessi, 009. 'come in questo caso. La funzione che useremo necessita di 010. 'un delegate come parametro. 011. Public Delegate Function EnumCallback(ByVal Handle As IntPtr, _ 012. ByVal lParam As Int32) As Boolean 013. 014. 'La funzione EnumDesktopWindows definita nella libreria 015.

016. 017. 018. 019. 020. 021. 022. 023. 024. 025. 026. 027. 028. 029. 030. 031. 032. 033. 034. 035. 036. 037. 038. 039. 040. 041. 042. 043. 044. 045. 046. 047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087.

'C:\WINDOWS\system\system32\user32.dll. Dato che si tratta di una 'libreria di sistema, possiamo omettere il percorso e scrivere solo 'il nome (provvisto di estensione). Come vedete, il nome con cui ' dichiarata lo stesso dl metodo definito 'in user32.dll. Per importarla correttamente, per, anche 'i parametri usati devono essere identici, almeno per tipo. 'Infatti, per identificare univocamente un metodo che potrebbe 'essere provvisto di overloads, necessario sapere solo 'il nome del metodo e la quantit e il tipo di parametri. 'Anche cambiando il nome a un parametro, la signature non 'cambia, quindi mi sono preso la libert di scrivere 'dei parametri pi "amichevoli", poich la 'dichiarazione originale - come tipico del C 'prevede dei nomi assurdi e difficili da ricordare. <DllImport("user32.dll")> _ Public Function EnumDesktopWindows(ByVal DesktopIndex As IntPtr, _ ByVal Callback As EnumCallback, ByVal lParam As Int32) As Boolean 'Notare che il corpo non viene definito End Function 'Questa funzione ha il compito di ottenere il titolo di 'una finestra provvisto in input il suo indirizzo (handle). 'Notare che non restituisce una stringa, ma un Int32. 'Infatti, la maggiore parte dei metodi definiti nelle librerie 'di sistema sono funzioni che restituiscono interi, ma questi 'interi sono inutili. Anche se sono funzioni, quindi, le si 'pu trattare come banali procedure. In questo caso, 'tuttavia, l'intero restituito ha uno scopo, ed equivale 'alla lunghezza del titolo della finestra. 'GetWindowText, dopo aver identificato la finestra, 'ne deposita il titolo nello StringBuilder, che, essendo 'un tipo reference, viene sempre passato per indirizzo. 'Capacity indica invece il massimo numero di caratteri 'accettabili. <DllImport("user32.dll")> _ Public Function GetWindowText(ByVal Handle As IntPtr, _ ByVal Builder As StringBuilder, ByVal Capacity As Int32) As Int32 End Function Public Function GetWindowTitle(ByVal Handle As IntPtr) 'Crea un nuovo string builder, con una capacit 'di 255 caratteri Dim Builder As New System.Text.StringBuilder(255) 'Richiama la funzione di sistema per ottenere il 'titolo della finestra GetWindowText(Handle, Builder, Builder.Capacity) 'Dopo la chiamata a GetWindowText, Builder conterr 'il titolo: lo restituisce alla funzione Return Builder.ToString End Function Public Function FoundWindow(ByVal Handle As IntPtr, _ ByVal lParam As Int32) As Boolean 'Ottiene il titolo della finestra Dim Title As String = GetWindowTitle(Handle) 'Scrive a schermo le informazioni sulla finestra Console.WriteLine("Handle {0:X8} - Titolo: {1}", _ Handle.ToInt32, Title) 'Restituisce sempre True: come gi detto, i 'risultati delle funzioni di sistema non sono sempre 'utili, se non al sistema operativo stesso Return True End Function 'Enumera tutte le finestra Public Sub EnumerateWindows() 'Inizializza il metodo callback Dim Callback As New EnumCallback(AddressOf FoundWindow) Dim Success As Boolean 'Richiama la funzione di sistema. IntPtr.Zero come primo 'parametro indica che il desktop da considerare

088. 089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. End

'quello correntemente aperto. Il secondo parametro 'fornisce l'indirizzo del metodo callback, che verr 'chiamato ogni volta che sia stata trovata una nuova finestra Success = EnumDesktopWindows(IntPtr.Zero, Callback, 0) 'Se la funzione non ha successo, restituisce un errore If Success = False Then Console.WriteLine("Si verificato un errore nell'applicazione!") End If End Sub Sub Main() Console.Clear() Console.WriteLine("Questo programma enumera tutte le finestre aperte.") Console.WriteLine("Premere un tasto qualsiasi per iniziare.") Console.ReadKey() EnumerateWindows() Console.ReadKey() End Sub Module

Il meccanismo di fondo non difficile. Quando si r ichiama la funzione EnumDesktopWindow s, questa par te e cer ca tutte le finestr e attive sul desktop: ogni volta che ne tr ova una r ichiama il delegate Callback passato come par ametr o, passandogli l'indir izzo della finestr a e un ar gomento aggiuntivo che non c'inter essa. Il delegate, quindi, che nel nostr o caso FoundWindow , esegue le azioni necssar ie, ossia la stampa a video delle infor mazioni. Facendo cor r er e il pr ogr amma dovr este veder e una lista assai numer osa, molto di pi di quanto ci si sar ebbe aspettato: questo accade per ch non vengono consider ate finestr e solo le w indow s for ms, ma ci sono anche oggetti di sistema nascosti (che poi non sono altr o che pr ocessi) ed altr i di natur a ingannevole (ad esempio, la bar r a delle applicazioni essa stessa una finestr a). Per r idur r e un po' il numer o di r isultati, si potr ebbe intr odur r e un contr ollo sul titolo, e impor r e che non sia Nothing. Tuttavia, l'impor tante che abbiate capito come funziona il meccanismo del platfor m invoke (PInvoke).

Doc umentazione
Il platfor m invoke facile da usar e, ma molto pi difficile saper e quali funzioni usar e e dove tr ovar le. Se in un pr ogr amma intuite di aver bisogno di funzioni di sistema, la pr ima cosa da far e chieder e in un for um: ci sar sicur amente qualcuno che conosce il metodo adatto da usar e. Successivamente, potete far e una r icer ca su MSDN, il sito documentativo ufficiale della Micr osoft, e ottener e una spiegazione esatta di quello che la funzione fa e dei par ametr i r ichiesti. Alla fine, potete anche consultar e PInv o k e.NET, che espone una lista enor me di tutte le libr er ie di sistema e dei lor o metodi, cor r edate di codice dichiar ativo e talvolta anche di esempi. Quest'ultimo sito, inoltr e, r ipor ta sempr e il numer o e il tipo esatto di par ametr i da usar e: vi r icor do, infatti, che in Visual Basic 6 i tipi numer ici sono differ enti da Visual Basic .NET e copiar e un codice vecchio potr ebbe causar e un er r or e di sbilanciamento dello stack.

E8. La clase Marshal e i puntatori

I puntatori
I puntator i sono speciali var iabili che non contengono un valor e, bens un indir izzo ad un'altr a ar ea di memor ia. I puntator i sono una car atter istica peculiar e del C e dei suoi immediati discendenti e per mettono di gestir e la memor ia a basso livello. Il .NET non suppor ta ufficialmente i puntator i, sebbene in C# sia possibile usar li in cer ti blocchi di codice non gestito. Nonostante ci, definito nel namespace System un tipo str uttur ato di nome IntPtr che r appr esenta un puntator e, anche se molto diver so da quelli tipici del C. Nell'ambito di quest'ultimo linguaggio, infatti, si definisce un puntator e specificando a quali tipi di dato esso punti: un puntator e a Integer , significa, ad esempio, che l'ar ea di memor ia da esso puntata contiene un numer o inter o a 32 bit; allo stesso modo, un puntator e a Point indica che l'ar ea di memor ia puntata contiene una str uttur a di quel tipo, che viene r appr esentata in binar io come sequenza dei suoi membr i, ossia due Int32 (X e Y). Questo consente di eseguir e oper azioni ar itmetiche sui puntator i tenendo conto della dimensione occupata dai dati puntati. Non mi dilungo oltr e nella spiegazione, pur inter essante, poich in .NET tutto questo no n possibile. Esiste solo IntPtr , che r appr esenta un gener ico puntator e.

La c lasse Marshal
Il mars hallin g (da cui il nome della classe) consiste nel passar e da dati gestiti a dati non gestiti e vicever sa. Tipicamente, i "dati gestiti" sono le entit che noi utilizziamo nella pr ogr ammazione ad oggetti: tipi value, str uttur e, oggetti, delegate, ecceter a... Par allelamente, i "dati non gestiti" cor r ispondono alla r appr esentazione gr ezza dei dati in memor ia, ossia semplici sequenze di bytes. Per indicar e dati non gestiti si utilizzano i puntator i, che r ifer iscono dove, nella memor ia di lavor o, sono state allocate le infor mazioni che ci ser vono. Quando si lavor a a basso livello sulla memor ia, tuttavia, bisgna r icor dar si di eseguir e sempr e cer te oper azioni di cui nor malmente non ci pr eoccupiamo, poich vengono amministr ate dal CLR e dal Gar bage Collector : Allocar e la memor ia pr ima dell'uso. Nel caso si debba conver tir e un oggetto nella sua r appr esentazione unmanaged, pr ima necessar io r ichieder e al gestor e della memor ia un cer to spazio da poter utilizzar e per scr iver ci sopr a. Tale spazio deve esser e delle dimensioni pi piccole possibili, per evitar e spr echi; Liber ar e la memor ia dopo l'uso. Quando si finito di elabor ar e, tutta la memor ia esplicitamente allocata va liber ata. In caso ci non venga effettuato, i dati r esidui r imar r anno ad occupar e spazio fino al ter mine dell'esecuzione. Il Gar bage Collector no n pr ovveder al r ilascio della memor ia, poich esso oper a solo in ambiente managed. Ecco un semplice esempio: 01. Imports System.Runtime.InteropServices 02. 03. Module Module1 04. 05. Sub Main() 06. 'Un nuovo Guid. Il Guid un tipo di identificativo 07. 'usato in gran quantit dal Framework. 08. ' un tipo strutturato semplice del namespace 09. 'System, perci l'ho scelto come esempio 10. Dim G As Guid = Guid.NewGuid() 11. 'Calcola la dimensione in bytes di G 12. Dim GuidSize As Int32 = Marshal.SizeOf(G) 13. 'Un puntatore 14. Dim Pointer As IntPtr 15. 16.

'La funzione AllocHGlobal (dove H sta per Handle) alloca 17. 'un certo numero di bytes, passato come parametro, nella 18. 'memoria di lavoro e restituisce un puntatore 19. 'all'area allocata. In questo caso allochiamo un 20. 'numero di bytes pari alla dimensione di G: 21. Pointer = Marshal.AllocHGlobal(GuidSize) 22. 'Copia la struttura G nell'area di memoria puntata 23. 'da Pointer, eventualmente eliminando una vecchia 24. 'struttura se esiste (True) 25. Marshal.StructureToPtr(G, Pointer, True) 26. 27. 'Ora leggiamo un byte alla volta dall'area di memoria 28. 'allocata, ed avremo la rappresentazione binaria 29. 'della variabile G. 30. 'La funzione ReadByte legge e restituisce un byte di 31. 'informazione all'indirizzo puntato da Pointer. Come 32. 'secondo parametro accetta un intero che indica l'offset 33. 'di cui spostarsi rispetto all'indirizzo base 34. For I As Int32 = 0 To GuidSize - 1 35. Console.Write("{0:X2} ", Marshal.ReadByte(Pointer, I)) 36. Next 37. 38. 'Libera la memoria puntata da Pointer. Questa funzione ' un po' pi sofisticata, poich 39. 'non solo libera la memoria strettamente indicata da 40. 'Pointer, ma elimina anche tutti i sottoriferimenti 41. 'contenuti nella struttura. Ad esempio, se la struttura 42. 'contenesse una stringa (che, come sappiamo un 43. 'tipo reference), la rappresentazione binaria 44. 'indicherebbe solo un intero al posto della stringa, 45. 'ossia un puntatore all'oggetto stringa che risiede 46. 'in un'altra parte della memoria. DestroyStructure 47. 'elimina anche riferimenti di questo tipo, assicurando 48. 'che non rimanga alcuna area di memoria inutilizzata 49. Marshal.DestroyStructure(Pointer, GetType(Guid)) 50. 51. Console.ReadKey() 52. End Sub 53. 54. 55. End Module Gli altr i metodi di Mar shal hanno un utilizzo altamente specifico e molto tecnico, per ci non utile analizzar li tutti. I membr i pi comuni sono stati esposti nell'esempio pr ecedente, ed altr e funzioni ver r ano tr attate in seguito par lando di sicur ezza.

F1. Magie con le stringhe

Di tutti i tipi disponibili nel Fr amew or k .Net, sicur amente le str inghe costituiscono quello pi potente, flessibile, ver satile e utile. Saper lavor ar e cone le str inghe, massimizzar e il pr ogr amma, r idur r e i tempi di elabor azione, pr odur r e r isultati miglior i davver o un'"ar te", se cos si pu dir e. Non sempr e i pr ogr ammator i scelgono la via pi giusta: ecco una panor amica delle oper azioni su str inga.

Sy stem.String
Il tipo che espone le str inghe System.Str ing ed essendo un tipo r efer ence, dispone di tr e costr uttor i in over load: il pr imo accetta un car atter e e un inter o X e gener a di conseguenza una str inga for mata da quel car atter e r ipetuto X volte; la seconda accetta un ar r ay di valor i Char , che vengono conver titi in un unico dato str ing; la ter za un ar r icchimento della seconda che per mette di specificar e anche l'indice da cui par tir e e il numer o di car atter i da pr elevar e. Non sempr e il pr ogr ammator e ne a conoscenza, poich la classica dichiar azione di una var iabile di questo tipo "Dim S As Str ing", dalla quale non si evince con evidenza la natur a di oggetto della str inga. Per esser e pr ecisi esse sono oggetti immutabili, checch se ne pensi: infatti una volta assegnatovi un valor e non c' modo di modificar lo. Questo potr ebbe sembr ar e str ano, dato che le assegnazioni non hanno mai pr odotto pr oblemi di sor ta, ma in r ealt non lo . La spiegazione semplice: quando si assegna un valor e a un oggetto str inga, si cr ea implicitamente un nuo v o oggetto str inga e si passa il puntator e ad esso alla var iabile in assegnazione: 1. 2. 3. 4. 'Oggetto stringa inizialmente uguale a Nothing Dim S As String 'Crea un nuovo oggetto stringa "Ciao" e lo assegna a S S = "Ciao"

E lo stesso avviene quando si usa l'oper ator e di concatenazione &: tutti i valor i str inga inter calati dall'oper ator e sono nuovi oggetti cr eati al momento dal pr ogr amma. Per questo motivo l'appr occio della cr eazione di str inghe con l'uso dell'oper ator e sconsigliabile, a favor e invece della funzione For mat, come si vedr da questo elenco di metodi e pr opr iet: Char s(I) : collezione in sola lettur a che contiene tutti i car atter i della str inga, accessibili tr amite l'indice I. Char s la pr opr iet di default della classe, quindi scr iver e S.Char s(0) e S(0) esattamente la stessa cosa Clone : r estituisce un nuo v o oggetto il cui valor e identico alla str inga da cui viene chiamata la funzione Compar e : funzione statica con moltissimi over load che per mette di confr ontar e due str inghe. Implementa ICompar aer .Compar e e r estitusce valor i definiti analizzati nei capitoli sulle inter facce Compar eTo : funzione che implementa l'inter faccia ICompar able.Compar eTo Concat : concatena tutti gli ar gomenti passati Contains(S) : r estituisce Tr ue se S contenuta nella str inga, altr imenti False Copy(S) : come Clone, ma statica Empty : costante che r appr esenta una str inga vuota EndsWith(S) : r estituisce Tr ue se la str inga ter mina con S, altr imenti False Equals(S) : deter mina se la str inga e S contengano lo stesso valor e ( la stessa cosa che utilizzar e l'oper ator e =) For mat(S, ...) : for matta la str inga secondo la str inga di for mato S, utilizzando i par ametr i passati dopo Index Of(C) : r estituisce l'indice di C nella str inga. -1 se la r icer ca non ha pr odotto r isultati. Ha molti over load, che per mettono di cer car e una sottostr inga e di specificar e l'indice a cui iniziar e la r icer ca e la lunghezza del testo da contr ollar e

Index OfAny(C()) : r estituisce l'indice di uno qualsiasi dei car atter i passati come ar r ay in C Inser t(S, I) : inser isce nella str inga un valor e S all'indice I IsNullOr Empty : deter mina se la str inga sia Nothing oppur e vuota Join(S, V()) : concatena i valor i nell'ar r ay V, separ andoli con un separ ator e str inga S LastIndex / LastIndex OfAny : come Index Of, solo l'ultima occor r enza e non la pr ima Length : la lunghezza della str inga PadLeft / PadRight (C, N) : allinea a sinistr a o a destr a la str inga fino alla lunghezza N, r iempiendo gli eventuali vuoti col car atter e C Refer enceEquals(S1, S2) : deter mina se S1 e S2 puntino allo stesso oggetto ( la stessa cosa che utilizzar e l'oper ator e Is) Remove(I, L) : r imuove dalla str inga tutti i car atter i a par tir e dall'indice I, opzionalmente specificandone anche il numer o L Replace(O, N) : r impiazza tutte le occor r enze di O nella str inga con nuovi valor i N Split(C) : spezza la str ingha in pi sottostr inghe utilizzando come separ ator e il car atter e o l'ar r ay di car atter i passato Star tsWith(S) : deter mina se la str inga inizi per S Substr ing(I, L) : ottiene una sottostr inga r icavata pr endendo solo i car atter i dall'indice I in poi, opzionalmente specificandone anche il numer o L ToChar Ar r ay : r estituisce la str inga sotto for ma di ar r ay di car atter i ToLow er : conver te la str inga tutta in minuscolo ToUpper : conver te la str inga tutta in maiuscolo Tr im / Tr imEnd / Tr imStar t : cancella tutti gli spazi bianchi dalla str inga. A seconda della funzione r ichiamata esegue tale pr ocesso sia all'inizio che alla fine, solo alla fine o solo all'inizio. L'unico over load di tutte queste funzioni per mette di specificar e una cer ta gamma di car atter i da eliminar e in luogo degli spazi bianchi Tutti i metodi non statici elencati, bisogna r icor dar lo, sono funzio ni d'istanza, quindi r estituiscono un valor e, ossia una str inga nuova ottenuta modificando quella esistente. Risulta infatti evidente che, siccome le str inghe sono oggetti immutabili, non possano esser e modificate. Per ci il seguente codice non pr odur r alcuna modificazione: 1. 2. 3. 4. 5. 6. 7. Dim S As String = "Ciao" 'Rimuove il primo carattere dalla stringa, ma il valore 'restituito viene perso in quanto non c' 'alcuna assegnazione S.Remove(0, 1) Console.WriteLine(S) '> Ciao

Mentr e questo otter r il r isultato: 1. 2. 3. 4. 5. 6. Dim S As String = "Ciao" 'Rimuove il primo carattere dalla stringa, e pone il 'riferimento alla nuova stringa creata in S S = S.Remove(0, 1) Console.WriteLine(S) '> iao

Nei fr ammenti di codice in cui si gener ano str inghe, tuttavia, molto meglio utilizzar e uno Str ingBuilder .

Sy stem.Text.StringBuilder
Questo oggetto ha il compito ci costr uir e pezzo per pezzo una str inga: pr ogettato in modo da lavor ar e con singoli car atter i alla volta, cos da non cr ear e alcun str inga tempor anea in memor ia, r ispar miando molto spazio e molto tempo. Espone alcuni metodi pr esi da Str ing, come Inser t e Remove. Ha una pr opr iet car atter istica denominata Capacity, che indica il numer o massimo di car atter i contenibili nell'oggetto, cor r elata di funzione Ensur eCapacity che si

assicur a che questa condizione venga r ispettata: dur ante il lavor o, se la lunghezza della str inga in elabor azione r isulta maggior e della capacit, comunque, quest'ultima viene aumentata in modo da ader ir e alle nuove necessit di spazio, eliminando quindi ogni pr oblema. Le pr ocedur e impor tanti sono Append, che accoda una str inga al tutto, AppendLine, che inoltr e manda anche a capo e AppendFor mat, che aggiunge un valor e for mattato con una str inga di for mato, come al solito. Per pr ovar e l'efficacia di Str ingBuilder , ecco un test a pr ova di bomba che calcola le pr estazioni di entr ambi gli utilizzi: 01. Module Module1 02. Sub Main() 03. Dim T As New Stopwatch Dim S As String = "" 04. Dim B As New StringBuilder 05. 06. 07. 'Cronometra quando si impiega a concatenare 100'000 08. 'caratteri con l'operatore & T.Start() 09. For I As Int32 = 1 To 100000 10. S &= "c" 11. 12. Next 13. T.Stop() Console.WriteLine("Operatore & : {0}ms", T.ElapsedMilliseconds) 14. 15. 'Cronometra l'operazione con StringBuilder 16. 17. T.Reset() 18. T.Start() For I As Int32 = 1 To 100000 19. B.Append("c") 20. Next 21. T.Stop() 22. Console.WriteLine("StringBuiler : {0}ms", T.ElapsedMilliseconds) 23. 24. Console.ReadKey() 25. End Sub 26. 27. End Module Sul mio computer , la pr ima ver sione impiega 14'678ms, mentr e la seconda 4ms... Sembr a sbalor ditivo, ma Str ingBuilder , in questo caso quasi 4000 volte pi veloce. Guar dando il consumo di RAM, si noter inoltr e che la pr ima ver sione spr eca in media 2000 bytes in pi di memor ia.

Sy stem.Sec urity .Sec ureString


Quando si memor izzano passw or d o numer i impor tanti come quelli della car ta di cr edito, le str inghe or dinar ie per dono una quantit esager ata di punti in sicur ezza. Infatti, oltr e a esser e r eper ibili nello spazio di memor ia che il pr ogr amma utilizza (anche se ottener e tali indir izzi davver o assai difficile), spesso vengono salvate in file di sistema tempor anei, pi accessibili, oppur e per mangono in diver se copie nella memor ia poich, essendo immutabili, non se ne pu ver amente azzer ar e il valor e. Secur eStr ing un tipo sicur o che consente di evitar e questi r ischi: non pr esenta alcun costr uttor e, non deducibile da una str inga or dinar ia (altr imenti non avr ebbe senso), costr uisce la str inga sicur a un car atter e alla volta usando un algor itmo di cr iptazione per mascher ar e il car atter e. Pr esenta metodi simili a Str ingBuilder , ma che lavor ano solo con un car atter e alla volta. Implementar e una tex tbox che legga una passw or d ad esempio, pu esser e un compito molto semplice: la str inga non viene memor izzata nel contr ollo, che espone un testo omogeneo, ma nell'oggetto Secur eStr ing: l'unico modo per costr uir e una str inga in questo modo un car atter e per volta utilizzando l'evento keypr ess. 01. Dim Password As New SecureString 02. 03. Private Sub TextBox1_KeyPress(ByVal sender As Object, _ 04. ByVal e As KeyPressEventArgs) Handles TextBox1.Click 05. Select Case Asc(e.KeyChar) 06. Case 8 07. 'Il carattere 8 corrisponde al backspace: cancella 08.

09. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. End

'l'ultimo carattere If TextBox1.SelectionStart > 0 Then Password.RemoveAt(TextBox1.SelectionStart - 1) End If '32 rappresenta uno spazio, ed il primo carattere 'intelleggibile nella tabella ASCII. In caso sia maggiore 'o uguale a 32, quindi, il carattere non di controllo Case Is >= 32 'Se il cursore non si trova alla fine della stringa, 'inserisce il carattere all'indice dato If TextBox1.SelectionStart < TextBox1.Text.Length - 1 Then Password.InsertAt(TextBox1.SelectionStart, e.KeyChar) Else Password.AppendChar(e.KeyChar) End If 'Nella textbox, visualizza * e.KeyChar = "*" End Select Sub

Un'alter nativa a questo metodo no n data dall'uso della pr opr iet Passw or dChar della tex tbox , poich essa si limita a mascher ar e ester ior mente il contenuto, che invece per mane immutato nella memor ia. L'unico modo per r ipr ender e i dati cos immessi nell'oggetto Secur eStr ing utilizzar e questo codice, che conver te la passw or d in un puntator e a str inga binar ia e successivamente der efer enzia tale puntator e per ottener e il valor e or iginar io: 01. 02. 03. 04. 05. 06. 07. 08. 09. 10. 11. 12. 13. 14. 15. 16. 17. 'Ottiene il puntatore a stringa BSTR. La sigla indica una Basic 'STRing alias Binary STRing. Le caratteristiche di questa 'stringa risiedono nelle modalit di memorizzazione 'sottoforma di dati nella memoria. composta da un prefisso 'che ne specifica la lunghezza, un contenuto e un terminatore, 'noto come NULL terminator ai programmatori C. 'Non procedo oltre nell'argomento, poich non scopo 'del capitolo trattare questo tipo di stringhe Dim Ptr As IntPtr = Marshal.SecureStringToBSTR(Password) 'Dereferenzia il puntatore e ottiene la password vera Dim Pass As String = Marshal.PtrToStringBSTR(Ptr) 'In questa parte viene usato il valore della stringa 'Quindi il suo spazio di memoria viene istantaneamente liberato per 'evitare che possa essere rintracciata in qualche modo Marshal.ZeroFreeBSTR(Ptr)

Mar shal una classe appar tenente al Namespace System.Runtime.Inter opSer vices e ser ve per copiar e e manipolar e blocchi di memor ia a livello molto basso, senza r iguar do n per il tipo che per la compatbilit, ma solo per indir izzi e dimensioni. il w r apper managed del cor r ispondente metodo unmanaged CopyMemor y della libr er ia ker nel32.dll di Window s. Neppur e questo ar gomento ver r tr attato oltr e in questo capitolo, ma vi r imando alla lezione cor r ispondente della sezione E.

F2. Espressioni regolari

Cos' un'espressione regolare?


Mi sembr a palese che un'espr essione r egolar e sia... un'espr essione r egolar e! Niente di pi di quello che si intende in italiano con questo ter mine, solo con una sfumatur a di str inga: una por zione di testo che, pur non r ipetendosi esattamente uguale, possibile r icondur r e ad uno schema specifico. Ad esempio, un indir izzo email pu esser e r icondotto a questo schema: una sequenza di due o pi car atter i alfanumer ici o under scor e o punti; il simbolo @ un'altr a sequenza di car atter i alfanumer ici; un punto; una sequenza limitata scelta tr a un insieme finito di possibilit. Poich possiamo r iconoscer e un indir izzo e-mail da queste car atter istiche, possiamo anche cr ear e una espr essione r egolar e che lo r appr esenti. Esiste un linguaggio a par te per le espr essioni r egolar i, che uno standar da e viene implementato allo stesso modo in tutti i linguaggi di pr ogr ammazione e di scr ipting esistenti. Per questo motivo, vale la pena spender e qualche capitolo per intr odur r e tale linguaggio. Ed or a andiamo un p pi nello specifico...

Desc rizione del linguaggio Regular Expression


Le espr essioni r egolar i vengono definite attr aver so deter minati patter n, scr itti usando delle specifiche r egole di sintassi e deter minati car atter i "speciali", che svolgono funzioni dispar ate e inter essanti. Si pu consider ar e questo come un linguaggio a par te, che deve anch'esso esser e appr eso al fine di sfr uttar e fino in fondo le potenzialit offer te al pr ogr ammator e. Ecco un piccolo esempio di patter n: 1. [aeiou] Questo r appr esenta una qualsiasi delle vocali: impiegandolo in un metodo di sostituzione, si potr ebber o quindi sostituir e tutte le vocali non accentate di un testo con qualcos'altr o. In questo caso par ticolar e si notato come le par entesi quadr e svolgano una funzione di r aggr uppamento di altr i car atter i: sono dei car atter i "jolly", che vengono inter pr etati dal par ser come dir ettive di compor tamento. Allo stesso modo, si possono r aggr uppar e tali jolly sotto alcune classificazioni: Car atter i di escape : vengono utilizzati per indicar e singoli car atter i e r efer enziar e car atter i non stampabili, ossia di contr ollo. Inoltr e possono for nir e ver sioni "nor mali" dei jolly che assumono par ticolar e significato nelle espr essioni. Esempio: \t (tabulazione), \n (a capo), \[ (una par entesi quadr a, che non viene consider ata come jolly, ma come semplice car atter e); Classi di car atter i : r appr esentano insiemi di car atter i. Nel caso delle par entesi quadr e, non necessar io utilizzar e sequenze di escape per i jolly tr anne che per il car atter e ] e -. Esempio: [aei()ou\-] (uno qualsiasi tr a i car atter i: a, e, i, o, u, (, ) e -) A sser zio ni ato m iche di am piezza zer o : specificano dove debba tr ovar si l'espr essione da cer car e/sostituir e. Esempio: ^ac (la str inga "ac" all'inizio di una r iga) Qualificato r i : specificano quante volte una deter minata espr essione debba appar ir e all'inter no della str inga. Sono distinti in due gr uppi: gr eedy e lazy. I membr i del pr imo confr ontano sempr e quanti pi car atter i

possibili; quelli del secondo invece quanti meno possibili. Esempio: \w * (zer o o pi letter e vicine) Co str utto r i di r ag g r uppam ento : ser vono per r aggr uppar e una o pi espr essioni assieme; assumono par ticolar e utilit in combinazione con i qualificator i, ma anche nei metodi di r icer ca, in quanto possono "mar car e" una deter minata sottostr inga con una chiave che potr esser e usata in seguito per r ecuper ar e quell'espr essione. Esempio: (?<inizio>^\w +) So stituzio ni : indicano di r ipr ender e un dato gr uppo di espr essioni mar cato nel patter n di sostituzione con un costr uttor e di r aggr uppamento. Esempio: se il patter n di sostituzione indica di sostituir e "(?<inizio>^\w +)" con "Linea: ${inizio}", tutte le par ole di almeno una letter e a inizio r iga sar anno sostituite con la str inga "Linea: " seguita dalla par ola Co str utto r i di r ifer im ento all'indie tr o : per mettono di r efer enziar e un par ticolar e gr uppo pr ecedentemente definito nell'espr essione. Esempio: (?<gr p>\s+\w +\s+)ciao\k<gr p> (una par ola separ ata da spazi, seguita da "ciao", seguita dalla stessa par ola di pr ima) Co str utto r i di alte r nanza : for niscono un modo per specificar e alter native. Esempio : (Ciao|Buongior no) Totem! (cer ca una di queste possibilit "Ciao Totem!" e "Buongior no Totem!") Ecco una lista di tutte le espr essioni jolly pi impor tanti: Car atter i di escape \a : campanello \b : tr a par entesi quadr e e nelle sostituzioni, r appr esenta il backspace, altr imenti il limite di una par ola \t : tabulazione \r : r itor no car r ello \v : tabulazione ver ticale \f : avanzamento pagina \n : a capo \e : escape \000 : un car atter e ASCII espr esso in notazione ottale. Deve sempr e aver e tr e cifr e. Ad esempio \040 r appr esenta uno spazio (32 in decimale) \x 00 : un car atter e ASCII espr esso in notazione esadecimale. Lo spazio, ad esempio, \x 20 \* : quando il backslash seguito da un car atter e che sar ebbe un jolly, r appr esenta quel car atter e nor malmente. \* r appr esenta un aster isco (e non ha pi quindi la funzione di qualificator e) Classi di car atter i . : qualsiasi car atter e eccetto l'a capo [abcde] : uno qualsiasi dei car atter i specificati all'inter no delle par entesi [a-z] : il tr attino r appr esenta una ser ie di car atter i consecutivi. In questo caso, tutte le letter e minuscole da a a z [a-z-[aeiuo]] : quando una classe di car atter i appar e nidificata all'inter no di un'altr a e pr eceduta da un segno meno, allor a si consider a la ser ie pr ima espr essa escludendo i car atter i specificati nella seconda coppia di par entesi. In questo caso, tutte le consonanti minuscole \w : un car atter e alfanumer ico o un under scor e (compr ende anche car atter i accentati) \W : negazione di \w , ossia tutti i car atter i non alfanumer ici, inclusi quelli accentati \s : uno spazio bianco. Rappr esenta contempor aneamente uno qualsiasi tr a uno spazio nor male, \t, \r , \v, \f e \n \S : negazione di \s, ossia tutti i car atter i che non sono uno spazio bianco \d : una cifr a decimale \D : negazione di \d A sser zio ni ato m iche di am piezza zer o

^ : inizio di una r iga $ : fine di una r iga (se l'opzione Multiline attiva, come si vedr in seguito) o fine della str inga \A : inizio della str inga (sempr e, anche se Multiline attivo) \Z : fine della str inga (sempr e, anche se Multiline attivo); nel caso ci sia un car atter e di a capo, r appr esenta la posizione immediatamente pr ecedente \z : come \Z, solo che compr ende anche l'a capo \b : limite di una str inga, ossia il pr imo o l'ultimo car atter e di una par ola delimitata da spazi bianchi o altr i car atter i di punteggiatur a \B : negazione di \b Qualificato r i * : zer o o pi cor r ispondenze. Ad esempio \s*Public indica la par ola Public pr eceduta da zer o o pi car atter i di spazio + : una o pi cor r ispondenze ? : zer o o una cor r ispondenza {n} : esattamente n cor r ispondenze. Ad esempio (\b\w +\b){6} indica sei par ole consecutive di almeno un car atter e {n,} : almeno n cor r ispondenze. Ad esempio \*{1,} uguale a \*+ e indica una ser ie di aster ischi {n,m} : da n a m cor r ispondenze *? : la pr ima cor r ispondenza con il minor numer o di r ipetizioni +? : la pr ima cor r isponde con il minor numer o di r ipetizioni, ma almeno una ?? : se possibile zer o, altr imenti una cor r ispondenza {n}? {n,}? {n,m}? : come sopr a Co str utto r i di r ag g r uppam ento (str inga) : una str inga o un'espr essione. Le par entesi vengono numer ate: la pr ima ha indice 1. Ci si pu r ifer ir e ad esse anche con l'indice (?<nome>ex pr ) : cattur a l'espr essione "ex pr " e le assegna il nome "nome" (?=ex pr ) : continua il confr onto solo se l'espr essione a destr a cor r isponde a quella data. Ad esempio \w +(?=,) indica una par ola seguita da una vir gola, ma senza compr ender e la vir gola (?!ex pr ) : continua il confr onto solo se l'espr essione a destr a non cor r isponde a quella data (?<=ex pr ) : continua il confr onto solo se l'espr essione a sinistr a cor r isponde a quella data. Ad esempio (?<=,)\w +\b r appr esenta una par ola che segue una vir gola, ma senza compr ender e la vir gola (?<!ex pr ) : continua il confr onto solo se l'espr esisone a sinistr a non cor r isponde a quella data So stituzio ni $n : sostituisce la sottostr inga r appr esentata dall'espr essione n-esima (si contano solo quelle tr a par entesi tonde). $0 indica tutta la str inga ${nome} : sostituisce la sottostr inga r appr esentata da un'espr essione (?<nome>) $& : analogo a $0 $$ : simbolo del dollar o (solo nelle sostituzioni) Co str utto r i di r ifer im ento all'indietr o \n : si r ifer isce all'n-esimo gr uppo di car atter i all'indietr o a par tir e da \n. Ad esempio (\w )\1 r appr esenta un car atter e r ipetuto ( come se fosse \w \w ) \k<nome> : si r ifer isce a un gr uppo denominato "nome" Co str utto r i di alter nanza

| : indica un'alter nativa tr a due o pi espr essioni. Ad esempio (\.net|\.com|\.it) r appr esenta una qualsiasi delle str inghe ".net", ".com" e ".it" (?(ex pr )s|n) : r appr esenta la par te s se l'espr essione cor r isponde a ex pr , altr imenti la par te n

La c lasse Regex
La classe che r appr esenta un'espr essione r egolar e Regex , facente par te del namespace System.Tex t.Regular Ex pr essions. Ha due costr uttor i ed entr ambi hanno come pr imo par ametr o un patter n. Il secondo par emetr o consiste di un enumer ator e codificato a bit che indica le opzioni con le quali debba esser e eseguita la r icer ca; i valor i pi utili sono: Compiled (compila l'espr essione r egolar e, r endendola pi veloce, ma impiega pi memor ia), Ignor eCase (disattiva il case sensitive), Ignor ePatter nWithSpace (ignor a gli spazi bianchi epliciti, ossia quelli non mar cati da un car atter e di escape \s, e abilita i commenti intr odotti da # nel testo da anlizzar e), Multiline (la r icer ca svolta su un testo di pi r ighe: i car atter i speciali $ e ^ cambiano il lor o significato), Singleline (la r icer ca svolta su un testo di una sola r iga) e RightToLeft (il testo viene analizzato da destr a a sinistr a). Le funzioni pi impor tanti in assoluto sono IsMatch, che contr olla la validit di un'espr essione r egolar e nella str inga data e r estituisce Tr ue o False, Match, che esegue la stessa cosa e r estituisce un oggetto Match, e finine Matches, che r estituisce una collezione a tipizzazione for te MatchCollection. Gli altr i metodi utili di Regex : Escape(S) : sostituisce tutti i car atter i speciali nella str inga con car atter i di escape, quindi r estituisce la nuova str inga GetGr oupNames / GetGr oupNumber s : r estituisce un ar r ay di str inghe o inter i che deter minano i var i gr uppi definiti nel patter n GetGr oupNameFr omNumber / GetGr oupNumber Fr omName : r estituisce il nome di un gr uppo a par tir e dall'indice o vicever sa Replace(T, S) : analizza il testo T con le opzioni definite in pr ecedenza e sostituisce tutte le occor r enze dell'espr essione r egolar e inser tia come patter n nel costr uttor e con la str inga S, che opzionalmente pu contener e Sostituzioni. Alla fine dell'oper azione viene r estituita la str inga r isultante Split(S) : lavor a come la funzione Str ing.Split, solo che il separ ator e costituito dall'espr essione r egolar e Unescape(S) : esegue l'opzione inver sa a Escape, ossia sostituisce tutti i car atter i di escape con car atter i nor mali

Le c lassi Matc h e Matc hCollec tion


Un oggetto di tipo Match il r isultato della funzione Regex .Match, mentr e lo stesso avviene per Regex .Matches con MatchCollection. Un Match una cor r ispondenza dell'espr essione tr ovata all'inter no della str inga da anlizzar e. Se non viene tr ovata nessuna cor r ispondenza, viene r estituito un oggetto Match la cui pr opr iet Success impostata a False. Ecco una lista dei membr i pi usati: Gr oups(N) : r estituisce un oggetto di tipo Gr oupCollection, del quale ogni elemento r appr esenta un singolo gr uppo r acchiuso da par entesi tonde. possibile pr elevar e un gr uppo usando un ar gomento N che pu esser e un indice inter o o una str inga nel caso si siano usati dei costr uttor i di r aggr uppamento. Ogni oggetto Gr oup ha alcune pr opr iet: Index r estituisce l'indice della sottostr inga nella str inga inter a, Length la sua lunghezza, Value il suo valor e e Success se quel gr uppo pr esente oppur e no Index : l'indice del pr imo car atter e che inizia la sottoespr essione tr ovata all'inter no della str inga inter a Length : la lunghezza della sottostr inga tr ovata Success : indica se la r icer ca ha avuto successo oppur e no Value : r estituisce tutta la sottostr inga

Un esempio pratic o
Ecco un esempio: 01. Module Module1 02. Sub Main() 03. Dim Text As String = _ "Questo un testo intervallato da alcuni spazi e " & vbCrLf & _ 04. "un a capo. Inoltre, viene supportata anche la punteggiatura." 05. 06. 'Questa espressione ricerca tutti gli insiemi di almeno un 07. 'carattere separati dal resto del testo da spazi bianchi o 08. 'segni di punteggiatura. 09. 'Ergo: cerca tutte le singole parole Dim R As New Regex("\b\w+\b") 10. 11. Dim Matches As MatchCollection = R.Matches(Text) 12. 13. For Each M As Match In Matches 'chr(34) rappresenta il carattere 34 della tabella ASCII, 14. 15. 'ossia le virgolette 16. Console.WriteLine("All'indice {0}, la sottostringa {1}{2}{1}", _ 17. M.Index, Chr(34), M.Value) 18. Next 19. Console.ReadKey() 20. End Sub 21. 22. End Module E un esempio pi complesso: 01. Module Module2 02. Sub Main() 03. Dim Text As String = String.Format( _ 04. "Sub Prova(){0}" & _ 05. " Dim Int As Int32 = 4{0}" & _ 06. " Dim Str As String = {1}Ciao {1}{0}" & _ 07. " For I As Int32 = Int To 48{0}" & _ 08. " Dim Str2 As String = Str & I{0}" & _ 09. " Console.WriteLine(Str2){0}" & _ 10. " Next{0}" & _ 11. "End Sub", vbCrLf, Chr(34)) 12. 13. 'Questa espressione ricerca tutte le dichiarazioni di 14. 'variabili nel codice sopra 15. Dim R As New Regex( _ 16. "\s*Dim\s+(?<Name>\w+)\s+As\s+(?<Type>\w+)(\s+=\s+(?<Value>[\w"" ]+))?", _ 17. RegexOptions.Multiline) 18. 'Ecco la spiegazione di ogni parte del codice: 19. '\s* : le dichiarazioni possono essere a inizio riga o precedute 20. ' da tabulazioni o spazi. Perci si deve usare *, che 21. ' indica zero o pi ripetizioni ' 22. 'Dim : ovviamente deve essere presenta la keyword Dim 23. 24. ' '(?<Name>\w+) : dopo Dim viene il nome della variabile, 25. ' rappresentato con \w+, ossia almeno un carattere o 26. ' underscore. Questo gruppo chiamato Name, cosicch 27. ' lo potremo riprendere in seguito 28. ' 29. 'As : la clausola As, separata da almeno uno spazio (\s+) 30. ' del nome e dal tipo della variabile 31. ' 32. '(?<Type>\w+) : come Name 33. ' 34. '(...)? : tutto quello che viene ora posto in una coppia di 35. ' parentesi tonde per poter usare il qualificatore ?. Quindi 36. ' tutta questa espressione pu apparire 0 o una volta, 37. ' ossia opzionale. Si tratta dell'inizializzazione 38. ' della variabile in-line 39. ' 40. 41.

'\s+=\s+ : un segno uguale, separato da spazi dal resto 42. ' 43. '(?<Value>[\w" ]+) : il valore della variabile pu 44. ' contenere lettere, underscore, spazi bianchi singoli 45. ' o virgolette.Nell'esempio ci sono due virgolette 46. ' poich ci si trova in una stringa e una 47. ' sola sarebbe interpretata come fine della stringa. 48. ' Due di seguito vengono lette invece come una 49. ' virgoletta nel testo 50. Dim Matches As MatchCollection 51. 52. Matches = R.Matches(Text) 53. 54. For Each M As Match In Matches 55. Console.Write("- Nome: {1}{0} Tipo: {2}{0}", _ 56. vbCrLf, M.Groups("Name").Value, M.Groups("Type").Value) 57. 58. If M.Groups("Value").Success Then 59. Console.WriteLine(" Valore: {0}", M.Groups("Value").Value) 60. End If 61. Next 62. 63. Console.ReadKey() 64. End Sub 65. End Module

F3. Espressioni regolari in azione

Ric erc a di parole


La funzione pr incipale delle espr essioni r egolar i la r icer ca di deter minate sottostr inghe in un testo. Per oper ar e una r icer ca si usa solitamente la funzione Regex .Matches, che r estituisce tutte le occor r enze, oppur e un ciclo nel quale, ad ogni iter azione, si ottiene il match successivo con la funzione Match.Nex tMatch. Entr ambi i metodi eseguono l'oper azione nella stessa manier a, poich anche Matches non viene r iempito tutto subito, ma ad ogni iter azione del ciclo For a cui viene sottoposto, cer ca e inser isce nel r isultato una nuova cor r ispondenza. Ad esempio, questo codice r icer ca nel sor gente di questa pagina tutte le keyw or ds usate per l'indicizzazione dei motor i di r icer ca: 01. Module Module1 02. Sub Main() 03. Dim Text As String = IO.File.ReadAllText("74.php") 04. 'Cerca la definizione del tag, ottenendo l'elenco delle 05. 'parole 06. 07. Dim Keywords As New Regex("\<meta name=""keywords"" content='(?<Keywords>[\w, ]+)'\>", RegexOptions.Multiline) Dim Match As Match 08. Dim Matches As MatchCollection 09. 10. 11. Match = Keywords.Match(Text) 12. 'Se la ricerca ha avuto successo, scandisce ogni parola 13. If Match.Success Then 14. Dim Word As New Regex("\b\w+\b") 15. 16. Dim Index As Int32 = 1 'Dal gruppo Keywords, preleva ogni singola parola 17. Matches = Word.Matches(Match.Groups("Keywords").Value) 18. Console.WriteLine("Parole chiave:") 19. For Each M As Match In Matches 20. 'E le scrive a schermo numerandole 21. Console.WriteLine("{0} - {1}", Index, M.Value) 22. Index += 1 23. Next 24. End If 25. 26. Console.ReadKey() 27. End Sub 28. 29. End Module Quest'altr o esempio, invece, molto pi diver tente e... cattivello. Cer ca in un testo copiato negli appunti tutti gli indir izzi e-mail e li r aggr uppa in una lista, con la possibilit di salvar li in un file di testo. L'inter faccia semplice: compr ende una listbox e due pulsanti. Ed ecco il codice: 01. Class Form1 02. Private Sub cmdSearch_Click(ByVal sender As Object, _ 03. ByVal e As EventArgs) Handles cmdSearch.Click 04. 'Clipboard una propriet di My.Computer, di tipo 05. 'Microsoft.VisualBasic.MyService.ClipboardProxy: un 06. 'oggetto singleton che rappresenta gli appunti. Questo 07. 'codice ottiene il testo copiato nella clipboard con 08. 'la funzione Copia 09. Dim Text As String = Clipboard.GetText 10. 'Questa espressione deve ricercare tutti gli indirizzi 11. 'e-mail presenti nel testo, quindi aggiungerli alla lista: 12. '\b(\w+) : la prima serie di caratteri costituisce 13. ' l'username dell'utente. Ad esempio in 14. ' gianni90@provider.it, gianni90 15. ' 16. '\s* : zero o pi spazi. Pu capitare che per non rendere 17.

18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. End

' l'indirizzo reperibile, si usi questa sintassi: ' "gianni90 at provider dot it" ' '(@|at|\[at\]) : uno qualsiasi tra @, at e [at], a seconda ' di come viene scritto l'indirizzo ' '\s*(\w+)\s* : spazi per lo stesso discorso di prima, poi ' una serie di caratteri che indica il provider. ' '(\.|dot|\[dot\]) : uno qualsiasi tra ., dot e [dot], a ' seconda di come viene scritto l'indirizzo ' '(\w+) : l'ultima serie di caratteri il dominio Dim Email As New Regex( _ "\b(\w+)\s*(@|at|\[at\])\s*(\w+)\s*(\.|dot|\[dot\])(\w+)", _ RegexOptions.Multiline) For Each M As Match In Email.Matches(Text) 'Aggiungi un elemento alla lista e lo spunta 'Attenzione! Bisogna tenere in conto che: '- il gruppo 0 rappresenta sempre tutta la sottostringa ' catturata e non uno dei raggrupamenti presenti '- anche i costruttori di alternanze sono gruppi, ' poich racchiusi entro parentesi tonde 'Perci il risultato sarebbe '0 1 2 3 4 5 ' gianni90[at]provider[dot]it 'Quindi 1 l'username, 3 il provider e 5 il dominio lstEmail.Items.Add(String.Format( _ "{0}@{1}.{2}", M.Groups(1).Value, M.Groups(3).Value, _ M.Groups(5).Value), True) Next End Sub Private Sub cmdSave_Click(ByVal sender As Object, _ ByVal e As EventArgs) Handles cmdSave.Click 'Salva gli indirizzi spuntati Dim Save As New SaveFileDialog Save.Filter = "File di testo|*.txt" If Save.ShowDialog = Windows.Forms.DialogResult.OK Then Dim Writer As New IO.StreamWriter(Save.FileName) 'CheckedItems una collezione in sola lettura che 'restituisce tutti gli elementi spuntati For Each Item As String In lstEmail.CheckedItems Writer.WriteLine(Item) Next Writer.Close() End If End Sub Class

Per pr ovar e il pr ogr amma potete copiar e questo testo: Salve, ragazzi! Il mio indirizzo gianni90@email.it, scrivetemi presto, attendo risposte per la mia domanda. Ciao gianni90: ti devo ricordare che sarebbe meglio se non mettessi il tuo indirizzo in chiaro, poich potrebbe essere pi facilmente rintracciato. Prova invece ad usare questa forma: bartolo[at]prov[dot]com. Ok grazie! Anzi, se invece vuoi, puoi usare anche questo: caio at miap dot net. Vedr di provare anche questo. Ma potrei anche fare delle combinazioni, ad esempio ciack[at]email.fr oppure mark at prov[dot]it... O no? Certo, ottima idea.

V alidazione di espressioni
Le espr essioni r egolar i tor nano utili anche nella validazione di dati immessi dall'utente: possibile contr ollar e che i valor i abbiano un par ticolar e for mato pr ima di pr oceder e in oper azioni che potr ebber o pr odur r e degli er r or i. In questi casi non si usa Matches e di solito si analizza solamente una linea di testo: vengono per lo pi usate le funzioni IsMatch e Match. Ad esempio possibile contr ollar e che un indir izzo e-mail immesso abbia la giusta for mattazione: 01. Module Module2 02. Sub Main() 03. 'Controlla la formattazione dell'indirizzo Dim R As New Regex("(\w+)@(\w+)\.(\w+)") 04. Dim Input As String 05. 06. 07. Do 08. Console.Write("Inserire il proprio indirizzo e-mail: ") Input = Console.ReadLine 09. Loop Until R.IsMatch(Input) 10. Console.WriteLine("Indirizzo accettato!") 11. 12. 13. Console.ReadKey() End Sub 14. 15. End Module Oppur e contr ollar e che numer i e date siano immessi cor r ettamente: 01. Module Module3 02. Sub Main() 03. 'Controlla la formattazione del numero. uso frequente 04. 'nelle validazioni usare i caratteri ^ e $, che indicano 05. 'inizio e fine della stringa, per controllare che all'interno 06. 'ci sia solo il valore che si vuole e non altre cose 07. Dim R As New Regex("^\d{4}$") 08. Dim Input As String 09. 10. Do 11. Console.Write("Inserire un numero intero tra 1000 e 9999: ") 12. Input = Console.ReadLine 13. Loop Until R.IsMatch(Input) 14. Console.WriteLine("Numero accettato!") 15. 16. Console.ReadKey() 17. End Sub 18. End Module 01. Module Module4 02. Sub Main() 03. 'Controlla la formattazione del numero. 04. '\d+ : almeno una cifra 05. ' 06. '(...)? : tutto quello che viene dopo messo tra parentesi 07. ' per poter usare il qualificatore ?. Il numero pu 08. ' infatti anche non essere decimale 09. ' 10. '(\.|,) : virgola o punto 11. ' 12. '\d+ : le cifre decimali, almeno una 13. Dim R As New Regex("^\d+((\.|,)\d+)?$") 14. Dim Input As String 15. 16. Do 17. Console.Write("Inserire un numero anche decimale: ") 18. Input = Console.ReadLine 19. Loop Until R.IsMatch(Input) 20. Console.WriteLine("Numero accettato!") 21. 22. Console.ReadKey() 23. End Sub 24.

End Module 01. Module Module5 02. Sub Main() 03. 'Controlla la formattazione della data 04. 'Una o due cifre, seguite da /, - o |, seguite dalla stessa 05. 'cosa e da due o quattro cifre indicanti l'anno 06. 'Ovviamente non viene controllata la coerenza della data 07. Dim R As New Regex("^\d{1,2}[/\-\|]\d{1,2}[/\-\|](\d{2}|\d{4})$") 08. Dim Input As String 09. 10. Do 11. Console.Write("Inserire una data: ") 12. Input = Console.ReadLine 13. Loop Until R.IsMatch(Input) 14. Console.WriteLine("Data accettata!") 15. 16. Console.ReadKey() 17. End Sub 18. End Module

Parsing di file di dati


Quando l'applicazione non utilizza n x ml n database n altr i tipi par ticolar i di file per il salvataggio di dati, pu impiegar e un file di dati, con estensione *.dat (cer te volte, il softw ar e usa un'estensione pr opr ietar ia). All'inter no di taluni tipi di file si possono immagazzinar e valor i for mattati a piacer e, senza sottostar e a nessuna r egola di sintassi pr edefinita. In questi casi il pr ogr ammator e che cr ea da solo le r egole per scr iver e le infor mazioni sul suppor to. Una gr ammatica che ha usato in passato per molti pr ogr ammi questa: 1. Campo1|Campo2|Campo3|... Dove ogni campo separ ato dagli altr i da un car atter e pipe, e ogni r iga r appr esenta un oggetto diver so. Ecco un esempio: 01. Module Module6 02. Sub Main() 03. 'Scandisce una riga del file, ottenendo i vari valori 04. Dim R As New Regex( _ 05. "^(?<FirstName>[\w\s]+)\|(?<LastName>[\w\s]+)\|(?<BirthDay>.+)$", _ 06. RegexOptions.Multiline) 07. Dim File As String 08. 09. Console.WriteLine("Inserire il nome del file da caricare:") 10. File = Console.ReadLine 11. 12. If Not IO.File.Exists(File) Then 13. Console.WriteLine("File inesistente!") 14. Exit Sub 15. End If 16. 17. 18. 'Classe creata nelle prime lezioni sulle classi 19. Dim P As Person 20. 'Come sopra 21. Dim List As New PersonCollection 22. Dim M As Match 23. Dim Line As String Dim Reader As New IO.StreamReader(File) 24. 25. 26. While Not Reader.EndOfStream 'Legge una linea di testo 27. Line = Reader.ReadLine 28. 'La confronta con l'espressione regolare 29. M = R.Match(Line) 30. 31. 'E se ha successo... If M.Success Then 32. 33.

'Crea un nuovo oggetto Person 34. P = New Person(M.Groups("FirstName").Value, _ 35. M.Groups("LastName").Value, _ 36. Date.Parse(M.Groups("BirthDay").Value)) 37. 'Lo visualizza a schermo 38. Console.WriteLine("{0}, nato il {1}", P.CompleteName, _ 39. P.BirthDay.ToShortDateString) 40. 'E lo aggiunge alla lista 41. List.Persons.Add(P) 42. End If 43. End While 44. 45. Reader.Close() 46. Console.WriteLine("L'et media {0} ", List.AverageAge) 47. 48. Console.ReadKey() 49. End Sub 50. End Module Un file di esempio: 1. 2. 3. 4. Tizio Caio|Sempronio|21/12/2006 Pinco|Pallino|13/01/2000 Chissoio|Nonso|06/07/1990 Mario|Rossi|19/06/1994

Parsing di c odic e
possibile anche analizzar e del codice per ottener e infor mazioni sulle sue var ie par ti. Ad esempio, si possono contar e e r eper ir e tutte le pr ocedur e o tutte le var iabili, con codice annesso, ottener e le linee di codice e di commenti e cos via. Nel pr ogr amma Sour ce Scanner , ho usato le espr essioni r egolar i per scansionar e uno o pi sor genti ed individuar e tutte le occor r enze di ogni membr o di classe. Questo esempio mostr a come far e la stessa cosa con le pr ocedur e: 01. Module Module7 02. Sub Main() 03. 'Scandisce una riga del file, ottenendo i vari valori 04. Dim Argument As String = "(ByVal|ByRef) (?<Arg>\w+) As (?<Type>\w+)" Dim R As New Regex( _ 05. 06. "\s*[\w\s]*\s*Sub\s+(?<Name>\w+)\((" & Argument & "\W*)*\)\s*$", _ 07. RegexOptions.Multiline) 08. Dim File As String 09. Console.WriteLine("Inserire il nome del file da caricare:") 10. 11. File = Console.ReadLine 12. 13. If Not IO.File.Exists(File) Then 14. Console.WriteLine("File inesistente!") 15. Exit Sub 16. End If 17. 18. For Each M As Match In R.Matches(IO.File.ReadAllText(File)) 19. Console.Write(M.Groups("Name").Value) 20. Console.Write("(") 21. 'Dato che ci possono essere pi argomenti, ogni gruppo Arg 'e Type pu venire catturato pi volte. In questo caso 22. 'la propriet Captures restituisce ogni singola 23. 24. 'istanza del gruppo For I As Int32 = 0 To M.Groups("Arg").Captures.Count - 1 25. Console.Write("{0} As {1}", M.Groups("Arg").Captures(I), _ 26. M.Groups("Type").Captures(I)) 27. If I < M.Groups("Arg").Captures.Count - 1 Then 28. Console.Write(", ") 29. End If 30. Next 31. Console.WriteLine(")") 32. Next 33. 34. 35.

Console.ReadKey() 36. End Sub 37. End Module

F4. Drag and Drop

Con il ter mine "Dr ag and Dr op" si indica una tecnica visuale che per mette di tr ascinar e dati da un contr ollo su un altr o contr ollo con il solo ausilio del mouse. assai utile poich per mette all'utente di ottener e il massimo gr ado di inter azione con il pr ogr amma con il minimo sfor zo. Per far s che un contr ollo possa r ecepir e dati spostati mediante Dr ag and Dr op, la sua pr opr iet Allow Dr op deve esser e impostata a Tr ue. L'oper azione di tr ascinamento inizia quando viene pr emuto il pulsante sinistr o del mouse sul contr ollo, per ci nell'evento MouseDow n. Si cr ei ad esempio un for m con due tex tbox vuote, e Allow Dr op di una su Tr ue: 1. Private Sub TextBox1_MouseDown(ByVal sender As Object, _ 2. ByVal e As EventArgs) Handles TextBox1.MouseDown 3. 'Inizia l'operazione di Drag e Drop dalla textbox numero 1, 'usando come dati da trasportare il suo testo. L'effetto 4. 'del mouse, invece, deve essere quello usato per la copia 5. 6. TextBox1.DoDragDrop(TextBox1.Text, DragDropEffects.Copy) 7. End Sub DoDr agDr op un metodo appar tenente alla classe Contr ol e per ci viene er editato da tutti i contr olli. Il pr imo par ametr o costituito dall'insieme dei dati da passar e nell'oper azione, mentr e il secondo un enumer ator e che definisce le modalit di spostamento. Queste non influiscono sul compor tamento del meccanismo a meno che non lo voglia il pr ogr ammator e: infatti tutto il codice per il tr avaso e la manipolazione dei dati viene scr itto manualmente. Or a che si possono iniziar e oper azioni di Dr ag&Dr op, non tuttavia ancor a possibile por tar le a ter mine: manca infatti il codice che gestisce il meccanismo sul contr ollo r icevente. Per pr ima cosa bisogna contr ollar e in entr ata, che ci siano dati e, in questo caso, che siano coer enti con il contenuto del contr ollo. Per far questo si utilizza l'evento Dr agEnter , che notifica quando il mouse entr a nell'ar ea specificata. 01. Private Sub TextBox2_DragEnter(ByVal sender As Object, _ 02. ByVal e As DragEventArgs) Handles TextBox2.DragEnter 03. 'Se contiene i dati giusti di tipo String 04. If e.Data.GetDataPresent(GetType(String)) Then 05. 'Continua a copiare 06. e.Effect = DragDropEffects.Copy 07. Else 08. 'Altrimenti annulla l'azione 09. e.Effect = DragDropEffects.None 10. End If 11. End Sub Il ter zo passo il pi impor tante e per mette di scr iver e il pezzo di codice per la gestione effettiva dei dati. Quando il mouse viene r ilasciato, si gener e l'evento Dr agDr op, nel quale si deve oper ar e: 1. Private Sub TextBox2_DragDrop(ByVal sender As Object, _ 2. ByVal e As DragEventArgs) Handles TextBox2.DragDrop 3. 'Ottiene i dati di tipo string presenti in memoria 4. Dim S As String = e.Data.GetData(GetType(String)) 5. 'Imposta il testo della seconda textbox uguale a quello 6. 'della prima 7. TextBox2.Text = S 8. End Sub In questo esempio si cr eato un meccanismo molto semplice che per mette di tr ascinar e del testo da una tex tbox ad un'altr a, ma nulla vieta di far lo con ar gomenti assai pi complessi, come ad esempio il Dr ag&Dr op di file. Quest'ultimo si pu effettuar e dall'ex plor er di w indow s sui pr ogr ammi .net semplicemente contr ollando che i dati siano coer enti a DataFor mat.FileDr op: in questo caso i dati sono un ar r ay di str inghe contenenti i per cor si completi dei file.

F5. La classe Graphics

La gr afica una delle par ti meno usate, o meno compr ese, del Fr amew or k .NET. Essenzialmente ser ve a disegnar e tutto quello che il suppor to .NET di per s non pr ogettato per far e. Ad esempio, si possono cr ear e gr afici, modificar e immagini e r ipr odur r e effetti par ticolar i. Tutta l'infr astr uttur a di contr ollo della gr afica si basa su una classe por tante, chiamata Gr aphics, che non possiede alcun costr uttor e: per questo motivo no n istanaziabile. Dopo aver chiar ito un concetto del gener e, dovr ebbe sor ger e spontaneamente il dubbio su come si possa far e, allor a, per usar la, dato che non espone metodi statici e che non pu esser e inizializzata. La r isposta semplice: ogni contr ollo possiede un pr opr io oggetto Gr aphics associato, per mezzo del quale viene disegnato sullo scher mo e gr azie a cui il pr ogr ammator e inter viene nella sua visualizzazione. Questo fa pensar e che in r ealt il costr uttor e esista, ma sia specificato come Pr ivate (o al massimo Fr iend) e per ci accessibile solo all'inter no degli oscur i meccanismi .NET, i quali si occupano di for nir ne uno a ogni contr ollo dur ante la costr uzione dell'inter faccia. Bisogna comunque r icor dar e che ci sono metodi statici factor y per la cr ezione di Gr aphics a par tir e da altr e immagini o da altr e finestr e, ma nessuna for nisce un nuovo oggetto vuoto. N.B.: Diver samente dall'appr occio adottato nelle ver sioni pr ecedenti della guida, non user emo l'evento Paint per disegnar e su un contr ollo. Tale evento viene gener ato ogniqualvolta un contr ollo deve esser e r idisegnato sullo scher mo e per ci, se ne facessimo uso, far emmo eseguir e lo stesso codice pi volte senza bisogno. Piuttosto, user emo un'alter nativa pi elengate e decisamente pi per for mante. Cr eer emo una nuova immagine vuota, associandovi un oggetto Gr aphics, e disegner emo su questa immagine, che ver r poi depositata su un contr ollo (o sul suo sfondo). possibile eseguir e questa oper azione fino a un centinaio di volte al secondo senza che l'utente si accor ga di quei fastidiosi sfar falii che si avver tono quando si inser isce il codice di disegno nell'evento Paint. Ecco or a un elenco dei membr i pi impor tanti di Gr aphics: Clear (C) : cancella tutto il contenuto di Gr aphics, nei suoi mar gini, e lo r impie con un color e unifor me definito da C CompositingMode : deter mina il modo in cui due o pi immagini sovr apposte vengano disegnate. deter minato da un enumer ator e che assume solamente due valor i: Sour ceCopy (la par te pi r ecente r impiazza quella esistente, "sovr ascr ivendo" i pr opr i color i sui vecchi) e Sour ceOver (le due par ti vengono sovr apposte in modo da for mar e una sfumatur a, in cui entr a pr epotentemente in gioco il fattor e Alpha, ossia la tr aspar enza, che deter mina quale dei due color i pr evalga sull'altr o e come debbano esser e miscelati) CompositingQuality : deter mina la qualit dell'oper azione di composizione sopr a illustr ata. I valor i dell'enumer ator e sono pochi, e per mettono di sceglier e se ottener e una maggior qualit o una maggior velocit oppur e se consider ar e il fattor e di cor r ezione gamma CopyFr omScr een(P1, P2, Size) : questa pr ocedur a davver o molto utile. Per mette di cattur ar e una par te dello scher mo e r ipr odur la sul suppor to dell'oggetto Gr aphics (che, in definitiva, la super ificie del contr ollo a cui esso appar tiene). Accetta tr e ar gomenti: il pr imo, P1 As Point, deter mina il mar gine super ior e sinistr o della r egione dello scher mo da cui pr elevar e l'immagine; il secondo, P2 As Point, deter mina il mar gine super ior e sinistr o della r egione di Gr aphics su cui copiar e l'immagine; l'ultimo di tipo Size e specifica lar ghezza e altezza della r egione da pr elevar e. Ad esempio, si supponga che questo codice sia eseguito nell'evento Paint del for m: 1. e.Graphics.CopyFromScreen(New Point(0, 0), New Point(50, 50), _ 2. New Size(200, 200)) Ebbene, il quadr ato di lato 200 pix el che inizia nel punto (0,0) dello scher mo (ossia in alto a sinistr a), ver r copiato nella super ficie del for m a par tir e dal punto (50,50) DpiX, DpiY : r estituiscono r ispettivamente la r isoluzione su X e su Y, in punti per pix el

Dr aw ... : tutte le pr ocedur e che iniziano per "Dr aw " per mettono di disegnar e l'elemento cor r ispondente sul suppor to di Gr aphics. A seconda dell'entit geometr ica, cambiano i par ametr i, che sono sempr e visibili gr azie al fumetto che li sugger isce Fill... : tutte le pr ocedur a che iniziano per "Fill" disegnano una figur a e la r iempiono con lo stesso color e Fr omHdc(Ptr ) : inizializza e r estituisce un oggetto Gr aphics par tendo da un'immagine: di tale immagine si dispone solo di un puntator e inter o (System.IntPtr ). Pu esser e utilizzata in r ar i casi, ad esempio nel Mar shalling di oggetti Fr omHw nd(Ptr ) : inizializza e r estituisce un oggetto Gr aphics par tendo da una finestr a: di tale finestr a si dispone solo dell'handle Fr omImage(Img) : inizializza e r estituisce un oggetto Gr aphics par tendo da un'immagine Img As Image Render ingOr igin : specifica quale sia l'or igine del r ender ing, ossia il punto consider ato (0,0) ResetTr ansor m : annulla ogni tr asfor mazione RotateTr ansfor m(A) : effettua una r otazione di A gr adi su tutti gli elementi di Gr aphics ScaleTr ansfor m(sX, sY) : effettua una tr asfor mazione delle dimensioni, mltiplicandole per sX su X e per sY su Y SmoothingMode : deter mina quale modalit usar e per smussar e linee e per cor si cur vi. Per un buon r isultato si pu usar e l'anti-alias, che r iduce di molto le sgr anatur e evidenti pr odotte dai pix el Tr ansfor mation : r estituisce o imposta una matr ice che r appr esenta tutti i cambiamenti dello spazio 2D Tr anslateTr ansfor m(dX, dY) : tr asla tutto il contenuto di Gr aphics di un vettor e (dX, dY) Nell'esempio che segue, scr iver un pr ogr amma per disegnar e gr afici a tor ta bidimensionali. Il tutto si divide in due diver si sor genti: una libr er ia di classi Gr aphItems e l'applicazione pr incipale.

GraphItems
La libr er ia espone tr e classi. La pr ima Gr aphItem, una classe astr atta che r appr esenta la base per gli altr i elementi. Si usa questo tipo di tecnica poich ser vir immagazzinar e diver si tipi di elementi in una sola lista: per evitar e liste a tipizzazione debole come Ar r ayList, si usa una lista a tipizzazione for te in cui il tipo gener ics collegato costituito da una classe base comune a tutte. Accade molto spesso di usar e questa tecnica, per ci fate attenzione. La seconda classe esposta r appr esenta uno spicchio del gr afico a tor ta e contiene le infor mazioni e la pr ocedur a per poter lo disegnar e. La ter za, invece, r appr esenta l'etichetta cor r ispondente al color e nella legenda: il r isultato che visualizzer sullo scher mo un quadr atino color ato al cui fianco pr esenzia la didascalia associata al color e e il suo valor e. Ecco il codice: 001. 'Questa classe astratta costituisce la base per ogni 002. 'elemento che andr ad essere disegnato sul controllo 003. Public MustInherit Class GraphItem 004. 'Per disegnare delle forme geometriche con i metodi Draw 005. 'si usano oggetti di tipo Pen (penna): una penna definisce 006. 'il colore usato per tracciare le linee e il loro 007. 'spessore. Sono presenti delle penne predefinite nella 008. 'classe statica Pens: una per ogni colore (per tutte, 009. 'l'ampiezza del tratto costante e pari a 1). 010. 'Noi useremo sempre delle penne nere per il contorno 011. 'dei prossimi oggetti, ma ho voluto aggiungere 012. 'questo membro per completezza. 013. Private _ColorPen As Pen 014. 015. 'Allo stesso modo, per riempire delle forme deometriche 016. 'coi metodi Fill, si usano i pennelli (Brush). Brush 017. 'una classe astratta che costituisce la base di tutti 018. 'i pennelli derivati. Noi useremo dei SolidBrush, oggetti 019. 'che colorano un'area con colore uniforme. Tuttavia, come 020. 'spiegher in seguito, esistono molti altri tipi 021. 'di pennelli, ad esempio per eseguire sfumature o per 022. 'riempire un'area con delle immagini 023. Private _ColorBrush As Brush 024.

025. Public Property ColorPen() As Pen 026. Get 027. Return _ColorPen 028. End Get 029. Set(ByVal Value As Pen) 030. _ColorPen = Value 031. End Set 032. End Property 033. 034. Public Property ColorBrush() As Brush 035. Get 036. Return _ColorBrush 037. End Get 038. Set(ByVal Value As Brush) 039. _ColorBrush = Value 040. End Set 041. End Property 042. 043. 'Ogni elemento deve esporre la procedura Draw, 044. 'per mezzo della quale esso disegner la propria 045. 'rappresentazione sul supporto grafico specificato 046. 'da G. Come gi accennato, disegneremo tutto 047. 'su un'immagine vuota creata da noi Public MustOverride Sub Draw(ByVal G As Graphics) 048. 049. End Class 050. 051. 'Un pezzo di torta XD 052. Public Class PiePiece Inherits GraphItem 053. 054. 'I parametri necessari a disegnarla sono: il centro 055. 'della torta, il raggio, l'ampiezza (in gradi) e 056. 'l'angolo iniziale 057. Private _Center As Point 058. Private _Radius As Int32 059. Private _StartAngle, _EndAngle As Single 060. 061. 'Centro 062. Public Property Center() As Point 063. Get 064. Return _Center 065. End Get 066. Set(ByVal Value As Point) 067. _Center = Value 068. End Set 069. End Property 070. 071. 'Raggio 072. Public Property Radius() As Int32 073. Get 074. Return _Radius 075. End Get 076. Set(ByVal Value As Int32) 077. _Radius = Value 078. End Set 079. End Property 080. 081. 'Angolo di partenza 082. Public Property StartAngle() As Single 083. Get 084. Return _StartAngle 085. End Get 086. Set(ByVal Value As Single) 087. _StartAngle = Value 088. End Set 089. End Property 090. 091. 'Ampiezza 092. 093. Public Property SweepAngle() As Single 094. Get 095. Return _EndAngle 096.

097. 098. 099. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121. 122. 123. 124. 125. 126. 127. 128. 129. 130. 131. 132. 133. 134. 135. 136. 137. 138. 139. 140. 141. 142. 143. 144. 145. 146. 147. 148. 149. 150. 151. 152. 153. 154. 155. 156. 157. 158. 159. 160. 161. 162. 163. 164. 165. 166. 167. 168.

End Get Set(ByVal Value As Single) _EndAngle = Value End Set End Property Sub New(ByVal Center As Point, ByVal Radius As Int32, _ ByVal StartAngle As Single, ByVal SweepAngle As Single) Me.Center = Center Me.Radius = Radius Me.StartAngle = StartAngle Me.SweepAngle = SweepAngle End Sub Public Overrides Sub Draw(ByVal G As Graphics) 'Calcola il quadrato in cui inscritta la circonferenza 'della quale lo spicchio fa parte Dim UpperLeft As New Point(Me.Center.X - Me.Radius, _ Me.Center.Y - Me.Radius) 'Calcola la dimensione di tale quadrato Dim Size As New Size(Me.Radius * 2, Me.Radius * 2) 'Riempie il pezzo di torta con il colore. FillPie 'riempie col pennello specificato un settore circolare 'dell'ellisse inscritto nel rettangolo passato come 'parametro, a partire dall'angolo StartAngle, 'spazzando un angolo SweepAngle G.FillPie(Me.ColorBrush, New Rectangle(UpperLeft, Size), _ Me.StartAngle, Me.SweepAngle) 'Quindi disegna il contorno del pezzo in nero. Gli 'argomenti sono gli stessi, ad eccezione della penna 'al posto del pennello. Pens.Black una 'penna nera di tratto 1 G.DrawPie(Pens.Black, New Rectangle(UpperLeft, Size), _ Me.StartAngle, Me.SweepAngle) End Sub End Class 'Un'etichetta che visualizza il colore e il testo 'corrispondente Public Class ColorLabel Inherits GraphItem 'I parametri necessari a disegnarla sono: il testo, 'le coordinate e il colore, che viene definito 'nella classe base Private _Text As String Private _Location As Point 'Testo Public Property Text() As String Get Return _Text End Get Set(ByVal Value As String) _Text = Value End Set End Property 'Coordinate Public Property Location() As Point Get Return _Location End Get Set(ByVal Value As Point) _Location = Value End Set End Property Sub New(ByVal Text As String) Me.Text = Text End Sub

169. 170. 171. 172. 173. 174. 175. 176. 177. 178. 179. 180.

Public Overrides Sub Draw(ByVal G As System.Drawing.Graphics) 'Disegna un quadratino colorato G.FillRectangle(Me.ColorBrush, New Rectangle(Me.Location, _ New Size(20, 10))) 'Disegna il contorno nero al quadratino G.DrawRectangle(Pens.Black, New Rectangle(Me.Location, _ New Size(20, 10)))

'Disegna il testo 'New Font... inizializza un nuovo font, ossia Microsoft 'Sans Serif di dimensione 12, senza stili aggiuntivi G.DrawString(Me.Text, New Font("Microsoft Sans Serif", 12, FontStyle.Regular), Brushes.Black, Me.Location.X + 30, Me.Location.Y - 5) 181. End Sub 182. End Class

L'applic azione princ ipale


L'applicazione pr incipale contiene due componenti: un DataGr idView e una Pictur eBox . Per veder e come li ho impostati, guar date lo scr eenshot in fondo alla pagina. 001. Class Form1 002. Private Items As New List(Of GraphItem) 003. 004. Private Sub cmdDraw_Click(ByVal sender As Object, ByVal e As EventArgs) _ 005. Handles cmdDraw.Click 006. Dim Total As Single 007. 008. Items.Clear() 009. 010. 'Calcola il totale 011. For Each Row As DataGridViewRow In dgvValues.Rows 012. 'Controlla che il valore sia diverso da NULL 013. If Row.Cells Is Nothing Then 014. Continue For 015. End If 016. 'Quindi somma il valore della cella al totale 017. Total += Row.Cells(0).Value 018. Next 019. 020. 'Costruisce gli spicchi 'Valore di una riga 021. 022. Dim Value As Single 'Variabile ausiliare del ciclo: tiene traccia dell'angolo a cui 023. 'si arrivati 024. 025. Dim PrevAngle As Single = 0 026. 'Anche questa, come sopra: tiene traccia a quale altezza si 027. ' arrivati con la legenda Dim Y As Int32 = 20 028. 'Testo della riga 029. 030. Dim Text As String 'Pennello > colore 031. Dim Br As Brush 032. 'Una etichetta della legenda 033. Dim Lab As ColorLabel 034. 'Un pezzo della torta 035. Dim Piece As PiePiece 036. 037. For Each Row As DataGridViewRow In dgvValues.Rows 038. 'Controlla che i valori esistano e che la cella non 039. 'sia l'ultima (che sempre vuota) 040. If Row.Cells Is Nothing OrElse _ 041. Row.Index = dgvValues.RowCount - 1 Then 042. Continue For 043. End If 044. 045. Value = Row.Cells(0).Value 046. 'Costruisce il testo della legenda, formato da quello della 047. 048.

'riga, con la specificazione, tra parentesi, del valore 049. 'corrispondente e della percentuale 050. Text = String.Format("{0} ({1:N2} - {2:N2}%)", _ 051. Row.Cells(1).Value, Value, Value * 100 / Total) 052. 'Questo sempre per l'intelligenza di DataGridView, 053. Select Case Row.Cells(2).Value 054. Case "Rosso" 055. Br = Brushes.Red 056. Case "Arancio" 057. Br = Brushes.Orange 058. Case "Giallo" 059. Br = Brushes.Yellow 060. Case "Verde" 061. Br = Brushes.Green 062. Case "Azzurro" 063. Br = Brushes.LightBlue 064. Case "Indaco" 065. Br = Brushes.Blue 066. Case "Viola" 067. Br = Brushes.Violet 068. Case "Nero" 069. Br = Brushes.Black 070. End Select 071. 'Inizializza la nuova etichetta 072. Lab = New ColorLabel(Text) 073. Lab.ColorBrush = Br 074. Lab.Location = New Point(280, Y) 075. 076. 'E il nuovo pezzo di torta. Value * 360 / Totale 077. 'l'ampiezza dell'angolo, ottenuta con la proporzione: 078. 'Value : Total = x : 360 079. Piece = New PiePiece(New Point(150, 125), 100, _ 080. PrevAngle, Value * 360 / Total) 081. Piece.ColorBrush = Br 082. 083. 'Tiene traccia dell'angolo 084. PrevAngle += Value * 360 / Total 085. 'Si sposta pi in gi per la prossima etichetta 086. Y += 20 087. 'Aggiunge gli elementi 088. Items.Add(Lab) 089. Items.Add(Piece) 090. Next 091. 092. 'Crea una nuova immagine vuota 093. Dim Img As New Bitmap(imgPreview.Width, imgPreview.Height) 094. 'Prende l'oggetto Graphics associato a quell'immagine 095. Dim G As Graphics = Graphics.FromImage(Img) 096. 'Attiva l'anti-alias 097. G.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias 098. 'E disegna ogni elemento 099. For Each Item As GraphItem In Me.Items 100. Item.Draw(G) 101. Next 102. 'Ogni cosa disegnata mediante G verr trasferita 103. 'sull'immagine Img associata 104. G.Flush() 105. 106. imgPreview.Image = Img 107. End Sub 108. 109. End Class Ed ecco un esempio di come si pr esenter alla fine, tutta l'applicazione:

F6. Utilizzo avanzato della classe Graphics

Penne alternative
Nel capitolo pr ecedente abbiamo impiegato penne pr edefinite. Or a vogliamo cer car e qualcosa di pi accattivante. Dopo aver inizializzato un nuovo oggetto Pen, possiamo modificar ne le pr opr iet: Br ush : associando un pennello a questa pr opr iet possibile r iempir e il tr atto della penna mediante tale pennello con sfumatur e, disegni, immagini, ecceter a... CompoundAr r ay : ammettiamo che l'oggetto Pen abbia una lar ghezza abbondante, ad esempio 10. Tutto il tr atto viene r iempito unfir memente con un color e (a meno che non abbiate modificato la pr opr iet Br ush). Mediante questa pr opr iet possiamo decider e di r impiazzar e il blocco cr omatico con pi str isce color ate di lar gezza definita. Ecco un esempio: 1. 2. 3. 4. 5. 6. 7. 8. Dim b As New Bitmap(300, 300) Dim g As Graphics = Graphics.FromImage(b) Dim p As New Pen(Color.Black, 14) p.CompoundArray = New Single() {0, 0.2, 0.4, 0.8, 0.9, 1} g.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias g.Clear(Color.White) g.DrawLine(p, 10, 10, 80, 100) La penna lascia un tr atto di lar ghezza 14, ma esso non unifor ma. La pr opr iet CompoundAr r ay di tipo ar r ay di Single. In questo ar r ay vanno specificate delle posizioni per centuali, che indicano l'inter vallar si di str isce e spazi. Nel codice sopr a, ho specificato che da 0% a 20% della lar ghezza ci deve esser e una linea, dal 20% al 40% uno spazio, dal 40% all'80% una linea, dall'80% al 90% uno spazio e dal 90% al 100% una linea. Il tutto ha pr odotto un r isultato come quello in figur a. DashStyle : per mette di sceglier e un differ ente tipo di tr atteggio tr a alcuni pr edefiniti. Eccone un esempio:

L'ultimo stato cr eato modificando la pr opr iet DashPatter n: anch'essa un'ar r ay di Single, ma specifica la lunghezza in pix el di un tr atto e di uno spazio (in figur a er a 5, 5, 10, 5, ossia 5px di linea, 5 di spazio, 10 di linea e 10 di spazio). Si tr atta di pix el poich il tr atto si estende in lunghezza (quindi non si possono specificar e valor i r elativi) DashCap : deter mina la for ma degli estr emi dei punti o delle linee che costituiscono una linea tr atteggiata. Ecco un esempio, combinato con la pr opr iet CompoundAr r ay:

Star tCap / EndCap : specifica la for ma geometr ica posta all'inizio o alla fine della linea (di tutta la linea). Simile a DashCap, ma con molte pi var ianti.

Pennelli alternativi
Oltr e a SolidBr ush, esistono alcuni altr i pennelli con car atter istiche peculiar i. Ad esempio: HatchBr ush : r iempie una super ficie con una tr ama specificata. Qui ci sono tutte le var ianti; Linear Gr adientBr ush : esegue una sfumatur e sull'ar ea da r iempir e. Potete consultar e alcuni esempi nella sezione Appunti; Tex tur eBr ush : r iempie un'ar ea specificata con un'immagine, eventualmente r ipetendola e/o allungandola. Esem pio

Esempio 1: Creare una userbar


Per mostr ar vi qualche utilizzo pr atico e non pr opr io basilar e alla gr afica in .NET voglio mostr ar e come sia possibile disegnar e una user bar simile a quelle cr eate con Photoshop. Per cr ear e una user bar si seguono pi o meno sempr e questi passaggi: si pr ende uno sfondo di dimensioni 350x 19 (for mato standar d), oppur e si r iempie quest'ar ea con un gr adiente color ato e vi si applica sopr a un'altr a por zione di immagine; si applica sullo sfondo una tr ama omogenea for mata da r ighe diagonali molto sottili e r avvicinate di color e scur o; si cr ea un effetto lucido sovr apponendo all'immagine cos cr eata una semiellisse di color e bianco, con una tr aspar enza del 20-30%; per ultimar e il lavor o, si pone una scr itta sulla par te destr a della bar r a, di solito usando il font Visito r TT2 BRK. Noi automatizzer emo tutto questo cr eando una classe apposita: 001. Namespace Userbars 002. 003. Class Userbar 004. Private _BackgroundImage As Image 005. Private _BackgroundImagePosition As Int32 006. Private _Text As String 007. Private _Size As Size = New Size(350, 25) 008. 009. Public Property Size() As Size 010. Get 011. Return _Size 012. End Get 013. Set(ByVal value As Size) 014. If value.Width > 0 And value.Height > 0 Then 015. _Size = value 016. Else 017. _Size = New Size(350, 25) 018. End If 019. End Set 020. End Property 021. 022. Public Property BackgroundImage() As Image 023. Get 024. Return _BackgroundImage 025. End Get 026.

027. 028. 029. 030. 031. 032. 033. 034. 035. 036. 037. 038. 039. 040. 041. 042. 043. 044. 045. 046. 047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097.

Set(ByVal value As Image) If value.Width > Me.Size.Width Or value.Height > Me.Size.Height Then Throw New ArgumentOutOfRangeException() Else _BackgroundImage = value End If End Set End Property Public Property BackgroundImagePosition() As Int32 Get Return _BackgroundImagePosition End Get Set(ByVal value As Int32) If value > Me.Size.Width Then Throw New ArgumentOutOfRangeException() Else _BackgroundImagePosition = value End If End Set End Property Public Property Text() As String Get Return _Text End Get Set(ByVal value As String) _Text = value End Set End Property Public Function Create() As Image Dim Result As New Bitmap(Me.Size.Width + 1, Me.Size.Height + 1) Dim G As Graphics = Graphics.FromImage(Result) 'Questi valori sono statici poich costanti. Una 'volta inizializzati saranno sempre uguali e non 'verranno creati ulteriori oggetti ad ogni invocazione 'di Create() 'HatchBrush permette di riempire un'area con una trama 'prefissata. Noi vogliamo disegnare delle sottili righe 'scure, un motivo a cui corrisponde l'enumeratore 'indicato. 'Il colore usato un Nero con opacit pari 'a 48: dato che il valore massimo 255, si tratta di 'un nero al 19% di opacit. Il colore di sfondo 'invece trasparente (come se non ci fosse). Static LinesBrush As New HatchBrush(HatchStyle.DarkUpwardDiagonal, Color.FromArgb(48, Color.Black), Color.Transparent) 'Il font usato Visitor TT2 BRK, grandezza 11pt. '11 va molto bene per le userbar di dimensione 'standard. Se volete qualcosa di pi generale, 'il font deve dipendere dall'altezza Static FontUsed As New Font("Visitor TT2 BRK", 11, FontStyle.Regular) 'Questo pennello servir per l'effetto lucido. Dato 'che si tratta di un SolidBrush, riempir l'area con 'un colore omogeneo, in questo caso un bianco 'trasparente Static TranspBrush As New SolidBrush(Color.FromArgb(70, Color.White)) 'Imposta la modalit di smussamento delle linee. Dato 'che questa funzione viene usata solo quando l'utente 'la richiede (quindi non molte volte al secondo), e che 'il risultato deve essere il migliore possibile, 'utilizziamo un algoritmo ad alto rendimento e 'bassa velocit di rendering. G.SmoothingMode = SmoothingMode.HighQuality 'Imposta la modalit di sovrapposizione. Poich 'dobbiamo disegnare molte cose le una sopra alle altre, ci 'serve che i nuovi disegni non sovrascrivano quelli

098. 099. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120.

'precedenti, ma vi si applichino sopra rispettandone 'le trasparenze G.CompositingMode = CompositingMode.SourceOver 'Disegna l'immagine di sfondo G.DrawImage(Me.BackgroundImage, Me.BackgroundImagePosition, 0) 'Disegna le righe su tutta l'immagine G.FillRectangle(LinesBrush, 0, 0, Me.Size.Width, Me.Size.Height) 'Applica il velo di bianco trasparente. Notate che 'utilizzo delle coordinate negative per disegnare 'l'ellisse fuori dall'immagine. In questo modo, 'noi vedremo solo la parte di ellisse che rientra 'nell'area effettivamente esistente, ossia solo met. 'Inoltre ho messo qualche coefficiente per aggiustare 'la larghezza e rendere migliore l'aspetto G.FillEllipse(TranspBrush, -5, -Me.Size.Height + 3, Me.Size.Width + 10, CInt(Me.Size.Height * 1.5)) 'Disegna il contorno della barra G.DrawRectangle(Pens.Black, 0, 0, Me.Size.Width, Me.Size.Height) 'Calcola la dimensione del testo (in pixel) Dim TextSize As SizeF = G.MeasureString(Me.Text, FontUsed) 'Quindi disegna la stringa Text in bianco, spostata 'rispetto al margine destro in modo che il testo non 'vada fuori dall'immagine. G.DrawString(Me.Text, FontUsed, Brushes.White, Me.Size.Width CInt(TextSize.Width) - 30, Me.Size.Height \ 3)

121. 'Restituisce il risultato 122. Return Result 123. End Function 124. 125. End Class 126. 127. 128. End Namespace Ed ecco un esempio:

Come noter ete, la pr opr iet Backgr oundImagePosition ha senso solo se l'immagine di lar ghezza infer ior e alla user bar , ossia nel caso in cui lo sfondo debba esser e r iempito con un gr adiente unifor me (Linear Gr adientBr ush). Non ho implementato questa funzionalit nel codice, ma la lascio come eser cizio. Potete usar e come r ifer imento il mio ar ticolo al r iguar do nella sezione Appunti.

Esempio 2: Orologio analogic o


Ecco uno stupidissimo codice di esempio per disegnar e un or ogoloio: 01. Class Form1 02. Private ClockImage As New Bitmap(230, 230) 03. Private G As Graphics = Graphics.FromImage(ClockImage) 04. 05. Private Sub tmrClock_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles tmrClock.Tick 06. G.Clear(Me.BackColor) 07. 08. Dim Time As Date = Date.Now 09. 10. Static BorderPen As New Pen(Color.Black, 5) 11. Static HourPen As New Pen(Color.Red, 4) With {.EndCap = LineCap.Triangle} 12. Static MinutePen As New Pen(Color.Blue, 2) With {.EndCap = LineCap.Triangle} 13. Static SecondPen As New Pen(Color.Green, 1) 14. 15. Dim C As Point = New Point(ClockImage.Width / 2, ClockImage.Height / 2) 16. Dim h, m, s As Single 17. 18.

19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30.

h = Time.Hour + (Time.Minute / 60) m = Time.Minute + (Time.Second / 60) s = Time.Second G.SmoothingMode = SmoothingMode.AntiAlias G.FillEllipse(Brushes.White, 5, 5, ClockImage.Width - 10, ClockImage.Height - 10) G.DrawEllipse(BorderPen, 5, 5, ClockImage.Width - 10, ClockImage.Height - 10) For I As Int32 = 0 To 11 G.FillEllipse(Brushes.Black, _ C.X + CInt(ClockImage.Width / 2.3 * Math.Cos(Math.PI / 2 - I / 6 * Math.PI)) 1, _ C.Y - CInt(ClockImage.Width / 2.3 * Math.Sin(Math.PI / 2 - I / 6 * Math.PI)) 1, _ 2, 2) Next

31. 32. 33. G.DrawLine(HourPen, C.X, C.Y, _ 34. C.X + CInt(ClockImage.Width / 4 * Math.Cos(Math.PI / 2 - h / 6 * Math.PI)), _ 35. 36. C.Y - CInt(ClockImage.Width / 4 * Math.Sin(Math.PI / 2 - h / 6 * Math.PI))) 37. G.DrawLine(MinutePen, C.X, C.Y, _ 38. 39. C.X + CInt(ClockImage.Width / 3 * Math.Cos(Math.PI / 2 - m / 30 * Math.PI)), _ C.Y - CInt(ClockImage.Width / 3 * Math.Sin(Math.PI / 2 - m / 30 * Math.PI))) 40. 41. G.DrawLine(SecondPen, C.X, C.Y, _ 42. C.X + CInt(ClockImage.Width / 2.2 * Math.Cos(Math.PI / 2 - s / 30 * Math.PI)), _ 43. C.Y - CInt(ClockImage.Width / 2.2 * Math.Sin(Math.PI / 2 - s / 30 * Math.PI))) 44. 45. imgClock.Image = ClockImage 46. End Sub 47. 48. End Class

Ulteriori esempi
Nella sezione Dow nload ci sono numer osi pr ogr ammi che utilizzando Gr aphics in modo intensivo. Tr a questi: Cur ve Ar t, Totem Char ting, TWave Editor , Wave, File Compar er , Data View er , MGr aphing, ecceter a... E vi r icor do che sono tutti open sour ce, quindi potete studiar ne il codice liber amente.

F7. Usare la stampante

C' un motivo per cui ho posizionato pr opr io qui questo capitolo, che in appar enza avr ebbe dovuto tr ovar si nella sezione B: per stampar e bisogna usar e... la gr afica! E gi, bisogna disegnar si tutto da s. Il contr ollo Pr intDialog ser ve soltanto a sceglier e le impostazioni adatte e la stampante giusta, ma il r esto viene fatto per mezzo di un altr o oggetto Pr intDocument, che espone il metodo Pr int. Non deve ingannar e questo nome, poich d solamente inizio al pr ocesso, ma ogni oper azione deve esser e svolta dal pr ogr ammator e. Infatti, una volta avviato, viene lanciato l'evento Pr intPage, gener ato ogniqualvolta ci sono una o pi pagine da stampar e. All'inter o del sottoscr ittor e di questo evento va scr itto il codice. Ad esempio, si pr enda l'esempio del capitolo F5, aggiungendo poi un pulsante cmdPr int (Tex t = "Stampa"). Ecco il codice: 01. 'Ricordarsi di importare questo namespace 02. Imports System.Drawing.Printing 03. Class Form1 '... 04. 05. 06. 07. Private Sub cmdPrint_Click(ByVal sender As Object, _ ByVal e As EventArgs) Handles cmdPrint.Click 08. 'Una nuova finestra di dialogo per la stampante 09. Dim PrintDialog As New PrintDialog 10. 'Il nuovo oggetto che fa da mediatore tra l'utente e 11. 'la stampante 12. 13. Dim PrintDoc As New PrintDocument 14. 15. With PrintDialog 'Determina se sia possibile stampare su un file 16. 17. .AllowPrintToFile = False 18. 'Determina se sia possibile stampare solo la 'selezione. In questo caso non c' nessuna selezione 19. 'quindi non ci sono problemi a disativare 20. 'l'impostazione 21. .AllowSelection = False 22. 'Determina se sia possibile stampare delle pagine in 23. 'particolare. Vale lo stesso discorso fatto sopra 24. .AllowSomePages = False 25. 'Assegna l'oggetto PrintDoc a Document, cos da 26. 'collegare le impostazioni selezionate al documento 27. .Document = PrintDoc 28. End With 29. 30. If PrintDialog.ShowDialog = Windows.Forms.DialogResult.OK Then 31. 'Copia le impostazioni di stampa nel documento 32. PrintDoc.PrinterSettings = PrintDialog.PrinterSettings 33. 'Quindi aggiunge l'handler d'evento per Printpage 34. AddHandler PrintDoc.PrintPage, AddressOf PrintDocument_PrintPage 35. 'Il nome del documento visualizzato sulla finestra 36. 'di stampa 37. PrintDoc.DocumentName = "Grafico a torta" 38. 'Fa partire il processo di stampa 39. PrintDoc.Print() 40. End If 41. End Sub 42. 43. Private Sub PrintDocument_PrintPage(ByVal sender As Object, _ 44. ByVal e As PrintPageEventArgs) 45. 'Bisogna considerare anche i margini della pagina, perci 46. 'sposta l'origine pi in basso, come specificato 47. 'dai margini superiore e sinistro selezionati in PrintDialog 48. e.Graphics.RenderingOrigin = New Point(e.MarginBounds.Left, _ 49. e.MarginBounds.Top) 50. 51. 52.

'In questo caso le operazioni sono molto semplici: basta 53. '"disegnare" sulla stampante (ossia sullo stream che 54. 'permette l'interazione con essa) gli stessi elementi 55. 'presenti nella lista Items 56. For Each Item As GraphItem In Me.Items 57. Item.Draw(e.Graphics) 58. Next 59. 60. 'Sicuramente ci sta tutto in una pagina, quindi specifica 61. 'che non ci sono pi pagine da stampare. 62. e.HasMorePages = False 63. End Sub 64. 65. End Class

Or a spostiamoci su qualcosa di meno semplice. Bisogna stampar e un file di testo. Ecco un esempio del codice di stampa: 01. Class Form1 02. Private Reader As IO.StreamReader 03. Private Sub cmdPrintFile_Click(ByVal sender As Object, _ 04. 05. ByVal e As EventArgs) Handles cmdPrintFile.Click Dim PrintDialog As New PrintDialog 06. Dim Open As New OpenFileDialog 07. Dim PrintDoc As New PrintDocument 08. 09. With PrintDialog 10. .AllowPrintToFile = False 11. .AllowSelection = False 12. .AllowSomePages = False 13. .Document = PrintDoc 14. End With 15. 16. Open.Filter = "File di testo|*.txt" 17. 18. If Open.ShowDialog = Windows.Forms.DialogResult.OK Then 19. Reader = New IO.StreamReader(Open.FileName) 20. Else 21. Exit Sub 22. 23. End If 24. 25. If PrintDialog.ShowDialog = Windows.Forms.DialogResult.OK Then 26. PrintDoc.PrinterSettings = PrintDialog.PrinterSettings 27. AddHandler PrintDoc.PrintPage, AddressOf PrintFile_PrintPage 28. PrintDoc.DocumentName = IO.Path.GetFileName(Open.FileName) 29. PrintDoc.Print() 30. End If 31. 32. End Sub 33. 34. Private Sub PrintFile_PrintPage(ByVal sender As Object, _ 35. ByVal e As PrintPageEventArgs) 36. 'Imposta l'unit di misura per le misurazioni 37. 'successive 38. e.Graphics.PageUnit = GraphicsUnit.Pixel 39. 'Il font per il testo da stampare: Times New Romand 12pt 40. Static Font As New Font("Times New Roman", 12) 41. 'Per sapere quante righe ci possono stare nella pagine, 42. 'bisogna misurare l'altezza dei caratteri 43. Static CharHeight As Single = Font.GetHeight(e.Graphics) 44. 'Calcola le linee di testo che possono stare in una pagina 45. Static TotalLines As Int16 = e.MarginBounds.Height / CharHeight 46. 47. 'La linea a cui si arrivati a leggere 48. Dim LineIndex As Int16 = 1 49. 'Il testo della riga 50. Dim Line As String 51. 52. 'Tiene conto della posizione attuale 53. 54.

Dim Y As Int16 = e.MarginBounds.Top 55. 56. e.Graphics.RenderingOrigin = New Point(e.MarginBounds.Left, _ 57. e.MarginBounds.Top) 58. 59. Do 60. 'Legge la riga di testo dal file 61. Line = Reader.ReadLine 62. 63. 'Si suppone che la larghezza della stringa sia minore 64. 'di quella della pagina 65. e.Graphics.DrawString(Line, Font, Brushes.Black, _ 66. e.MarginBounds.Left, Y) 67. Y += CharHeight 68. LineIndex += 1 69. Loop While (LineIndex < TotalLines) And Not (Reader.EndOfStream) 70. 71. 'Se il file alla fine, non ci sono pi pagine 72. 'da stampare, altrimenti continua 73. e.HasMorePages = Not Reader.EndOfStream 74. If Reader.EndOfStream Then 75. Reader.Close() 76. End If 77. End Sub 78. 79. End Class In conclusione, si tr atta di far e pr atica, poich la teor ia molto semplice.

Ec c ezioni alla regola


pur ver o che per stampar e dati che abbiamo cr eato noi, nel nostr o pr ogr amma, necessar io usar e un codice simile a quelli sopr a r ipor tati, tuttavia esiste una scappatoia a questa fatica. Se il nostr o obiettivo consiste solamente nello stampar e un file per cui esista un pr ogr amma che ne gestisca la stampa, allor a basta eseguir e queste poche r ighe di codice: 1. 2. 3. 4. Dim P As New Process P.StartInfo.FileName = "file da stampare" P.StartInfo.Verb = "Print" P.Start()

Il pr ocesso avviato gestir la stampa del file mediante un pr ogr amma ester no. Come gi detto, per , necessar io che esista un pr ogr amma del gener e installato sulla macchina dell'utente. Per contr ollar e la possibilit di eseguir e questo codice, bisogna ver ificar e l'esistenza della chiave HKEY_CLASSES_ROOT\[ex tkey]\shell\pr int\command, dove [ex tkey] il valor e pr edefinito della chiave il cui nome cor r isponde all'estensione del file da stampar e. Ad esempio, vogliamo stampar e il file "r eadme.tx t". Essendo un file di testo, cer chiamo nel r egistr o di sistema la chiave HKEY_CLASSES_ROOT\.tx t. Il suo valor e (Pr edefinito) "tx tfile". Rechiamoci allor a alla chiave HKEY_CLASSES_ROOT\tx tfile: ver ifichiamo che esista la sottochiave shell\pr int\command. Esiste! Quindi siamo a posto. Si pu r ipeter e lo stesso pr ocedimento per tutti i tipi di file. Per saper e come ispezionar e il r egistr o di sistema da codice, veder e capitolo r elativo.

F8. Manipolazione di file XML

XML un acr onimo per eXtensibale Mar kup Language (Linguaggio di Contr assegno Estensibile). Si tr atta di un linguaggio per mezzo del quale possibile immagazzinar e dati in una str uttur a for temente ger ar chica e or ganizzata, un modello ideale che r ispecchia appieno il meccanismo della pr ogr ammazione or ientata agli oggetti. L'XML oggi usatissimo in un gr an numer o di casi, e la sua gr ande diffusione si deve attr ibuir e alla sua flessibilit. Non so se "i miei venticinque lettor i" conoscano l'HTML, ma pr obabile di s. Nell'HTML ci sono tag e pr opr iet definiti: il w eb master pu usar e solamente quelli. Al contr ar io, in XML il pr ogr ammator e che definisce i tag e gli attr ibuti, ossia la g r am m atica con cui il documento viene scr itto. Pr ima di iniziar e, segue una br eve intr oduzione all'XML.

Introduzione all'X ML
Un documento XML un file di testo contenente dati incasellati in una str uttur a ger ar chico-logica for temente definita. Ogni par te di questa str uttur a viene detta elem ento (o nodo in analogia con la for mazione "ad alber o" gi descr itta con il contr ollo Tr eeView ). Ogni elemento pu posseder e infor mazioni ulter ior i che ne specificano le pr opr iet peculiar i: tali infor mazioni sono specificate sotto for ma di attr ibuti. L'elemento pr incipale, di or dine super ior e a tutti gli altr i, si chiama elem ento r o o t o semplicemente r o o t. L'unica entit in gr ado di star e allo stesso livello del r oot la dichiar azione della ver sione x ml o altr e dir ettive di inter pr etazione. Ecco un esempio di semplice file: 01. 02. 03. 04. 05. 06. 07. 08. 09. 10. 11. 12. 13. <?xml version="1.0" ?> <biblioteca> <libro titolo="I sei numeri dell'universo" autore="Martin Rees"> <capitolo titolo="Il cosmo e il microcosmo" numero="1" pagina="11"> Alla base della struttura del nostro universo - non solo ... </capitolo> </libro> <libro titolo="Le ostinazioni di un matematico" autore="Didier Nordon"> <capitolo titolo="Dalle stalle alle stelle" numero="1" pagina="11"> Ecco a voi un romanzo il cui protagonista conosce una morte ... </capitolo> </libro> </biblioteca>

Questo codice sintetizza i pr imi due libr i che mi sono capitati in mano: ne specifica alcuni dettagli e r ipor ta un pezzo della pr ima fr ase del pr imo capitolo. Par tendo da questo sor gente, biblioteca, libr o e capitolo sono elementi, mentr e titolo, autor e, numer o e pagina sono attr ibuti di questi elementi. Biblioteca il r oot. Dopo aver letto e individuato le var ie par ti del documento, bisogna far e alcune osser vazioni: Tutti i valor i, qualsiasi sia il lor o tipo, vanno r acchiusi tr a apici singoli o doppi Ogni elemento aper to deve esser e chiuso. Se un elemento non ha co ntenuto si pu usar e la sintassi abbr eviata 1. <elemento attributo="valore" ... /> Tutto il testo analizzato dai par ser in modalit case-sensitive Ogni identificator e di elemento o attr ibuto deve esser e un nome valido. Per questo ver so segue le stesse r egole per la cr eazione di un nome di var iabile VB, ad eccezione della possibilit di usar e il tr attino Il fatto che ci sia piena liber t nella cr eazione dei nomi e della pr opr ia gr ammatica non deve far consider ar e l'XML come un linguaggio poco r igor oso. Ci sono, infatti, degli speciali tipi di file, detti Schema, che sono in gr ado di definir e con cor r ettezza e pr ecisione tutta la gr ammatica di un dato tipo di documenti: possono indicar e, ad esempio, quali tag

possono esser e nidificati in quali altr i; quali valor i possa assumer e un dato attr ibuto; quanti elementi sia possibile definir e per un cer to tag padr e; ecceter a... Queste r egole sono talmente potenti che esistono dei par ser , ossia inter pr etator i di codice, che possono for nir e gli stessi sugger imenti che for nisce l'Intellisense del .Net per un dato Schema, oltr e al fatto di poter convalidar e un documento contr ollando che vengano r ispettati i pr incipi definiti. Dato che questo non un cor so di XML, con l'intr oduzione mi fer mo qui, ma siete liber i di appr ofondir e l'ar gomento in altr a sede, ad esempio qui.

Uso di X ML in ambiente .NET


Descr iver e dettagliatamente tutte le classi atte alla manipolazione dei documenti XML sar ebbe un enor me spr eco di tempo, e sicur amente non aiuter ebbe poi molto: inoltr e una documentazione esaur iente e dettagliata esiste gi all'inter no della libr er ia MSDN di Micr osoft. In questo par agr afo elencher br evemente tali classi con una piccola descr izione: System.Xml.XmlAttr ibute : r appr esenta un attr ibuto System.Xml.XmlCDataSection : r appr esenta una sezione CData. Questo tipo di elemento un contenitor e di testo esteso, al cui inter no si possono dichiar ar e anche pezzi di codice XML: in questo modo essi non si confondono con il r esto del documento System.Xml.XmlComment : r appr esenta un commento System.Xml.XmlDocument : r appr esenta un inter o documento XML ed espone metodi per il salvataggio e il car icamento veloce System.Xml.XmlElement : r appr esenta un singolo elemento System.Xml.XmlNode : r appr senta un nodo. Da questa classe der ivano molte altr e System.Xml.XmlReader : classe astr atta di base per XmlTex tReader e XmlValidatigReader , che consentono r ispettivamente la lettur a di testo x ml o di uno schema x ml System.Xml.XmlWr iter : classe astr atta di base per XmlTex tWr iter , che consente la scr ittur a di testo x ml sul documento Di quelli elencati, che gi sono una r istr etta minor anza r ispetto a quelli effettivamente pr esenti, noi user emo solo XmlTex tReader e XmlTex tWr iter . Iniziamo con XmlTex tRaeder . Questa classe espone una quantit gigantesca di membr i, che sar ebbe tr oppo dispendioso elencar e completamente. Il suo funzionamento non semplicissimo da capir e a un pr imo impatto, ma un poco di r agionamento lo r ender pi chiar o. La funzione pr incipale Read, che legge un nodo e r estituisce False se non c' niente da legger e. Una volta letto, le sue infor mazioni diventano disponibili attr aver so le pr opr iet di XmlTex tReader , che funge da totum continens: infatti gli attr ibuti e i contenuti vengono letti tutti nello stesso modo, consider ati, quindi, tutti nodi. Ecco un esempio che pr ende un file XML, lo analizza e lo r estituisce sotto for ma di file INI (anche se questo non suppor ta la ger ar chia): 01. Imports System.Xml 02. Module Module1 03. Sub Main() 04. 'Crea un nuovo lettore xml. 05. Dim Reader As New Xml.XmlTextReader("C:\libri.xml") 06. Dim Indent, Content As String 07. 08. 'La funzione Reader.Read legge il nodo successivo e 09. 'restituisce False se non c' niente altro da 10. 'leggere. Il ciclo legge tutti gli elementi 11. Do While Reader.Read 12. 'Indenta il codice a seconda della profondit 13. 'del nodo 14. Indent = New String(" ", Reader.Depth * 2) 15. 'Se il nodo un tag di chiusura, come ad 16.

'esempio </libro>, lo salta 17. If Not Reader.IsStartElement Then 18. 'Viene considerato un nodo anche il testo all'interno 19. 'di un elemento, perci bisogna controllare 'se questo non sia effettivamente un testo 20. 21. If Reader.HasValue Then 22. Console.WriteLine(Reader.Value) 23. End If 24. Continue Do 25. End If 26. 27. 'Scrive il nome del tag a schermo, tra parentesi quadre, 28. 'come nei file INI 29. Console.WriteLine("{0}[{1}]", Indent, Reader.Name) 30. 'Se l'elemento ha un contenuto, lo memorizza per scriverlo 31. 'successivamente a schermo 32. If Reader.HasValue AndAlso Reader.Value <> vbCrLf Then 33. Content = Reader.Value 34. Else 35. Content = Nothing 36. End If 37. 'Se l'elemento possiede attributi, li scrive 38. If Reader.HasAttributes Then For I As Int16 = 0 To Reader.AttributeCount - 1 39. 40. Reader.MoveToAttribute(I) 41. Console.WriteLine("{0}{1} = {2}", Indent, _ Reader.Name, Reader.Value) 42. Next 43. End If 44. 45. If Content IsNot Nothing Then 46. Console.WriteLine("{0}Contenuto = {1}", Indent, Content) 47. End If 48. Loop 49. Reader.Close() 50. 51. Console.ReadKey() 52. End Sub 53. 54. End Module Il r isultato sar questo: 01. [biblioteca] 02. [libro] 03. titolo = I sei numeri dell'universo 04. autore = Martin Rees [capitolo] 05. 06. titolo = Il cosmo e il microcosmo 07. numero = 1 08. pagina = 11 09. 10. Alla base della struttura del nostro universo - non solo ... 11. 12. [libro] 13. titolo = Le ostinazioni di un matematico 14. autore = Didier Nordon 15. [capitolo] 16. titolo = Dalle stalle alle stelle 17. numero = 1 pagina = 11 18. 19. Ecco a voi un romanzo il cui protagonista conosce una morte ... 20.

Passiamo or a a XmlTex tWr iter : i suoi membr i sono molto meno numer osi ed usar lo assai semplice. Quasi tutti i metodi iniziano per "Wr ite" e ser vono a scr iver e diver si tipi di dati, elementi, attr ibuti e specifiche del documento. La cosa impor tante da r icor dar si quando si lavor a con XmlTex tWr iter di r ichiamar e sempr e, pr ima di ogni metodo, Wr iteStar tDocument, che si occupa di inizializzar e il documento cor r ettamente con le dir ettive adatte; e dopo aver ter minato le var ie oper azioni Wr iteEndDocument, che chiude tutto. Ecco un esempio:

01. 02. 03. 04. 05. 06. 07. 08. 09. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37.

Imports System.Text Imports System.Xml Module Module2 Sub Main() 'Il secondo parametro del costruttore obbligatorio 'e specifica quale codifica di caratteri si debba usare. 'In questo caso ho messo UTF-8, in modo da poter usare anche 'i caratteri accentati Dim Writer As New XmlTextWriter("C:\guida.xml", UTF8Encoding.UTF8) With Writer .Indentation = 2 .IndentChar = " " 'Scrive l'intestazione .WriteStartDocument() 'Scrive l'elemento root .WriteStartElement("guida") 'E l'attributo "capitoli" .WriteAttributeString("capitoli", "100") 'Scrive un elemento capitolo .WriteStartElement("capitolo") .WriteAttributeString("numero", "1") .WriteAttributeString("titolo", "Introduzione") .WriteString("A differenza del Visual Basic classico, ...") .WriteEndElement() 'Chiude il root e il documento .WriteEndElement() .WriteEndDocument() .Close() End With Console.ReadKey() End Sub End Module

F9. Serializzazione di oggetti

La ser ializzazione consiste nel salvar e un oggetto su un qualsiasi suppor to compatibile (file, flussi di memor ia, var iabili, str eam, ecceter a...) per poi poter lo r icar icar e in ogni momento: questo pr ocesso cr ea di fatto una copia per fetta dell'oggetto di par tenza. Il fr amew or k .NET in gr ado di ser ializzar e tutti i tipi base, compr esi anche gli ar r ay di tali tipi: poich tutte le str uttur e e le classi utilizzano i tipi base, pr aticamente ogni oggetto pu esser e sottoposto senza pr oblemi a un pr ocesso di ser ializzazione di default, anche se in cer ti casi sor gono pr oblemi che, giustamente, spetta al pr ogr ammator e r isolver e. Esistono tr e possibili tipi di ser ializzazione, ognuno associato a un deter minato fo r m atter , ossia un oggetto capace di tr asfer ir e i dati sul suppor to: Binar y : i dati vengono salvati in for mato binar io, conser vando solamente i bit effettivi di infor mazione. A causa della sua natur a, i valor i pr ocessati con la ser ializzazione binar ia sono pi compatti e l'oper azione assai veloce, tuttavia essi non sono leggibili n dall'utente n dal pr ogr ammator e. Questo non un gr ave difetto, poich pr aticamente sempr e non si deve inter venir e sui suppor ti di memor izzazione: basta che funzionino cor r ettamente SOA P : i dati vengono salvati in un for mato intellegibile, ossia inter pr etabili dall'uomo. In questo caso, tale for mato si identifica con l'XML, per mezzo del quale le infor mazioni vengono per sistite seguendo le dir ettive del Simple Object Access Pr otocol. Questo tipo di ser ializzazione r ichiede pi memor ia e un tempo di elbor azione maggior e, ma pu esser e compr esa dall'uomo. Ad esempio pu r isultar e utile nell'inviar e dati ad applicazioni par ser che li visualizzano in schemi or dinati. Ad ogni modo, la ser ializzazione SOAP mar chiata come obsoleta anche nel Fr amew or k 2.0, a favor e della pi r apida e meno dispendiosa Binar y XM L : simile a quella SOAP, tr anne per il fatto che viene utilizzato l'oggetto XmlSer ializer come for matter e che gli attr ibuti che influenzano la ser ializzazione nor male non vengono inter pr etati con l'uso di questa tecnica. Ci sono molti altr i piccoli par ticolar i che li differ enziano, ma li si vedr nei pr ossimi esempi

Serializzare oggetti
Le classi necessar ie alla ser ializzazione si tr ovano nei namespace System.Runtime.Ser ialization e System.Xml.Ser ialization. Le classi da usar e sono Binar yFor matter e SoapFor matter , definite nei r ispettivi namespace Binar y e Soap. Ecco un esempio della pr ima: 001. Imports System.Runtime.Serialization.Formatters 002. Module Module1 003. <Serializable()> _ 004. Class Person 005. Implements IComparable 006. Protected _FirstName, _LastName As String 007. Private ReadOnly _BirthDay As Date 008. 009. Public Property FirstName() As String 010. Get 011. Return _FirstName 012. End Get 013. Set(ByVal Value As String) 014. If Value <> "" Then 015. _FirstName = Value 016. End If 017. End Set 018. End Property 019. 020. Public Overridable Property LastName() As String 021.

022. 023. 024. 025. 026. 027. 028. 029. 030. 031. 032. 033. 034. 035. 036. 037. 038. 039. 040. 041. 042. 043. 044. 045. 046. 047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093.

Return _LastName End Get Set(ByVal Value As String) If Value <> "" Then _LastName = Value End If End Set End Property Public ReadOnly Property BirthDay() As Date Get Return _BirthDay End Get End Property Public Overridable ReadOnly Property CompleteName() As String Get Return _FirstName & " " & _LastName End Get End Property Public Overloads Overrides Function ToString() As String Return MyBase.ToString End Function Public Overloads Function ToString(ByVal FormatString As String) _ As String Dim Temp As String = FormatString Temp = Temp.Replace("{F}", _FirstName) Temp = Temp.Replace("{L}", _LastName) Return Temp End Function Public Function CompareTo(ByVal obj As Object) As Integer _ Implements IComparable.CompareTo 'Un oggetto non-nothing (questo) sempre 'maggiore di un oggetto Nothing (ossia obj) If obj Is Nothing Then Return 1 End If Dim P As Person = DirectCast(obj, Person) Return String.Compare(Me.CompleteName, P.CompleteName) End Function Sub New(ByVal FirstName As String, ByVal LastName As String, _ ByVal BirthDay As Date) Me.FirstName = FirstName Me.LastName = LastName Me._BirthDay = BirthDay End Sub End Class Sub Main() 'Crea un nuovo oggetto Person da serializzare Dim P As New Person("Pinco", "Pallino", New Date(1990, 6, 1)) 'Crea un nuovo Formatter binario Dim Formatter As New Binary.BinaryFormatter() 'Crea un nuovo file su cui salvare l'oggetto Dim File As New IO.FileStream("C:\person.dat", IO.FileMode.Create) 'Serializza l'oggetto 'Attenzione! I Formatter definiti in 'System.Runtime.Serilization, 'a differenza di quello in System.Xml.Serialization, 'richiedono esplicitamente che un oggetto sia 'dichiarato serializzabile, anche se questo lo 'logicamente. Per questo, bisogna recuperare la vecchia 'classe Person e applicarvi l'attributo Serializable. 'Per rinfrescarvi la memoria, ve ne ho scritto

Get

'una copia sopra 094. Formatter.Serialize(File, P) 095. 'E chiude il file 096. File.Close() 097. Console.WriteLine("Salvataggio completato!") 098. 099. 'Crea una nuova variabile di tipo Person per contenere 100. 'i dati caricati dal file 101. Dim F As Person 102. 'Apre lo stesso file di prima, ma in lettura 103. Dim Data As New IO.FileStream("C:\person.dat", IO.FileMode.Open) 104. 105. 'Carica le informazioi salvate nel file 106. F = Formatter.Deserialize(Data) 107. 108. 'Verifica che F e P siano perfettamente uguali 109. Console.Write("F = P -> ") 110. Console.Write(F.CompareTo(P)) 111. '> Ricordate che CompareTo restituisce 0 nel caso di uguaglianza 112. 113. Console.ReadKey() 114. End Sub 115. End Module Se si pr ova ad apr ir e il file per son.dat, si tr ovano molti car atter i incompr ensibili inter vallati da altr i nomi leggibili, tr a i quali si possono legger e il nome completo dell'assembly e i nomi dei campi ser ializzati. Infatti, quando si ser ializza in binar io, vengono salvati anche tutti i r ifer imenti agli assembly a cui il tipo dell'oggetto salvato appar tiene, insieme coi nomi dei campi e i lor o valor i binar i. Non posso mostr ar e un esempio della ser ializzazione Soap, pur tr oppo, per ch nel momento in cui scr ivo ho ancor a il fr amew or k 2.0, nel quale il namespace r elativo non esiste. Posso invece mostr ar e tale esempio con l'XmlFor matter su una lista di oggetti: 01. Public Module Module1 02. '... 03. Sub Main() 04. 'Crea nuovi oggetti Person da serializzare 05. Dim P1 As New Person("Pinco", "Pallino", New Date(1990, 6, 1)) 06. Dim P2 As New Person("Tizio", "Caio", New Date(1967, 4, 13)) 07. Dim P3 As New Person("Mario", "Rossi", New Date(1954, 8, 12)) 08. 'Ho creato un array perch pi veloce, ma nulla 09. 'vieta di usare liste generics o qualsiasi altro tipo 10. 'di collezione 11. Dim Persons() As Person = {P1, P2, P3} 12. 'Crea un nuovo Formatter Xml. Il serializzatore in questo 13. 'caso ha bisogno anche dell'oggetto Type relativo 14. 'all'oggetto da serializzare (un array di person). 15. Dim Formatter As New Serialization.XmlSerializer(GetType(Person())) 16. 'Crea un nuovo file su cui salvare l'oggetto 17. Dim File As New IO.FileStream("C:\persons.dat", IO.FileMode.Create) 18. 19. 'Serializza l'oggetto 20. 'Attenzione! Se gli XmlSerializer non hanno bisogno che 21. 'l'oggetto in questione possegga l'attributo Serializable, 22. 'hanno invece bisogno che questo esponga almeno un 23. 'costruttore senza parametri. Quindi bisogna aggiungere 24. 'un nuovo New() a Person. Inoltre, altra limitazione 25. 'importante, con questo formatter possibile 'serializzare solo tipi pubblici. 26. Formatter.Serialize(File, Persons) 27. 28. 'E chiude il file File.Close() 29. Console.WriteLine("Salvataggio completato!") 30. 'Potrete constatare che il salvataggio impiega un 31. 'tempo notevolmente maggiore 32. 33. 'Crea una nuova variabile di tipo Person per contenere 34. 'i dati caricati dal file 35. Dim F As Person() 36. 'Apre lo stesso file di prima, ma in lettura 37. 38.

Dim Data As New IO.FileStream("C:\persons.dat", IO.FileMode.Open) 39. 40. 'Carica le informazioi salvate nel file 41. F = Formatter.Deserialize(Data) 42. 43. 'Verifica che F e P siano perfettamente uguali 44. For I As Byte = 0 To 2 45. Console.WriteLine("P{0} = F{0} -> {1}", I, _ 46. Persons(I).CompareTo(F(I))) 47. Next 48. '> Ricordate che CompareTo restituisce 0 nel caso di uguaglianza 49. 50. Console.ReadKey() 51. End Sub 52. End Module Se si pr ova ad apr ir e il file per sons.dat, si tr ova un nor malissimo file x ml: 01. <?xml version="1.0"?> 02. <ArrayOfPerson xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 03. xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <Person> 04. <FirstName>Pinco</FirstName> 05. 06. <LastName>Pallino</LastName> 07. </Person> <Person> 08. <FirstName>Tizio</FirstName> 09. <LastName>Caio</LastName> 10. </Person> 11. 12. <Person> 13. <FirstName>Mario</FirstName> <LastName>Rossi</LastName> 14. </Person> 15. 16. </ArrayOfPerson>

Problemi legati alla serializzazione


Potr ebbe capitar e di aver e dei r ifer imenti cir colar i all'inter no dei campi di un oggetto, ad esempio un pr incipale e un dipendente che si puntano vicendevolmente. In questi casi la ser ializzazione Xml fallisce miser amente e questo costituisce un'altr a delle gr avi pecche che essa por ta con s: se si tenta l'oper azione, viene lanciata un'eccezione con il messaggio "Individuato r ifer imento cir colar e", e tutto il meccanismo cade r ovinosamente. Al contr ar io, il binar y for matter r iesce a individuar e casi del gener e e si limita a ser ializzar e l'oggetto che causa il r ifer imento cir colar e una sola volta, ar ginando tutti i possibili pr oblemi. Quando ci si tr ova in situazioni di questo tipo, o anche quando si hanno dei r ifer imenti r icor sivi, la str uttur a che si for ma a par tir e da un oggetto si dice g r afo: si pu dir e che tutti i r ifer imenti "ger moglino" dall'unico oggetto, detto appunto "r adice". Pr opr io per la capacit di individuar e e r isolver e pr oblematiche di questo tipo, il binar y for matter costituisce la soluzione miglior e alla clonazione Deep di oggetti. Si er a infatti par lato, nel capitolo sull'inter faccia ICloneable, di come il metodo Member w iseClone si limiti solo a una copia super ficiale, clonando esclusivamente i campi non r efer ence dell'istanza (clonazione Shallo w ). La clonazione deep, invece, r icostr uisce tutto il gr afo dell'istanza. Un altr o inconveniente legato alla ser ializzazione costituito dagli eventi. Come spiegato tempo fa, essi non sono altr o che delegate, i quali a lor o volta sono pur sempr e tipi der ivati da System.Delegate e, in quanto tipi, sono ser ializzabili. Nulla di male fin qui, ma quella che ho pr ima definito come "astuzia" del CLR nel r ipr odur r e gr afi cor r etti, si tr asfor ma or a in un incr edibile spaur acchio. Mi spiego meglio. Dur ante il pr ocesso di salvataggio, il for matter cattur a tutti gli oggetti r aggiungibili dir ettamente o indir ettamente attr aver so le pr opr iet e i campi di una classe. Allo stesso modo, in un delegate sono r aggiungibili anche tutti gli oggetti sottoscr ittor i, ossia quelli che hanno r egistr ato alcuni dei pr opr i metodi come gestor i d'evento dell'oggetto r adice. Questo pr oduce due gr avi effetti collater ali: vegono ser ializzati anche tutti gli altr i oggetti coinvolti e, con lor o, pr aticamente tutta l'applicazione, il che, oltr e a esser e un enor me dispendio di spazio, non il r isultato desider ato; se anche uno solo di tutti gli oggetti nel gr afo non

ser ializzabile, il pr ocesso fallisce lanciando un'eccezione. Par tendo dal pr esupposto che i For m non sono ser ializzabili, nel 99% dei casi, si otter r ebbe lo stesso er r or e. L'unico modo per r idur r e i danni comunicar e al for matter di non ser ializzar e gli eventi, attr aver so l'attr ibuto NonSer ialized (che vedr emo fr a br eve): questo implica definir e l'evento come custom. Poich sono utilizzati r ar amente, non ho tr attato gli eventi custom, ma ecco un esempio: 01. Module Module1 02. Public Class EventLauncher 03. Private _ID As Int32 'Negli eventi custom, bisogna usare una variabile privata 04. 'dello stesso tipo dell'evento da lanciare. Si pu 05. 'vedere un elemento custom un p come una propriet, 06. 07. 'che media l'interazione con il vero delegate gestore 08. Private _IDChangedEventHandler As EventHandler 09. 'Dichiara il nuovo evento 10. 11. Public Custom Event IDChanged As EventHandler 12. 'Proprio come nelle propriet ci sono i 13. 'blocchi Get e Set per ottenere e assegnare un valore, 'qua di sono i blocchi AddHandler e RomveHandler 14. 15. 'per aggiungere o rimuovere un gestore d'evento e 16. 'il blocco RaiseEvent per lanciarlo 17. AddHandler(ByVal Value As EventHandler) 18. 'Basta richiamare System.Delegate.Combine per 'aggiungere il nuovo gestore Value 19. _IDChangedEventHandler = _ 20. [Delegate].Combine(_IDChangedEventHandler, Value) 21. End AddHandler 22. 23. RemoveHandler(ByVal Value As EventHandler) 24. _IDChangedEventHandler = _ 25. [Delegate].Remove(_IDChangedEventHandler, Value) 26. End RemoveHandler 27. 28. RaiseEvent(ByVal sender As Object, ByVal e As EventArgs) 29. 'Controlla che ci sia almeno un gestore, quindi li 30. 'richiama tutti 31. If _IDChangedEventHandler IsNot Nothing Then 32. _IDChangedEventHandler(sender, e) 33. End If 34. End RaiseEvent 35. End Event 36. 37. Public Property ID() As Int32 38. Get 39. Return _ID 40. End Get 41. Set(ByVal Value As Int32) 42. _ID = Value 43. RaiseEvent IDChanged(Me, EventArgs.Empty) 44. End Set 45. End Property 46. End Class 47. '... 48. 49. End Module Questo codice evidenzia come sia difficile gestir e gli eventi nella ser ializzazione. Per modificar e il compor tamente del for matter , possibile usar e alcuni attr ibuti. Eccone una br eve lista: NonSer ialized : il campo viene saltato dur ante il pr ocesso di salvataggio. Oltr e a poter evitar e fastidiose r iper cussioni sul codice come quella analizzata poco fa, contr ibuisce a r ispar miar e un pochetto di memor ia in pi. Solitamente questo attr ibuto viene applicato a quei valor i che possono esser e dedotti da altr i campi (ad esempio, l'et, che pu esser e calcolata par tendo dalla data di nascita) o che al pr ossimo car icamente sicur amente non conter r anno pi valor i validi (come handle di finestr e o di altr e r isor se non gestite). Solo i for matter Binar y e Soap tengono conto di NonSer ialized, al contr ar io di Xml OptionalField : il campo viene ser ializzato nor malmente, ma nel pr ocesso di car icamento dei dati la sua

mancanza non pr oduce alcun er r or e. Nel caso il campo non sia pr esente, assume il valor e di default della sua categor ia: 0 per i numer i, Nothing per i tipi r efer ence

Serializzazione c ustom
I tipi for niti dal Fr amew or k .Net espongono metodi capaci di r isolver e pr aticamente ogni casistica di pr oblemi e per ci solo in r ar i casi si r icor r e alla ser ializzazione custom. Questo tipo di ser ializzazione non inter viene nei meccanismi che modificano fisicamente il suppor to di memor izzazione e neanche in quelli che r ecuper ano i dati da questo, ma agisce pr ima e dopo che tali azioni vengano compiute. Per cr ear e un oggetto con queste car atter istiche, si deve implementar e l'inter faccia ISer ializable, la quale espone solo un metodo: GetDataObject. Esso ha il compito di selezionar e, tr a tutti i campi disponibili, quali per sister e e quali no, anche sulla base di cer te condizioni, e viene invocato pr ima che abbia inizio il pr ocesso di ser ializzazione. Inoltr e, l'oggetto deve anche espor r e un costr uttor e Pr ivate o Pr otected (a seconda che si debba er editar e oppur e no) con una par ticolar e signatur e. Ecco un esempio: 001. Module Module2 002. <Serializable()> _ 003. Public Class Client Implements ISerializable 004. Private _Name As String 005. <OptionalField()> _ 006. 007. Private _IP As String Private _IsIPStatic As Boolean 008. 009. Public Property Name() As String 010. Get 011. 012. Return _Name 013. End Get Set(ByVal Value As String) 014. _Name = Value 015. End Set 016. End Property 017. 018. 'ReadOnly perch l'IP viene deciso alla connessione 019. 'e poi non subisce cambiamenti 020. Public ReadOnly Property IP() As String 021. Get 022. Return _IP 023. End Get 024. End Property 025. 026. 'L'IP rilevato dal server, normalmente, cambia ad ogni 027. 'connessione e quindi sarebbe inutile serializzarlo. 028. 'Tuttavia, se viene reso statico, ad esempio con 029. 'l'uso di un DNS, allora lo si dovrebbe serializzare 030. 'e ricaricare al successivo avvio. Questa propriet 031. 'ne definisce lo stato e influenza i processi di 032. 'serializzazione e deserializzazione 033. Public Property IsIPStatic() As Boolean 034. Get 035. Return _IsIPStatic 036. End Get 037. Set(ByVal Value As Boolean) 038. _IsIPStatic = Value 039. End Set 040. End Property 041. 042. 'GetDataObject seleziona i campi da serializzare 043. Private Sub GetDataObject(ByVal info As SerializationInfo, _ 044. ByVal context As StreamingContext) _ 045. Implements ISerializable.GetObjectData 046. 'Info si comporta come un dictionary(of String, Object). 047. 'Basta aggiungere i valori da salvare 048. info.AddValue("Name", Me.Name) 049. info.AddValue("IsIPStatic", Me.IsIPStatic) 050. 'Questo passaggio attuabile solo con la 051. 052.

053. 054. 055. 056. 057. 058. 059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099. 100. End

'serializzazione custom If Me.IsIPStatic Then info.AddValue("IP", Me.IP) End If End Sub 'Private New viene richiamato dal formatter dopo la 'deserializzaziore per impostare i valori Private Sub New(ByVal info As SerializationInfo, _ ByVal context As StreamingContext) Me.Name = info.GetString("Name") Me.IsIPStatic = info.GetBoolean("IsIPStatic") If Me.IsIPStatic Then _IP = info.GetString("IP") Else _IP = "127.0.0.1" End If End Sub 'Un costruttore pubblico deve comunque esserci Sub New(ByVal Name As String, ByVal IP As String) Me.Name = Name _IP = IP End Sub End Class Sub Main() 'Crea un nuovo client Dim C As New Client("Totem", "86.45.8.23") Dim Formatter As New Binary.BinaryFormatter() Dim File As New IO.FileStream("C:\client.dat", IO.FileMode.Create) 'Lo serializza, con IP dinamico C.IsIPStatic = False Formatter.Serialize(File, C) File.Close() 'Lo ricarica, e osserva che l'IP stato impostato 'su quello della macchina locale Dim Data As New IO.FileStream("C:\client.dat", IO.FileMode.Open) C = Formatter.Deserialize(Data) Console.WriteLine(C.IP) ' > 127.0.0.1 Data.Close() Console.ReadKey() End Sub Module

Impostando IsIPStatic a Tr ue, l'output cambier in "86.45.8.23". Altr i attr ibuti che si possono usar e sono OnSer ialization, OnSer ialized, OnDeser ialization e OnDeser ialized, che, se applicati a un metodo, lo eseguono nelle var ie fasi della ser ializzazione: pr ima o dopo il salavatggio; pr ima o dopo il car icamento. Per mezzo di questi anche possibile impostar e campi opzionali che devono assumer e un deter minato valor e per esser e validi.

F10. Compressione di dati

Il .NET Fr amew or k consente la compr essione di dati in modo piuttosto semplice. All'inter no del namespace System.IO.Compr ession, infatti, sono esposte due classi, di nome DeflateStr eam e GZipStr eam. Entr ambe hanno lo stesso funzionamento e combinano l'algor itmo di compr essione LZ77 con la codifica di Huffman. Tuttavia, non sono "indipendenti", poich devono r egger si sul suppor to di un altr o str eam, che r appr esenta la ver a connessione con il file da compr imer e/decompr imer e. Il codice da usar e questo: 01. Imports System.IO 02. Imports System.IO.Compression 03. Module Module1 Sub Main() 04. 'I percorsi del file da comprimere e di quello compresso 05. 06. Dim File, CompressedFile As String 07. 'Lo stream che legge i dati da File Dim Input As FileStream 08. 'Lo stream di scrittura associato al file compresso 09. Dim Output As FileStream 10. 'Lo stream compresso che scrive i dati codificati per mezzo 11. 'dell'output stream 12. 13. Dim Zipped As DeflateStream 14. 15. Console.WriteLine("Inserire il percorso del file da compriemere:") File = Console.ReadLine 16. 17. Console.WriteLine("Inserire il percorso in cui salvare i dati compressi:") 18. CompressedFile = Console.ReadLine 19. 'Controlla che il file esista 20. If Not IO.File.Exists(File) Then 21. Console.WriteLine("File inesistente!") 22. Exit Sub 23. End If 24. 25. 'Inizializza lo stream di input 26. Input = New FileStream(File, IO.FileMode.Open) 27. 'Inizializza lo stream di output 28. Output = New FileStream(CompressedFile, IO.FileMode.Create) 29. 'Inizializza lo zipper 30. Zipped = New DeflateStream(Output, CompressionMode.Compress) 31. 32. 'Buffer temporaneo che contiene pacchetti di dati 33. Dim Buffer(4095) As Byte 34. 35. 'Legge i bytes a blocchi di 4KiB 36. For I As Int64 = 0 To Input.Length - 1 Step 4096 37. If Input.Length - I >= 4096 Then 38. Input.Read(Buffer, 0, 4096) 39. Else 40. Input.Read(Buffer, 0, Input.Length - I) 41. End If 42. 'Li scrive sullo stream compresso 43. Zipped.Write(Buffer, 0, Buffer.Length) 44. Next 45. 46. 'Trasferisce dati compressi sullo stream 47. Zipped.Flush() 48. 'Quindi chiude tutti gli stream 49. Zipped.Close() 50. Output.Close() 51. Input.Close() 52. 53. 'Alla fine calcola la compressione totale 54. 'Ottiene la dimensione iniziale del file 55. 56.

Dim StartSize As Int64 = FileLen(File) 57. 'E quella finale del file compresso 58. Dim EndSize As Int64 = FileLen(CompressedFile) 59. Console.WriteLine("Compressione totale: {0:N2}%", _ 60. 61. 100 - (EndSize * 100 / StartSize)) 62. Console.ReadKey() 63. End Sub 64. End Module Potete pr ovar e il pr ogr amma con il file assembly.tx t for nito nell'ultima lezione sulla Reflection, la cui compr essione sar cir ca dell'85%. Bisogna notar e che, alla fine del pr ocesso, i dati sono s compr essi, ma nessun pr ogr amma capace di apr ir li se non quello che si scr itto: sebbe l'algor itmo usato sia quello dei file *.zip, non vengono salvate le infor mazioni necessar ie a str uttur ar e lo str eam in manier a standar d cos da poter esser e letto nor malmente. Nella maggior par te dei casi, questo un vantaggio: il mio pr ogr amma Tot Compr essor (nella sezione dow nload), usa pr opr io queste tecniche, ma cr ea anche una nuova composizione inter na per i file in modo da r iuscir e ad estr apolar ne i dati e a stipar e pi files in uno solo. Per decompr imer e lo stesso file, si agisce in manier a inver sa: l'output sar lo str eam del file decompr esso, l'input quello compr esso e il DeflateStr eam avr modalit di compr essione Decompr ess.

F11. Sicurezza e criptazione

Anche in questo ambito, il Fr amew or k offr e ottime funzionalit, pr ocur ando al pr ogr ammator e molti modi per cifr ar e e decifr ar e messaggi che non dovebber o esser e inter cettabili da alcun altr a per sona che non sia l'utente. Le classi che ser vono per questo scopo sono esposte nei namespace System.Secur ity e System.Secur ity.Cr yptogr aphy. Dopo una br eve intr oduzione, mostr er come sia possibile cr iptar e e decr iptar e dati usando queste potenzialit.

Introduzione alla c riptazione


La cr iptazione una disciplina infor matica che si occupa di oscur ar e messaggi o dati in modo da r ender li accessibili solo alle per sone alle quali sono r ealemnte destinati. Cos facendo, si evita che qualche cr acker indesider ato possa impossessar sene ed utilizzar e le infor mazioni ivi contenute per chiss quali scopi. Esistono tr e tipi di algor itmi di cr iptazione: Sim m etr ici Sono i pi semplici e dir etti metodi di cifr atur a. Per funzionar e necessitano di una chiav e (o passw or d), per mezzo della quale i dati vengono oscur ati. I meccanismi inter ni lavor ano su blocchi di bytes di dimensione pr efissata, solitamente una potenza di 2: ogni algor itmo ha una chiave di dimensioni pr edefinite, spesso espr essa in bit. All'inter no di questo tipo, ci sono due modi oper andi diver si: cifr ar i a blo cchi e cifr ar i basati su str eam . I pr imi pr endono le infor mazioni da cr iptar e e le dividono in blocchi di bytes di lunghezza par i a quella della chiave, quindi eseguono delle oper azioni di Xor fr a ar r ay successivi e r estituiscono in output il r isultato. Il pr imo blocco di bytes viene cifr ato sulla base di un v etto r e di inizializzazione, anche detto IV, ossia un'accozaglia di dati casuali di dimensioni par i alla chiave. I secondi gener ano una chiave pseudo-casuale estr atta manipolando la chiave di base e la inser iscono in uno str eam: pr endono poi pezzi di dati di lunghezza ar bitr ar ia e li Xor ano con il contenuto dello str eam A sim m etr ici Costituiscono i pi sicur i algor itmi di cr iptazione, a cui devono supplir e, per , tempi di elabor azione maggior i. Un algor itmo del gener e ha bisogno di una chiav e pubblica, che pu esser e comunicata a tutti, e una chiav e pr iv ata, che la per sona tiene segr eta. Il messaggio viene inviato cr iptandolo con la chiave pubblica del destinatar io, il quale poi lo decr ipta usando la sua per sonale chiave pr ivata. Data la complessit del lor o funzionamento e la potenza di calcolo r ichiesta, bene usar li solo in caso di messaggi estr emamente br evi, a favor e dei pi abbor dabili algor itmi simmetr ici Hash Questi algor itmi cr eano una str inga di dimensiona fissa che non pu esser e decr iptata in nessun modo: testi uguali gener ano output uguali, ma non c' manier a di r isalir e al messagio or iginale. L'unico modo per saper e se un messaggio equivalente a quello sottoposto all'hash consiste nel compar ar e i due hash Il .Net Fr amew or k mette a disposizione w r apper per i seguenti algor itmi: RC2 (Cifr ar io Rivest): algor itmo simmetr ico a blocchi da 64-bit DES (Data Encr yption Standar d): algor itmo simmetr ico a blocchi da 64-bit 3-DES (Tr iple Data Encr yption Standar d):: algor itmo simmetr ico a blocchi da 192-bit (la chiave ha dimensione maggior e r ispetto al DES nor male) AES (Advanced Encr yption Standar d, alias Rijndael): algor itmo simmetr ico a blocchi da 256-bit

MD5 (Message Digest Algor ithm 5): hash da 128-bit SHA-1 (Secur e Hash Standar d 1): hash da 160-bit SHA-256, SHA-384, SHA-512 (var ianti di Secur e Hash Standar d 2): hash da 256, 384 o 512 bit RSA (Rivest Shamir Adleman, dai nomi dei suoi inventor i): algor itmo asimmetr ico, var iabile da 1024 fino a 4096 bit (di chiave)

Una prova pratic a


Nei seguenti esempi, for nir una dimostr azione del funzionamento degli algor itmi simmetr ici e di hashing. Poich i pr imi der ivando tutti dalla classe base Symmetr icAlgor ithm e i secondi da HashAlgor ithm, il funzionamento illustr ato per uno solo di questi pu esser e r ipetuto in manier a identica (eccetto che per dimensione della chiave) per ogni altr o algor itmo della stessa famiglia. Come esempio per gli algotir mi simmetr ici pr ender il Rijndael (per ch mi piace il nome XD). Pr ima di iniziar e con il codice, bisogna saper e che ogni algor itmo r appr esentato da una classe detta pr o v ider cr itto g r afico, che di solito por ta il nome cor r ispondente. Questo ha il compito di immagazzinar e le infor mazioni sulla chiave e sul blocco di dati e cr ar e ex novo un oggetto deputato alla cr iptazione o decr iptazione dei messaggi. Tale oggetto implementa l'inter faccia ICr yptoTr ansfor m, r appr esenta una tr asfor mazione concr eta sulle infor mazioni for nite e ha la funzione di conver tir le effettivamente tr amite il metodo Tr ansfor mFinalBlock. Ecco un esempio: 001. 002. 003. 004. 005. 006. 007. 008. 009. 010. 011. 012. 013. 014. 015. 016. 017. 018. 019. 020. 021. 022. 023. 024. 025. 026. 027. 028. 029. 030. 031. 032. 033. 034. 035. 036. 037. 038. 039. 040. 041. 042. Imports System.Security Imports System.Security.Cryptography Imports System.Text.UTF8Encoding Module Module1 'Vettore di bytes casuali usati per oscurare la chiave: 'verr usato nella funzione di derivazione della password Private SaltBytes As Byte() = New Byte() _ {162, 21, 92, 34, 27, 239, 64, 30, 136, 102, 223} 'Questo un vettore di inizializzazione per algoritmi 'simmetrici a 256-bit. Si nota, infatti, che lungo 32 bytes Private IV32 As Byte() = New Byte() _ {133, 206, 56, 64, 110, 158, 132, 22, _ 99, 190, 35, 129, 101, 49, 204, 248, _ 251, 243, 13, 194, 160, 195, 89, 152, _ 149, 227, 245, 5, 218, 86, 161, 124} 'La derivazione di password un'altra delle tecniche 'usate in criptazione: si cifra la chiave iniziale con un 'algoritmo di derivazione, fornendo come base un vettore 'di bytes casuali, chiamato <b>salt crittografico</b>. 'L'algoritmo applica una trasformazione sulla chiave un 'numero dato di volte (iterazioni) e restituisce alla fine una 'password di lunghezza specificata. In questo caso, poich 'si sta utilizzando l'algoritmo Rijndael a 256 bit, sar 'di 32 bytes Private Function DerivePassword(ByVal Key As String) As Byte() 'Il provider crittografico Dim Derive As Rfc2898DeriveBytes 'Il risultato dell'operazione Dim DerivedBytes() As Byte 'Crea un nuovo provider crittografico per l'algoritmo 'di derivazione RFC2898, che ha come input Key, come 'salt crittografico l'array SaltBytes sopra definito 'e come numero di iterazioni 5. Il secondo e il terzo 'parametro sono del tutto casuali: li si pu 'modificare arbitrariamente Derive = New Rfc2898DeriveBytes(Key, SaltBytes, 5) 'Applica la trasformazione e deriva una nuova password 'ottenuta come array di 32 bytes

043. 044. 045. 046. 047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114.

DerivedBytes = Derive.GetBytes(32) Return DerivedBytes End Function 'Data una chiave Key e un messaggio Text, usa l'algoritmo simmetrico 'a blocchi Rijndael (AES) per ottenere un insieme di dati criptato Public Function RijndaelEncrypt(ByVal Key As String, _ ByVal Text As String) As Byte() 'Crea il nuovo provider crittografico per questo algoritmo Dim Provider As New RijndaelManaged 'La password derivata Dim BytePassword As Byte() 'L'oggetto che ha il compito di processare le informazioni Dim Encryptor As ICryptoTransform 'L'output della funzione Dim Output As Byte() 'L'input della funzione, ossia il testo convertito 'in forma binaria. Il formato UTF8 permette di 'mantenere anche i caratteri speciali come quelli accentati Dim Input As Byte() = UTF8.GetBytes(Text) 'Imposta la dimensione della chiave Provider.KeySize = 256 'Imposta la dimensione del blocco Provider.BlockSize = 256 'Ottiene la password tramite derivazione dalla chiave BytePassword = DerivePassword(Key) 'Crea un nuovo oggetto codificatore Encryptor = Provider.CreateEncryptor(BytePassword, IV32) 'Cripta il testo Output = Encryptor.TransformFinalBlock(Input, 0, Input.Length) 'Elimina le informazioni fornite al provider Provider.Clear() 'Distrugge l'oggetto codificatore Encryptor.Dispose() Return Output End Function 'Data una chiave Key e un messaggio cifrato Data, usa l'algoritmo 'simmetrico a blocchi Rijndael (AES) per ottenere l'insieme di 'dati di partenza Public Function RijndaelDecrypt(ByVal Key As String, _ ByVal Data() As Byte) As String 'Crea un nuovo provider crittografico Dim Provider As New RijndaelManaged 'La password derivata Dim BytePassword As Byte() 'L'oggetto che ha il compito di processare le informazioni Dim Decryptor As ICryptoTransform 'L'output della funzione in bytes Dim Output As Byte() Provider.KeySize = 256 Provider.BlockSize = 256 BytePassword = DerivePassword(Key) 'Ottiene l'oggetto decodificatore Decryptor = Provider.CreateDecryptor(BytePassword, IV32) 'Tenta di decriptare il messaggio: se la chiave 'sbagliata, lancia un'eccezione Try Output = Decryptor.TransformFinalBlock(Data, 0, Data.Length) Catch Ex As Exception Throw New CryptographicException("Criptazione fallita!") Finally Provider.Clear() Decryptor.Dispose() End Try

115. 116. 117. 118. 119. 120. 121. 122. 123. 124. 125. 126. 127. 128. 129. 130. 131. 132. 133. 134. 135. 136. 137. 138. 139. 140. 141. 142. 143. 144. 145. 146. 147. 148. 149. 150. 151. 152. 153. 154. 155. 156. 157. 158. 159. End

Return UTF8.GetString(Output) End Function 'I dati prodotti in output sono allocati in vettori di bytes, 'ma le stringhe non sono il supporto pi adatto per 'visualizzarli, poich vengono compresi anche 'caratteri di controllo o null terminator. In ogni caso, 'la stringa sarebbe o compromessa o illeggibile (non che 'non lo debba essere). Questa funzione restituisce tutto 'il vettore come rappresentazione esadecimale in stringa 'rendendo pi gradevole la vista del nostro 'magnifico messaggio cifrato Public Function ToHex(ByVal Bytes() As Byte) As String Dim Result As New StringBuilder For I As Int32 = 0 To Bytes.Length - 1 'Accoda alla stringa il codice in formato esadecimale, 'facendo in modo che occupi sempre due posti, eventualmente 'pareggiando con uno zero sulla sinistra Result.AppendFormat("{0:X2}", Bytes(I)) Next Return Result.ToString End Function Sub Main() Dim Input, Output As String Dim Key As String Console.WriteLine("Inserire un testo qualsiasi:") Input = Console.ReadLine Console.WriteLine("Inserire una chiave di criptazione:") Key = Console.ReadLine Try Output = ToHex(RijndaelEncrypt(Key, Input)) Console.WriteLine() Console.WriteLine("Il testo criptato :") Console.WriteLine(Output) Catch CE As CryptographicException Console.WriteLine("Password errata!") End Try Console.ReadKey() End Sub Module

E questo per l'Hash: 01. 02. 03. 04. 05. 06. 07. 08. 09. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. Imports System.Security Imports System.Security.Cryptography Imports System.Text.UTF8Encoding Module Module2 'Questa semplice funzione genera un hash MD5 Public Function GetMd5(ByVal Text As String) As Byte() Dim Input As Byte() = UTF8.GetBytes(Text) Dim Output As Byte() 'MD5.Create() crea un nuovo provider crittografico per l'hash Md5 Output = MD5.Create().ComputeHash(Input, 0, Input.Length) Return Output End Function Sub Main() Dim Input As String Console.WriteLine("Inserire un testo qualsiasi:") Input = Console.ReadLine Console.WriteLine() Console.WriteLine("Il suo hash :") Console.WriteLine(ToHex(GetMd5(Input)))

Console.ReadKey() 25. End Sub 26. End Module

F12. Giocare con i file multimediali

Per poter r ipr odur r e Audio e Video possibile usar e una libr er ia del Fr amew or k, r aggr uppata nel namespace delle Dir ectX. Per questo motivo bisogna pr ima installar e le libr er ie micr osoft Dir ectX: in molti compilator i sono gi incluse nel pacchetto, ma per chi non le possedesse e avesse bisogno di un link di r ifer imento, si possono scar icar e anche qui nella lor o r elease del Giugno 2007. Dopo aver impor tato nel pr ogetto gli oppor tuni r ifer imenti a Micr osoft.Dir ectX e Micr osoft.Dir ectX.AudioVideoPlayback tr amite il men Add Refer ence del solution ex plor er , e scr itto le oppor tune dir ettive di impor tazione in cima al codice, diventano disponibili le due classi Audio e Video. Entr ambi i costr uttor i accettano il per cor so del file multimediale da apr ir e: suppor tano anche per cor si ur l w eb, ed quindi possibile r ipr odur r e file in str eaming. Una volta inizializzati gli oggetti, si possono ottener e moltissime pr opr iet inter essanti, ed altr ettanti metodi. Eccone una lista: Cur r entPosition : la posizione cor r ente nel contesto di r ipr oduzione, espr essa in secondi Dur ation : la dur ata complessiva del file, sempr e in secondi Fr omFile / Fr omUr l : funzioni statiche che inizializzano un nuovo oggetto Audio o Video a par tir e dal per cor so HD o Web specificato Open(F) : apr e il file F nell'oggetto cor r ente (F pu esser e una str inga o un Ur i, Unique Resour ce Identifier ). Questa una pr ocedur a che pu esser e usata nel caso non si voglia cr ear e un ulter ior e oggetto per ogni nuovo file Pause : mette in pausa la r ipr oduzione Paused : deter mina se la r ipr oduzione stata messa in pausa Play : r ipr oduce il file multimediale Playing : deter mina se il file in r ipr oduzione SeekCur r entPosition(T, F) : cambia la posizione cor r ente all'inter no del file. T la posizione desider ata, in secondi, mentr e F un enumer ator e che specifica con quale modalit ci si debba spostar e: tr a i possibili valor i, i pi usati sono AbsolutePosition (indica che T la posizione effettiva) e RelativePosition (indica di spostar si di T secondi in avanti) State : pr opr iet enumer ata che definisce lo stato cor r ente di r ipr oduzione. Pu assumer e tr e valor i: Running (in r ipr oduzione), Paused (in pausa), Stopped (inter r otta) Stop : fer ma la r ipr oduzione. Quando viene r ichiamata, sposta il cur sor se all'inizio del file, mentr e Pause mantiene la posizione cor r ente. Cos, se si r ichiama Stop e successivamente Play, la r ipr oduzione r ipar tit da capo Stopped : deter mina se la r ipr oduzione stata inter r otta StopPosition : indica dove la r ipr oduzione stata inter r otta Volume : modifica il volume della r ipr oduzione. I valor i che pu assumer e vanno da -10000 (muto) a 0 (volume nor male). Questa pr opr iet no n influenza il volume di sistema, ma solo quello dell'oggetto su cui viene r ichiamato I membr i elencati sono esposti sia da Audio che da Video. I seguenti, invece, sono esposti solo da Video: Audio : r estituisce l'oggetto Audio che cor r isponde al video in r ipr oduzione. In questo modo si pu r egolar e il volume o il bilanciamento nor malmente Aver ageTimePer Fr ame : il tempo impiegato dall'oggetto per r ipr odur r e un singolo fr ame. Si pu ottener e il classico valor e FPS (fr ame per second) calcolando il r ecipr oco di questo numer o:

Dim FramePerSecond As Single FramePerSecond = 1 / Video.AverageTimePerFrame Caption : se il video viene avviato senza un'adeguata pr opr iet Ow ner (illustr ata poco pi avanti nell'elenco), ver r automaticamente aper ta una nuova finestr a dentr o la quale esso viene r ipr odotto. Caption imposta il titolo di tale finestr a DefaultSize : indica la dimensione ottimale del video (ossia quella in cui stato cr eato) Fullscr een : indica se il video debba esser e r ipr odotto a tutto scher mo. Questa pr opr iet deve esser e impostata prima di r ichiamar e il metodo Play. Dur ante la r ipr oduzione possibile pr emer e Home per r itor nar e in modalit finestr a, ma lo si pu far e anche agendo da codice dir ettamente sulla pr opr iet HideCur sor : nasconde il mouse sopr a al video IsCur sor Hidden : deter mina se il cur sor e stato nascosto Max imumIdealSize : la massima dimensione possibile pr ima di defor mar e il video MinimumIdealSize : la minima dimensione possibile pr ima di defor mar e il video Ow ner : pr opr iet che definisce il "pr opr ietar io" del video. Con questo s'intende un qualsiasi contr ollo dentr o al quale esso ver r r ipr odotto. consigliabile usar e una Pictur eBox o un Panel per questi compiti Show Cur sor : r ende il mouse di nuovo visibile Size : la dimensione del video Pr ima di concluder e, vor r ei specificar e che le classi sopr a esposte sono valide per la r ipr oduzione di questi tipi di file: Window s Media Video (*.w mv) Moving Pictur es Ex per t Gr oup 1 for mat (*.mpg) Audio Video Inter leave (*.avi) Micr osoft Audio Wave (*.w av) Moving Pictur es Ex per t Gr oup 1 Layer 3 (*.mp3) Window s Media Audio (*.w ma)

F13. Sintesi vocale

Questo capito lo scr itto per VB2008!

Installazione del softw are


Nonostante esistano gi libr er ie appar tenenti al .Net Fr amew or k 3.0 scr itte apposta per questo ar gomento, esse si r eggono a lor o volta su altr e libr er ie - le SAPI - che necessitano di installazione. Per questo capitolo, avr emo bisogno delle SAPI 5.1 per Window s Xp. Recatevi a questo indir izzo e tr over ete un elenco di files da scar icar e: 1. m sttss22L.exe : si tr atta del Micr osoft Tex t-To-Speech Engine. l'insieme di libr er ie che per mette di far legger e un testo al computer con una voce scelta. Se avete gi installato Micr osoft Agent non necessar io scar icar e questo componente 2. sapi.chm : documentazione completa delle SAPI 5.1. Se volete appr ofondir e l'ar gomento scar icatela pur e 3. Sp5TTIntXP.exe : pacchetto autoestr aente che contiene un Micr osoft Mer ge Module da aggiunger e all'installazione nor male. Con questo componente aggiuntivo potr ete due usar e due nuove voci, oltr e al mitico Sam: Micr osoft Mike e Mar y 4. SpeechSDK51.exe : contiene tutti i files per l'installazione - dow nload obbligator io 5. SpeechSDK51Lang Pack .exe : contiene il nor male installer pi alcuni moduli per aggiunger e il r iconocimento delle lingue Cinese e Giapponese. A meno che non siate appassionati dell'Or iente, non vi conviene scar icar lo, dato che sono pi di 80MB 6. speechsdk 51m sm .exe : questo un pacchetto che contiene tutti gli altr i files messi insieme Per il codice che user emo necessar io scar icar e il pr imo e il ter zo componente, ossia il pacchetto di installazione minimo. Una volta scar icati, estr aete il contenuto in una qualsiasi car telle e avviate l'eseguibile "Setup.ex e", che installer tutti i componenti necessar i sul computer .

Sintesi voc ale


Per la sintesi vocale il codice molto semplice e basta un solo oggetto. Sto par lando della classe System.Speech.Synthesis.SpeechSynthesizer . Ecco una r apida panor amica dei suoi membr i: GetCur r entlySpokenPr ompt : r estituisce un oggetto Pr ompt (appar tenente alla classe System.Speech.Synthesis) che r appr esenta la fr ase che il sintetizzator e sta leggendo. Pr ompt ha solo un membr o, la pr opr iet booleana IsCompleted, che comunica quando la lettur a ter minata GetInstalledVoice : r estituisce una collezione di VoiceInfo che r appr esentano le voci installate. Ogni oggetto della collezione espone anche delle pr opr iet che comunicano infor mazioni sulla voce Pause : fa una pausa nella lettur a Resume : r ipr ende a legger e. Utilizzato per r ipr ender e dopo una chiamata a Pause() SelectVoice(N As Str ing) : seleziona la voce N come voce del sintetizzator e. La pi diffusa senza subbio "Micr osoft Sam", poich installata di default su tutti i sistemi oper ativi Window s. Come dicevo pr ima, per , se ne possono scar icar e altr e dal sito Micr osoft SelectVoiceByHints(G As VoiceGener , A As VoiceAge) : ogni voce installata ha delle pr opr ie car atter istiche, pr opr io come una voce nor male. Pu esser e di uomo, di donna o di bambino, e in queste categor ie, pu aver e una differ ente et. Questo metodo ser ve a selezionar e una voce in base a questi cr iter i: la pr ima voce installata

con i r equisiti r ichiesti ver r pr esa e attivata. Sia G che A sono semplici enumer ator i SetOutputToAudioStr eam(S As Str eam, F As AudioFor mat.SpeechAudioFor matInfo) : imposta l'output del sintetizzator e su un flusso di dati, in un dato for mato. Questo un modo complesso di dir e "r egistr a la voce su un file audio": infatti se l'output non sono le casse audio ma un file (str eam), la voce ver r "r egistr ata" in quel file. Lo str eam deve esser e aper to, altr imenti il sintetizzator e non ci pu scr iver e dentr o, mentr e il for mato deve esser e cr eato come oggetto a se stante in pr ecedenza, ad esempio: 1. 2. 3. 4. 5. 6. 7. 8. Dim Format As New AudioFormat.SpeechAudioFormatInfo( _ 44100, AudioFormat.AudioBitsPerSample.Sixteen, _ AudioFormat.AudioChannel.Stereo) Dim Stream As New IO.FileStream("Voce.wav", IO.FileMode.Create) Synt.SetOutputToAudioStream(Stream, Format) Synt.Speak("Hello!") Synt.Dispose() Stream.Close()

Resta ancor a da capir e in che for mato vengano salvati i dati, dato che con *.w av non funziona... Tuttavia la documentazione Micr osoft non pr esenta nessun tipo di spiegazione, n esempi al r iguar do SetOutputToDefaultAudioDevice : imposta l'output del sintetizzator e sul dispositivo standar d di output, ossia le casse del computer SetOutputToNull : annulla l'output SetOutputToWaveFile(F As Str ing) : r egistr a l'output del sintetizzator e sul file w ave il cui per cor so specificato in F. Sicur amente, questo metodo molto pi utile di SetOutputToAudioStr eam. Un piccolo esempio, sempr e ammettendo che Synt sia il nostr o SpeechSynthesizer : 1. Synt.SetOutputToWaveFile("Voce.wav") 2. Synt.Speak("Hello!") 3. Synt.Dispose() Questo metodo r egistr a efficacemente la fr ase "Hello!" sul file Voce.w av SetOutputToWaveStr eam(S As Str eam) : esattamente come il metodo pr ecedente, solo che il par ametr o passato di tipo Str eam Speak(S As Str ing) : legge tutto il testo contenuto in S. Il codice non pr oseguir finch non sia stato letto tutto il testo dato SpeakAsync(S As Str ing) : come sopr a, solo che questo metodo continua su un thr ead differ ente e per ci non blocca l'esecuzione del codice SpeakAsyncCancel : annulla la lettur a di un testo SpeakSsml(S As Str ing) / SpeakSsmlAsync(S As Str ing) : come i metodi pr ecedenti, solo che il testo for mattato in un modo par ticolar e. Ssml significa "Speech Synthesis Mar kup Language": un linguaggio di contr assegno der ivato dall'Xml che indica non solo le fr asi da legger e, ma specifica anche le pr onuncie, per mette di metter e enfasi in una par ola o di simular e un qualche stato d'animo attr aver so la voce State : deter mina lo stato del sintetizzator e (fer mo, in pausa, in lettur a) Voice : oggetto VoiceInfo che designa la voce in uso Volume : volume della voce. Penso che i valor i validi siano da -10000 a 0, come per Audio e Video Or a, far par lar e il computer non da esor cisti, infatti bastano poche linee di codice: 01. 02. 03. 04. 05. 06. 07. 08. 09. 10. 11. Imports System.Speech Imports System.Speech.Recognition Imports System.Speech.Synthesis 'Ricordatevi anche di importare la libreria System.Speech nel progetto, 'con Add Reference Module Module1 Sub Main() 'Inizializza il nuovo sintetizzatore Dim Synt As New SpeechSynthesizer

12. 'Sceglie la classica voce di Microsoft Sam 13. Synt.SelectVoice("Microsoft Sam") 14. 15. 'Imposta l'output sulle casse del computer 16. 'In questo passaggio obbligatorio usare un thread. 17. 'La ragione non ben chiara, ma se non si fa in questo 18. 'modo, risulta sempre un errore di tipo ArgumentException 19. Dim T As Threading.Thread 20. 'Imposta il nuovo thread: il suo compito principale sar 21. 'di eseguire il metodo Synt.SetOutputToDefaultAudioDevice 22. T = New Threading.Thread(AddressOf _ 23. Synt.SetOutputToDefaultAudioDevice) 24. 'Inizia il nuovo thread 25. T.Start() 26. 'Aspetta che abbia finito per continuare 27. T.Join() 28. 29. Dim Text As String 30. Do 31. Console.WriteLine("Inserisci una frase:") 32. Text = Console.ReadLine 33. 34. 'Fa leggere la frase a MS Sam Synt.Speak(Text) 35. Loop Until Text = "" 36. 37. 'Rilascia le risorse 38. Synt.Dispose() 39. End Sub 40. 41. End Module

F14. Riconoscimento vocale

Questo capito lo scr itto per VB2008!

Costruire la grammatic a
Per il r iconoscimento vocale, la faccenda si fa un po' pi complicata. L'oggetto pr incipale su cui si r egge tutto il capitolo System.Speech.Recognition.SpeechRecognitionEngine. Non analizzer in dettaglio i suoi membr i, poich la gr an par te di essi ver r spiegata nel codice che scr iver dopo: baster dir e che per l'inizializzazione, anch'esso dispone di metodi SetInpuTo... identici a quelli di SpeechSynthesizer . Or a, il computer non pu pr eveder e tutte le possibili combinazioni di par ole esistenti, quindi dobbiamo esser e noi a for nir gli un "dizionar io" su cui basar si per il r iconoscimento. La costr uzione di una str uttur a di questo tipo r ichiede l'uso di un oggetto par ticolar e: Gr ammar Builder . Questa semplice classe aiuta a for mar e delle fr asi che potr anno venir e cattur ate e r iconosciute dall'Engine attr aver so il micr ofono. Nelle pr ove che ho fatto, l'engine r iuscito a cattur ar e una sola par ola alla volta, ma for se mi sono dimenticato di impostar e i tempi giusti di inter vallo tr a una par ola e l'altr a. Sta di fatto che il modo pi semplice per far r iconoscer e una qualsiasi par ola all'engine consiste nell'aggiunger e a Gr ammar Builder la lista di tutte le par ole contemplate (ossia, solo quelle che vogliamo noi). Ecco un semplice esempio: 1. 2. 3. 4. 5. 6. 7. Imports System.Speech Imports System.Speech.Recognition Imports System.Speech.Synthesis '... Dim GrammarBuilder As New GrammarBuilder GrammarBuilder.Append(New Choices("one", "two", "three", "four"))

In questo modo si aggiunta al Gr ammar Builder una gamma di par ole possibili: one, tw o, thr ee e four . Usando un oggetto Choices comunichiamo all'engine che ogni par ola pu esser e pr esa singolar mente. Ho usato dei numer i per ch l'esempio di questo capitolo sar un pr ogr amma in gr ado di r iconoscer e un numer o pr onunciato in inglese. A questo pr oposito, bisogna dir e che l'oggetto Gr ammar Builder pu esser e cr eato per differ enti lingue (anche se non ci sono ancor a pacchetti per molte lingue diver se), ma esso da solo non in gr ado di r iconoscer e a quale lingua appar tengano le par ole che il pr ogr ammator e inser isce: per questo pr ende come nazionalit di default quella del computer su cui sta cor r endo. Per il 99% di color o che leggono questa guida, quindi, Gr ammar Builder consider er la cultur a cor r ente come Italiana, poich il computer ha installata quella. Tuttavia l'engine che user emo impostato so lo per la lingua inglese e questo gener er sicur amente un er r or e. Per ci, pr ima di inser ir e la gr ammatica di Gr ammar Builder nell'engine di r iconoscimento vocale, dobbiamo specificar e esplicitamente che si tr atta di inglese: 1. GrammarBuilder.Culture = Globalization.CultureInfo.GetCultureInfo("en-US") Una volta fatto questo, per for mar e una nuova gr ammatica usabile (un insieme di par ole in questo caso) bisogna cr ear e un nuovo oggetto Gr ammar e passar e al costr uttor e Gr ammar Builder come par ametr o: 1. Dim Grammar As Grammar 2. '... 3. Grammar = New Grammar(GrammarBuilder) Or a manca la cosa pi impor tante: l'engine di r iconoscimento vocale. User emo la classe SpeechRecognitionEngine, che descr iver dir ettamente nei commenti del pr ossimo esempio.

Esempio: Tell me how muc h


Per questo esempio basta un semplicissimo for m, con una label (lblNumber ) e due pulsanti (btnStar t e btnStop):

Questo il codice: 001. 002. 003. 004. 005. 006. 007. 008. 009. 010. 011. 012. 013. 014. 015. 016. 017. 018. 019. 020. 021. 022. 023. 024. 025. 026. 027. 028. 029. 030. 031. 032. 033. 034. 035. 036. 037. 038. 039. 040. 041. 042. 043. 044. 045. 046. 047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. 060. 061. 062. Imports System.Speech Imports System.Speech.Recognition Imports System.Speech.Synthesis Public Class Form2 'Nuovo Engine di riconoscimento vocale Private Engine As New SpeechRecognitionEngine 'GrammarBuilder per costruire la grammatica Private GrammarBuilder As New GrammarBuilder 'Oggetto Grammar che rappresenta la grammatica Private Grammar As Grammar 'Questo dizionario associa ad ogni parola il 'corrispondente valore numerico (one=1) Private TextNumber As Dictionary(Of String, Int32) 'Questo array gi inizializzato contiene l'elenco di 'tutte le parole che l'engine pu rilevare Private Numbers As String() = _ New String() {"one", "two", "three", "four", _ "five", "six", "seven", "eight", "nine", "ten", _ "eleven", "twelve", "thirteen", "fourteen", _ "fiftheen", "sixteen", "seventeen", "eighteen", _ "nineteen", "twenty", "thirty", "fourty", "fifty", _ "sixty", "seventy", "eighty", "ninty", "hundred", _ "thousand", "reset"} 'L'ultima parola, reset, serve per porre a 0 il 'conteggio, nel caso si volesse ripetere 'Prev ricorda l'ultimo numero immesso Private Prev As Int32 'Result contiene il numero finale Private Result As Int32 Private Sub Form2_Load(ByVal sender As Object, ByVal e As EventArgs) _ Handles MyBase.Load 'All'avvio del form, si imposta l'input dell'engine sul 'normale microfono (che deve essere collegato al computer). 'Anche in questo caso si usa un thread, per lo stesso 'motivo citato nel capitolo precedente Dim T As New Threading.Thread( _ AddressOf Engine.SetInputToDefaultAudioDevice) T.Start() T.Join() 'Poi si genera il dizionario che associa le parole ai 'valori numerici veri e propri. Dato che l'array 'Numbers contiene i numeri in ordine, sfruttermo 'qualche for per riempire il dizionario in poche 'righe di codice TextNumber = New Dictionary(Of String, Int32) With TextNumber 'I primi 20 numeri sono in ordine crescente, 'da 1 a 20. Perci basta aggiungere 1 'all'indice I per ottenere il numero che la 'parola indica For I As Int16 = 0 To 19 .Add(Numbers(I), I + 1) Next 'I successivi sette numeri sono tutti i multipli 'di 10, da 30 a 90. Con la formula: '(I-19)*10 + 20

063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121. 122. 123. 124. 125. 126. 127. 128. 129. 130. 131. 132. 133. 134.

' come se I andasse da 1 a 7 e quindi 'otteniamo tutte le decine da 20+10 a 20+70 For I As Int16 = 20 To 26 .Add(Numbers(I), (I - 19) * 10 + 20) Next 'Infine si aggiungono centinaia e migliaia a parte .Add("hundred", 100) .Add("thousand", 1000) End With 'Aggiunge tutte le parole-numero al GrammarBuilder GrammarBuilder.Append(New Choices(Numbers)) 'Imposta la lingua a inglese GrammarBuilder.Culture = _ Globalization.CultureInfo.GetCultureInfo("en-US") 'Costruisce la nuova "grammatica" con il GrammarBuilder Grammar = New Grammar(GrammarBuilder) 'Questo metodo serve per eliminare tutte le grammatiche 'gi presenti. Anche se quasi sicuramente non ci 'sar nessun grammatica precaricata, sempre 'meglio farlo prima di aggiungerne di nuove Engine.UnloadAllGrammars() 'Quindi carica la grammatica Grammar. Ora Engine in 'grado di riconoscere le parole dell'array Numbers Engine.LoadGrammar(Grammar) 'Parte importantissima: aggiunge l'handler di evento per 'l'evento SpeechRecognized, che viene lanciato quando 'l'engine ha ascoltato la voce, l'ha analizzata e ha 'trovato una corrispondenza valida nella sua grammatica AddHandler Engine.SpeechRecognized, AddressOf Speech_Recognized End Sub Private Sub btnStart_Click(ByVal sender As Object, _ ByVal e As EventArgs) Handles btnStart.Click 'Fa partire il riconoscimento vocale. Il metodo asincrono, 'quindi viene eseguito su un altro thread e non blocca il form 'chiamante. L'argomento Multiple indica che si effetteranno pi 'riconoscimenti e non uno solo Engine.RecognizeAsync(RecognizeMode.Multiple) 'Disabilita Start e abilita Stop btnStart.Enabled = False btnStop.Enabled = True End Sub Private Sub btnStop_Click(ByVal sender As Object, _ ByVal e As EventArgs) Handles btnStop.Click 'Termina il riconoscimento asincrono Engine.RecognizeAsyncCancel() 'Abilita Start e disabilita Stop btnStart.Enabled = True btnStop.Enabled = False End Sub Private Sub Speech_Recognized(ByVal sender As Object, _ ByVal e As SpeechRecognizedEventArgs) Dim N As Int32 'Ottiene il testo, ossia la parola pronunciata Dim Text As String = e.Result.Text 'Se il testo "reset", annulla tutto If Text = "reset" Then Result = 0 End If 'Se il testo contenuto nel dizionario, allora ' un numero valido If TextNumber.ContainsKey(Text) Then 'Ottiene il numero N = TextNumber(Text)

'Se 100, significa che si pronunciato 135. '"hundred". Hundred indica le centinaia e perci 136. 'sicuramente non si pu dire "twenty hundred", n 137. '"one thousand hundred": l'unico caso in cui si pu 138. 'usare hundred dopo una singola cifra, ad esempio 139. '"one hundred" o "nine hundred". Quindi controlla che il 140. 'numero precedente sia compreso tra 1 e 9 141. If (N = 100) And (Prev > 0 And Prev < 10) Then 142. 'Toglie l'unit 143. Result -= Prev 144. 'E la trasforma in centinaia 145. Result += Prev * 100 146. End If 147. 'Parimenti, si pu usare "thousand" solo dopo un 148. 'numero minore di mille. Anche se lecito, nessuno direbbe 149. '"a thousand thousand", ma piuttosto "a million" 150. If (N = 1000) And (Result < 1000) Then 151. Result *= 1000 152. End If 153. 'Se il numero minore di 100, semplicemente lo 154. 'aggiunge. Se quindi si pronunciano "twenty" e "thirty" 155. 'di seguito, si otter 50. Non chiedetemi perch 156. 'l'ho fatto cos... 157. If (N < 100) Then Result += N 158. End If 159. Else 160. N = 0 161. End If 162. 163. Prev = N 164. 165. 'Imposta il testo della label 166. lblNumber.Text = String.Format("{0:N0}", Result) 167. End Sub 168. 169. End Class Eseguendo questo codice e par lando bene nel micr ofono, si dovr ebbe ottener e un r isultato discr eto (alcune par ole, comunque, si confondono). Nonostante il codice sia esatto, tuttavia, System.Speech r imane un namespace str ano, poich le sue classi gener ano spesso er r or i incompr ensibili. Se siete stati cos for tunati da aver r iecevuto, come me, una Tar getInvocationEx ception nell'evento SpeechRecognized, mi sar ete gr ati per il codice che pr opongo qui, un'alter nativa pi di quella sopr a, ma almeno pi sicur a: 001. 002. 003. 004. 005. 006. 007. 008. 009. 010. 011. 012. 013. 014. 015. 016. 017. 018. 019. 020. 021. 022. 023. 024. 025. 026. 027. 028. Imports System.Speech Imports System.Speech.Recognition Imports System.Speech.Synthesis Public Class Form2 'Nuovo Engine di riconoscimento vocale Private Engine As New SpeechRecognitionEngine 'GrammarBuilder per costruire la grammatica Private GrammarBuilder As New GrammarBuilder 'Oggetto Grammar che rappresenta la grammatica Private Grammar As Grammar 'Questo dizionario associa ad ogni parola il 'corrispondente valore numerico (one=1) Private TextNumber As Dictionary(Of String, Int32) 'Questo array gi inizializzato contiene l'elenco di 'tutte le parole che l'engine pu rilevare Private Numbers As String() = _ New String() {"one", "two", "three", "four", _ "five", "six", "seven", "eight", "nine", "ten", _ "eleven", "twelve", "thirteen", "fourteen", _ "fiftheen", "sixteen", "seventeen", "eighteen", _ "nineteen", "twenty", "thirty", "fourty", "fifty", _ "sixty", "seventy", "eighty", "ninty", "hundred", _ "thousand", "reset"} 'L'ultima parola, reset, serve per porre a 0 il 'conteggio, nel caso si volesse ripetere

029. 030. 031. 032. 033. 034. 035. 036. 037. 038. 039. 040. 041. 042. 043. 044. 045. 046. 047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099. 100.

'Delegato che servir dopo Private Delegate Sub SetLabel(ByVal Res As RecognitionResult) 'Prev ricorda l'ultimo numero immesso Private Prev As Int32 'Result contiene il numero finale Private Result As Int32 Private Sub Form2_Load(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles MyBase.Load 'All'avvio del form, si imposta l'input dell'engine sul 'normale microfono (che deve essere collegato al computer). 'Anche in questo caso si usa un thread, per lo stesso 'motivo citato nel capitolo precedente Dim T As New Threading.Thread( _ AddressOf Engine.SetInputToDefaultAudioDevice) T.Start() T.Join() 'Poi si genera il dizionario che associa le parole ai 'valori numerici veri e propri. Dato che l'array 'Numbers contiene i numeri in ordine, sfruttermo 'qualche for per riempire il dizionario in poche 'righe di codice TextNumber = New Dictionary(Of String, Int32) With TextNumber 'I primi 20 numeri sono in ordine crescente, 'da 1 a 20. Perci basta aggiungere 1 'all'indice I per ottenere il numero che la 'parola indica For I As Int16 = 0 To 19 .Add(Numbers(I), I + 1) Next 'I successivi sette numeri sono tutti i multipli 'di 10, da 30 a 90. Con la formula: '(I-19)*10 + 20 ' come se I andasse da 1 a 7 e quindi 'otteniamo tutte le decine da 20+10 a 20+70 For I As Int16 = 20 To 26 .Add(Numbers(I), (I - 19) * 10 + 20) Next 'Infine si aggiungono centinaia e migliaia a parte .Add("hundred", 100) .Add("thousand", 1000) End With 'Aggiunge tutte le parole-numero al GrammarBuilder GrammarBuilder.Append(New Choices(Numbers)) 'Imposta la lingua a inglese GrammarBuilder.Culture = Globalization.CultureInfo.GetCultureInfo("en-US") 'Costruisce la nuova "grammatica" con il GrammarBuilder Grammar = New Grammar(GrammarBuilder) 'Questo metodo serve per eliminare tutte le grammatiche 'gi presenti. Anche se quasi sicuramente non ci 'sar nessun grammatica precaricata, sempre 'meglio farlo prima di aggiungerne di nuove Engine.UnloadAllGrammars() 'Quindi carica la grammatica Grammar. Ora Engine in 'grado di riconoscere le parole dell'array Numbers Engine.LoadGrammar(Grammar) 'Parte importantissima: aggiunge l'handler di evento per 'l'evento SpeechRecognized, che viene lanciato quando 'l'engine ha ascoltato la voce, l'ha analizzata e ha 'trovato una corrispondenza valida nella sua grammatica AddHandler Engine.SpeechRecognized, AddressOf Speech_Recognized End Sub Private Sub btnStart_Click(ByVal sender As Object, _ ByVal e As EventArgs) Handles btnStart.Click 'Fa partire il riconoscimento vocale. Il metodo asincrono, 'quindi viene eseguito su un altro thread e non blocca il form

101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121. 122. 123. 124. 125. 126. 127. 128. 129. 130. 131. 132. 133. 134. 135. 136. 137. 138. 139. 140. 141. 142. 143. 144. 145. 146. 147. 148. 149. 150. 151. 152. 153. 154. 155. 156. 157. 158. 159. 160. 161. 162. 163. 164. 165. 166. 167. 168. 169. 170. 171. 172.

'chiamante. L'argomento Multiple indica che si effetteranno pi 'riconoscimenti e non uno solo Engine.RecognizeAsync(RecognizeMode.Multiple) 'Disabilita Start e abilita Stop btnStart.Enabled = False btnStop.Enabled = True End Sub Private Sub btnStop_Click(ByVal sender As Object, _ ByVal e As EventArgs) Handles btnStop.Click 'Termina il riconoscimento asincrono Engine.RecognizeAsyncCancel() 'Abilita Start e disabilita Stop btnStart.Enabled = True btnStop.Enabled = False End Sub Private Sub Speech_Recognized(ByVal sender As Object, _ ByVal e As SpeechRecognizedEventArgs) 'Pu capitare che dopo l'esecuzione di questo evento, 'sia generata un'eccezione TargetInvocationException, causata 'dall'engine, il quale lancia un evento uguale prima che 'questo sia terminato. Usando un thread risolviamo tutto Dim T As New Threading.Thread(AddressOf InvokeSetLabel) T.Start(e.Result) End Sub Private Sub InvokeSetLabel(ByVal Res As RecognitionResult) 'Ovviamente questi stupido tipo di errori ci fa usare 'una via alternativa sprecando molto codice in pi. 'Dato che, come sapete, non si pu accedere ai 'controlli di un form da un thread differente da quello 'in cui sono stati creati, dobbiamo usare Invoke 'per far eseguire lo stesso compito al thread principale 'partendo da questo thread secondario. 'Per chi non si ricorda i delegate, Invoke permette di 'far correre un metodo nel thread dell'oggetto da cui 'richiamato (Me, ossia il form). In questo caso usiamo 'il delegato di tipo SetLabel che punta ad AnalyuzeText 'e gli passiamo dierettamente Res come parametro Me.Invoke(New SetLabel(AddressOf AnalyzeText), _ New Object() {Res}) End Sub Private Sub AnalyzeText(ByVal Res As RecognitionResult) Dim N As Int32 'Ottiene il testo, ossia la parola pronunciata Dim Text As String = Res.Text 'Se il testo "reset", annulla tutto If Text = "reset" Then Result = 0 End If 'Se il testo contenuto nel dizionario, allora ' un numero valido If TextNumber.ContainsKey(Text) Then 'Ottiene il numero N = TextNumber(Text) 'Se 100, significa che si pronunciato '"hundred". Hundred indica le centinaia e perci 'sicuramente non si pu dire "twenty hundred", n '"one thousand hundred": l'unico caso in cui si pu 'usare hundred dopo una singola cifra, ad esempio '"one hundred" o "nine hundred". Quindi controlla che il 'numero precedente sia compreso tra 1 e 9 If (N = 100) And (Prev > 0 And Prev < 10) Then 'Toglie l'unit Result -= Prev 'E la trasforma in centinaia

Result += Prev * 100 173. End If 174. 'Parimenti, si pu usare "thousand" solo dopo un 175. 'numero minore di mille. Anche se lecito, nessuno direbbe '"a thousand thousand", ma piuttosto "a million" 176. 177. If (N = 1000) And (Result < 1000) Then 178. Result *= 1000 179. End If 180. 'Se il numero minore di 100, semplicemente lo 181. 'aggiunge. Se quindi si pronunciano "twenty" e "thirty" 182. 'di seguito, si otter 50. Non chiedetemi perch 183. 'l'ho fatto cos... 184. If (N < 100) Then 185. Result += N 186. End If 187. Else 188. N = 0 189. End If 190. 191. Prev = N 192. 193. 'Imposta il testo della label 194. lblNumber.Text = String.Format("{0:N0}", Result) 195. End Sub 196. End Class Potr ebbe anche ver ificar si un altr o er r or e, qui: 1. Me.Invoke(New SetLabel(AddressOf AnalyzeText), New Object() {Res}) di tipo For matEx ception, che non c'entr a assolutamente niente con quello che si sta facendo. Se anche voi siete cos for tunati, chiudete visual studio e r ipr ovateci domani (con me ha funzionato XD).

G1. Il Namespace My

Il namespace My una novit di Visual Basic 2005: stato intr odotto per for nir e un accesso facilitato a classi del fr amew or k utili usate molto spesso, diminuendo cos il codice necessar io e di conseguenza in tempo impiegato a scr iver lo. My contiene oggetti singleton aggior nati dur ante la cr eazione dell'applicazione nell'ambiente di sviluppo: ad esempio My.Settings viene popolato con le r isor se e le impostazioni aggiunte dall'utente, oppur e i membr i di My.For ms sono aggiunti ogniqualvolta viene aggiunto un for m all'applicazione e costituiscono una scor ciatoia per r ichiamar li senza definir ne altr e istanze. Ecco una panor amica dei membr i di My: My.Application : espone infor mazioni sull'applicazione cor r ente, da dove sia stata lanciata, quale utente la stia utilizzando e con quali per messi e le infor mazioni assembly quali ver sione e cultur a My.Computer : per mette un'inter azione r apida e veloce con le per ifer iche del computer quali mouse e tastier a, con il filesystem, con la memor ia, con i for mati audio e video e per mette di gestir e le por te ser iali e la stampante My.For ms : espone una pr opr iet per ogni for m definito, cor r ispondente alla sua istanza di default My.Resour ces : contiene oggetti che fanno da w r apper a ciascuna r isor se utilizzata nel pr ogetto e r efer enziata esplicitamente My.Settings : ogni pr opr iet cor r isponde ad un'impostazione definita nei Settings del pr ogetto. Un oggetto pu esser e di qualsiasi tipo suppor tato e pu aver e scopo differ ente a seconda che venga usato dall'utente o dal pr ogr amma My.User : r estituisce infor mazioni sull'utente che sya usando il computer My.WebSer vices : per mette di usar e ser vizi w eb e r ichiamar e metodi w eb senza dover e scr iver e lo stesso codice pi volte

My .Applic ation
Ecco una lista dei membr i pi significativi di questo oggetto: ApplicationContex t : r estituisce il contesto applicativo in cui viene eseguito il pr ogr amma, tr amite cui si pu ottener e il Main For m CommandLineAr gs : r estituisce una lista in sola lettur a a tipizzazione for te di str inghe contenente tutti gli ar gomenti passati da console al pr ogr amma. Ond'evitar e confusione, per chi pr ovenisse da altr i linguaggi, il pr imo ar gomento no n il nome del pr ogr amma stesso ma pr opr io il pr imo ar gomento che viene dopo la dichiar azione dell'applicazione Cultur e : r estituisce un oggetto Cultur eInfo che per mette di aver e infor mazioni sulla cultur a cor r ente, in par ticolar e il modo in cui vengono for mattati valor i e date Deployment : r estituisce un oggetto ApplicationDeployment che per mette di scar icar e e aggior nar e il pr ogr amma scar icando i nuovi files da inter net. L'uso di questo oggetto ver r tr attato in un altr o momento, bench nel momento in cui scr ivo, un capitolo al r iguar do non sia ancor a stato inser ito nell'indice della guida Info : r estituisce un oggetto AssemblyInfo che consente di visualizzar e le infor mazioni sul pr ogr amma, quali titolo, ver sione, autor e, societ, descr izione ecc... Queste impostazioni possono esser e for nite al compilator e tr amite Designer o tr amite codice, ma anche questo punto ver r tr attato in seguito Log : r estituisce un oggetto Log del namespace VisualBasic.Logging, i cui metodi per mettono di inter agir e con i file di log, scr ivendo messaggi o r epor t di er r or i r iscontr ati a r un-time

OpenFor ms : se il pr ogetto cor r ente una Window s Application, ottiene una collezione di tutti i for m aper ti SaveMySettingsOnEx it : se il pr ogetto cor r ente una Window s Application, deter mina qualor a tutti i campi della classe My.Settings debbano esser e automaticamente salvati pr ima dell'uscita dal pr ogr amma DoEvents : se il pr ogetto cor r ente una Window s Application, pr ocessa tutti i messaggi w indow s in coda. Questo per mette che gli eventi siano gener ati e oppor tunamente gestiti anche dur ante lo svolgimento di una pr ocedur a par ticolar mente lunga (come quella di r icer ca dei file), oppur e che la gr afica del for m venga cor r ettamente aggior nata, impendendo all'applicazione di assumer e un aspetto che ver r ebbe altr imenti inter pr etato dall'utente come "bloccato". Sebbene sia molto comodo usar e DoEvents in casi del gener e, i pr incipi Micr osoft sugger iscono invece di utilizzar e un contr ollo Backgr oundWor ker oppur e un thr ead separ ato cr eato manualmente GetEnvir onmentVar iable(S) : r estituisce il valor e della var iabile d'ambiente di nome S. Le v ar iabili d'am biente sono cr eate, usate e gestite dal sistema oper ativo e contengono infor mazioni sulle dir ector y, sui file e sui dettagli tecnici dell'har dw ar e e del softw ar e. possibile ottener e in questo modo, ma anche attr aver so la classe System.Envir onment. Non sicur o modificar e alcune di esse, come ad esempio, la car tella di Window s Run(C()) : avvia una nuova istanza di questa applicazione passandole gli ar gomenti definiti nell'ar r ay di str inghe C() Fr a le altr e cose, questa classe espone anche degli eventi inter essanti: Star tup e Shutdow n vengono lanciati r ispettivamente quando l'applicazione viene aper ta o chiusa, ma possono facilmente esser e sostituiti dai pi semplici For mLoad e For mClosing; inter essanti sono invece gli eventi Star tupNex tInstance, lanciato quando viene avviata un'altr a istanza dell'applicazione, UnhandledEx ception, gener ato ogniqualvolta si r iscontr a un er r or e non gestito (e quindi utilissimo per non far appar ir e la famosa messagebox di er r or e cr itico) e Netw or kAvaiabilityChanged, che r ipor ta un cambiamento nello stato di usabilit della r ete (ossia quando ci si connette o disconnette). Gestir e cor r ettamente tali eventi non pu che por tar e benefici alla solidit e all'efficienza del pr ogr amma.

My .Computer
Ad eccezione di Name, che r estituisce il nome del computer , tutte le altr e pr opr iet esposte sono oggetti figli i quali a lor o volta mettono a disposizione altr e funzionalit: in questo namespace vengono r iassunti i metodi pi utili sul piano dell'audio, del filesystem, del r egistr o di sistema e del r ecuper o infor mazioni. Ecco una lista di quasi tutte le pr opr iet (Por ts stata tr alasciata poich r ar amente usata, mentr e Registr y non ver r analizzato, per ovvi motivi): Audio Play(S, M) : esegue un suono memor izzato in un file Audio Wave (*.w av), definito dal per cor so S. possibile specificar e anche, nell'over load, le modalit con cui avviene la r ipr oduzione. Esse sono espr esse da un enumer ator e a tr e valor i: Backgr ound (musica di sottofondo), Backgr oundLoop (la musica viene r ipetuta all'infinito, fino a quando non la si fer ma manualmente), WaitUntilComplete (aspetta che tutto il file sia r ipr odotto pr ima di passar e all'istr uzione successiva). Ad ogni modo, per la costr uzione di un Media Player assai meglio r icor r er e all'aiuto delle libr er ie Dir ectX.AudioVideoPlayback PlaySystemSound(S) : esegue un suono di default di Window s, definito dall'enumer ator e S di tipo System.Media.SystemSound. I valor i r ipor tati sono quasi gli stessi dell'enumer ator e che definisce le icone della MessageBox : infatti i suoni in questione non sono altr o che quelli r ipr odotti all'appar ir e di una MessageBox Stop : inter r ompe l'esecuzione di un suono in backgr ound Clipboar d (gli appunti) Clear : pulisce la clipboar d, annullando qualsiasi suo contenuto Contains... : le funzioni che iniziano per "Contains" deter minano quale tipo di dato contengano gli appunti, se immagini, suoni, files, testo o altr o

Get... : le funzioni che iniziano con "Get" r estituiscono il contenuto della clipboar d secondo i var i for mati Set... : allo stesso modo, le funzioni Set impostano il contenuto della clipboar d secondo i var i for mati Clock LocalTime : r estituisce la data e l'or a cor r ente memor izzate sul computer TickCound : r estituisce il numer o di tick passati dall'accesione del computer . Un tick cor r isponde a un milionesimo di secondo, ma questa pr opr iet, str anamente, r estituisce il r isultato in millisecondi FileSystem CopyDir ector y(S, D, O) : nel suo pr imo over load, questa pr ocedur a copia la dir ector y indicata da S nella dir ector y D; se D non esiste viene cr eata; se esiste e O (Over w r ite) = Tr ue, viene sovr ascr itta CopyDir ector y(S, D, Show , Cancel) : nel suo secondo over load, copia la dir ector y da S a D; Show un valor e che indica se visualizzar e la finestr a di copia pr edefinita di w indow s, mentr e Cancel specifica se possibile annullar e l'oper azione tr amite tale finestr a CopyFile(S, D, ...) : copia un file da S a D. Pr esenta over load uguali a quelli sopr a descr itti Cr eateDir ector y(D) : cr ea una nuova dir ector y secondo il per cor so D for nito Cur r entDir ector y : la dir ector y cor r ente DeleteDir ector y(D, F) : elimina una car tella D, specificando tr amite F, se si debba gener ar e un'eccezione qualor a la car tella non sia vuota DeleteDir ector y(D, Show , Recycle, Cancel) : elimina la car tella D, eventualmente visualizzando la finestr a di dialogo di w indow s; possibile specificar e se eliminar e completamente la car tella o se mandar la nel cestino con Recycle, mentr e Cancel indica se sia o meno concessa all'utente la possibilit di annullar e l'oper azione DeleteFile(F, ...) : elimina il file F. Pr esenta over load uguali a quelli sopr a descr itti Dir ector yEx ists(P) / FileEx ists(P) : deter minano se il file o la car tella P esistano o meno Dr ives : r estituisce una collezione in sola lettur a a tipizzazione for te di Dr iveInfo contenente infor mazioni sui var i dr ives disponibili FindInFiles(P, S, Case, Recur se) : r estituisce una collezione in sola lettur a a tipizzazione for te di Str ing contenente i nomi di tutti i files tr ovati. P la car tella in cui cer car e, S la par ola da cer car e nel file, Case indica se la r icer ca case sensitive oppur e no, mentr e Recur se indica se analizzar e anche le sottodir ector y GetDr iveInfo(P) / GetDir ector yInfo(P) / GetFileInfo(P) : r estituiscono infor mazioni sul per cor so specificato GetDir ector ies(P, R) / GetFiles(P, R) : r estituiscono r ispettivamente le car telle e i files pr esenti nella dir ector y P, eventualmente agendo r icor sivamente se R = Tr ue GetName(P) : r estituisce il nome del file o della car tella (la sua ultima par te) GetPar entPath(P) : r estituisce la car tella che si tr ova a livello super ior e r ispetto al file o alla dir ector y P GetTempFileName : cr ea un nuovo file nella car tella tempor anea del computer , vuoto, e ne r estituisce il per cor so MoveDir ector y : esattamente come CopyDir ector y, solo che la car tella di par tenza viene r imossa MoveFile : come sopr a OpenTex tFieldPar ser (P, W()) : apr e un par ser di file di testo sul file P. Questo oggetto legge i valor i in esso contenuti seguendo le dir ettive specificate nel Par amAr r ay W. Se si tr atta di dati a lar ghezza fissa, W specifica le lar ghezza dei dati; se si tr atta di dati separ ati da car atter i speciali, W specifica quei car atter i ReadAllBytes / ReadAllTex t : leggono tutti i bytes o tutto il testo del file specificato e li/lo r estituisce/ono RenameDir ector y(P, N) / RenameFile(P, N) : r inominano il file o la dir ector y P con un nuovo nome N (solo il nome, non tutto il per cor so) SpecialDir ector ies : r estituisce un oggetto le cui pr opr iet indicano il per cor so delle car telle speciali, come i documenti, le immagini, la car tella tempor anea, i video, ecceter a... Wr iteAllTex t / Wr iteAllBytes : scr ivono tutto il testo o tutti i bytes dati all'inter no del file specificato

Info AvaiablePhysicalMemor y : l'ammontar e di memor ia fisica liber a sul computer , in bytes OSFullName : il nome completo del sistema oper ativo OSPlatfor m : identifica la piattafor ma del sistema oper ativo TotalPhysicalMemor y : l'ammontar e totale di memor ia fisica del computer , in bytes Keyboar d AltKeyDow n, Ctr lKeyDow n, ShiftKeyDow n : r estituiscono Tr ue se Alt, Ctr l o Shift r isultano pr emuto nell'istante in cui la funzione viene r ichiamata CapsLock, NumLock, Scr ollLock : r estituiscono Tr ue se sono attivi CpasLock, NumLock o Scr ollLock SendKeys(K, W) : simula la pr essione del tasto K sulla finestr a attiva, opzionalmente aspettando finch tale messaggio non venga pr ocessato (W = Tr ue) Mouse ButtonsSw apped : deter mina se i pulsanti destr o e sinistr o r isultano scambiati WheelEx ists : deter mina se il mouse sia dotato di r otellina WheelScr ollLines : deter mina di quante linee si debba scor r er e quando la r ottelina venga r uotata di una tacca Netw or k Dow nloadFile(S, D) : scar ica il file S sul computer , salvandolo come D. possibile specificar e come ter zo e quar to par ametr o un nome utente e una passw or d nel caso il ser ver in questione ne r ichieda uno. Il quinto par ametr o, se pr esente, specifica se visualizzar e la finestr a di dialogo di default di w indow s; il sesto indica l'opzionale timeout di connessione. Il settimo specifica se sovr ascr iver e un file esistente e il settimo se sia possibile annullar e l'oper azione. Tutti i par ametr i dopo il secondo sono opzionali e for niti dagli over loads della funzione IsAvaiable : deter mina se il computer sia connesso o meno a una r ete Ping(IP, Timeout) : esegue un'oper azione di Ping sul ser ver IP (Ip pu esser e un Ip valido o un Dns). Il ping ser ve per contr ollar e se il ser ver sia attivo: viene inviato un pacchetto di bytes di contr ollo; se r isponde, significa che online e funzionante, altr imenti ci sono dei pr oblemi. Si pu specificar e opzionalmente un Timeout di millisecondi tr ascor so il quale non si pr osegua oltr e nell'oper azione di Ping UploadFile : car ica il file su un ser ver . I par ametr i sono gli stessi di Dow nloadFile Scr een AltScr een : r estituisce un ar r ay di tutti i dislapy del sistema BitsPer Pix el : il numer o di bit associati ad un unico pix el in memor ia Bounds : r estituisce un oggetto Rectangle contenente le dimensioni dello scher mo DeviceName : il nome del device associato allo scher mo Pr imar yScr een : lo scher mo pr imar io Wor kingAr ea : l'ar ea di lavor o

My .User
Espone poche pr opr iet, la pi impor tante delle quali Name, che r estituisce il nome dell'utente attualmente loggato. Ci sono altr e funzioni come IsInRole che per mettono anche di definir e il r uolo dell'utente (ad esempio Amministr ator e o Pr opr ietar io, oppur e un diver so stato se appar tiene a un dato gr uppo di computer ).

My .Resourc es
un contenitor e in gr ado di immagazzinar e qualsiasi file o r isor sa: immagini, video, suoni, file di dati, di testo, str inghe e altr o ancor a. Tutto quello che viene immesso nell'applicazione attr aver so questo w r apper inglobato

nell'assembly finale, mentr e dur ante lo sviluppo del softw ar e, tali file vengono tempor aneamente salvati nella car tella Resour ces del pr ogetto. possibile aggiunger e una nuova r isor sa dalle pr opr iet del pr ogetto (Nome pr ogetto->Click col destr o->Pr oper ties), come mostr ato in questa immagine:

Tr amite il men in alto a sinistr a nella finestr a delle r isor se si pu sceglier e quale tipo di dati aggiunger e: la lista al centr o visualizza r isor se per tipo, quindi in una volta sar anno visibili solo str inghe, solo immagini, solo suoni, ecceter a... Il pulsante a fianco, "Add Resour ce" per mette di aggiunger e una r isor sa di quel tipo; le altr e sottovoci pr esenti sono delle scor ciatoie per r isor se usate spesso come str inghe, immagini o file di testo. Una volta aggiunta una r isor sa tr amite il designer , il compilator e r iscr ive automaticamente tutto il codice nascosto di My.Resour ces, r endendo disponibili come pr opr iet tutti i dati immessi. A seconda del tipo specificato, tale pr opr iet sar r estituita in manier a differ ente: Str inghe e file di testo vengono r estituiti come Str ing Le immagini vengono r estituite come System.Dr aw ing.Bitmap I suoi vengono r estituiti come System.IO.UnmanagedMemor yStr eam Le icone vengono r estituite come System.Dr aw ing.Icon I file di altr o tipo vengono r estituiti come ar r ay di bytes

My .Settings
Per mezzo di questo oggetto possibile salvar e le impostazioni dell'applicazione che devono per maner e tr a due sessioni distinte. I settaggi vengono salvati can il suppor to dell'XML e della ser ializzazione in una car tella dal nome chilomentr ico all'inter no della dir ector y dell'utente cor r ente. Il nome stabilito usando le infor mazioni dell'assembly e il suo str ong name. All'avvio, tutti i campi vengono automaticamente impostati dal pr ogr amma, che si pr eoccupa pr ima del car icamento del for m, di r ecuper ar e il file XML e legger ne il contenuto. In questo r isiede la comodit di My.Settings, poich concede al pr ogr ammator e la liber t di dedicar si alla scr ittur a del codice significativo, delegando poi alla macchina l'esecuzione di compiti noiosi quali il car icamento delle impostazioni. Anche in questo caso, si oper a su My.Settings attr aver so una finestr a nella sezione Pr oper ties del pr ogetto. La scher mata semplice e intuitiva, e per mette di cr ear e non solo valor i di tipi semplici come Str ing, Boolean o Double, ma anche tipi complessi, sia value che r efer ence, anche definiti dallo sviluppator e: il r equisito minimo che siano

ser ializzabili (come si vedr in seguito, di default, tutti gli oggetti sono ser ializzabili). Un'applicazione diffusa e molto r ichiesta per la sua semplicit consiste nel poter salvar e valor i associati a contr olli. Ad esempio, si vuole che il font, il testo e il color e di un pulsante vengano conser vati tr a una sessione e l'altr a. In questo caso, ma anche negli altr i, il lavor o da far e non r isulta affatto lungo o complesso. Per pr ima cosa bisogna cr ear e tr e nuovi valor i attr aver so l'inter faccia My.Settings dal pannello di contr ollo delle pr opr iet di pr ogetto: uno di tipo Font, uno Str ing e uno Color . Ecco:

(Si faccia caso alla finestr a delle pr opr iet nell'angolo a destr a: anche da l, come se fosse un nor male contr ollo, si possono modificar e i campi di My.Settings, eventualmente specificando un commento in Descr iption) Lo scope User o Application r iguar da la finalit per cui il campo viene cr eato: in gener e le pr opr iet User sono modificabili, mentr e quelle Application sono ReadOnly. Or a che sono stati cr eati gli oppor tuni valor i, bisogna collegar li alle r ispettive pr opr iet del pulsante (ovviamente dopo aver cr eato anche il pulsante). Selezionato il pulsante, bisogna espander e, nella finestr a delle pr opr iet, la voce "(Application Settings)". Al suo inter o sar pr esente soltanto la voce "Tex t", alla quale si assegner il valor e ButtonTex t, tr amite la piccola finestr a di dialogo che appar ir cliccandoci sopr a. Per le altr e due pr opr iet, For eColor e Font, bisogna cliccar e sul pulsantino coi tr e puntini di sospensione sull'elemento appena sopr a, "(Pr oper tyBinding)": ver r visualizzata una gr iglia completa di tutte le pr opr iet, dando quindi la possibilit di collegar e ciascuna al cor r ispettivo valor e. Risultato:

Or a, cambiando un valor e di My.Settings, cambier anche l'aspetto del contr ollo. Le impostazioni vengono car icate automaticamente all'inizio, ma per salvar e bisogna o r ichiamar e il metodo My.Settings.Save oppur e impostar e a Tr ue la pr opr iet My.Application.SaveMySettingsOnEx t. Per quanto r iguar da gli altr i metodi di questa classe, necessar io annover ar e solo Save, Reload e Reset: dei pr imi due facilmente intuibile la funzione; il ter zo, invece, r eimposta al lor o valor e di default tutti i valor i che vengono dichiar ati con Per sist = Tr ue. Inoltr e, sono pr esenti anchq quattr o utili eventi: SettingsChanging (gener ato pr ima che venga modificata un'impostazione), Pr oper tyChanged (dopo che stato cambiato il valor e di un'impostazione), SettingsLoaded (all'avvio o dopo aver chiamato i metodi Reload o Reset) e SettingsSaving (pr ima che vengano salvate le impostazioni). Bisogna notar e che gli eventi gener ati pr im a che avvenga qualcosa possono anche modificar e il compor tamento dell'azione oppur e anche annullar la, sempr e ispezionando le possibilit offer te dai membr i di e.

My .Forms e My .W ebServic es
Espongono le istanze di default di tutti i For m o di tutti i Web Ser vice definiti nel pr ogetto: il secondo tipo di oggetto non tr attato in questa guida.

G2. Estendere il Namespace My

Come se non bastasse la sua gi str aor dinar ia potenza, il namespace My ulter ior mente estendibile dal pr ogr ammator e, in modo da adattar lo alle specifiche esigenze dell'applicazione. Pr ima di utilizzar e questa funzionalit, tuttavia, sar ebbe oppor tuno valutar e anche le alter native e concluder e se non sia meglio una nor male libr er ia oppur e un file ser ializzato. Ad ogni modo, possibile ampliar e questo namespace gr azie all'ausilio delle classi Par tial: infatti My a tutti gli effetti un nor male namespace, definito in un comune file *.vb, nascosto per agli occhi dello sviluppator e, dato che solo il compilator e lo utilizza. In tale file vengono definiti solamente i membr i inser iti dall'utente e non assolutamente consigliabile modificar lo manualmente. La soluzione pi cor r etta consiste, invece, nel dichiar ar e nel pr ogetto un nuovo namespace, sempr e di nome My, che, gr azie alla keyw or d Par tial usata dal compilator e nella cr eazione automatica, sar inter pr etato come estensione di quello or iginale. Dur ante questo pr ocesso possibile aggiunger e due tipi di oggetti: di pr imo e di secondo livello. Quelli di pr imo livello sono classi definite all'inter no di My con un nome nuovo e ne diventano membr i effettivi, in modo da esser e r esi accessibili con la semplice dicitur a My.[Classe]. In questo ambito esistono anche due differ enti modi di dichiar ar e oggetti di questo tipo. Il pr imo metodo consiste nel cr ear e una nuova classe all'inter no di My: 01. Namespace My 02. Public Class Utilities 03. 'Con questa versione, si utilizza la classe come contenitore 04. 'di metodi o membri statici, rendendola simile a un modulo 05. Public Shared Function Percent(ByVal Num As Int32, _ 06. ByVal Max As Int32) As Single 07. Return (Num * 100) / Max 08. End Function 09. End Class 10. End Namespace Il secondo metodo pr evede la cr eazione di un modulo nascosto entr o il quale figur a una pr opr iet che espone un'istanza della classe, definita altr ove. Infatti, l'infr astr uttur a di My non espone dir ettamente i membr i Application, Settings, ecceter a... ma moduli pubblici nascosti con nomi del tipo MyApplication e MySettings (anteponendo quindi "My" al nome del membr o) nei quali vengono definiti tali oggetti come pr opr iet. Per facilitar e la compr esione, ecco un esempio simile a quello di pr ima: 01. Namespace My 02. 'L'attributo HideModuleName permette di nascondere il 03. 'nome del modulo nella scala gerarchica dell'IntelliSense 04. 'e accedere direttamente a Utilities 05. <DebuggerNonUserCode(), _ 06. Microsoft.VisualBasic.HideModuleName()> _ 07. Public Module MyUtilities 08. Private _Utilities As New Utilities 09. 10. Public ReadOnly Property Utilities() As Utilities 11. Get 12. Return _Utilities 13. End Get 14. End Property 15. End Module 16. End Namespace 17. 18. Public Class Utilities 19. 'Con questa versione, si utilizza la classe come vero oggetto, 20. 'ma i metodi di istanza che non utilizzano altri campi d'istanza 21. 'possono comunque essere utilizzati allo stesso modo dei metodi 22. 'statici, poich l'oggetto gi inizializzato all'interno del 23.

'modulo sopra definito 24. Public Function Percent(ByVal Num As Int32, _ 25. ByVal Max As Int32) As Single 26. Return (Num * 100) / Max 27. End Function 28. End Class Per aggiunger e, invece, oggetti di secondo livello, ossia ger ar chicamente subor dinati a uno qualsiasi dei membr i gi appar tenenti a My, basta inser ir e nel nuovo namespace una classe Par tial Fr iend il cui nome sia for mato dal pr efisso "My" e dal nome del membr o da cui der ivar e la classe in questione. Ad esempio, questo codice espone il dns di un ser ver usato per le connessioni inter net in My.Application: 01. Namespace My 02. 'Aggiunge membri a My.Application 03. Partial Friend Class MyApplication 04. Private _ServerName As String = "http://www.example.com" 05. 06. Public Property ServerName() As String 07. Get 08. Return _ServerName 09. End Get 10. Set(ByVal Value As String) 11. _ServerName = Value 12. End Set 13. End Property End Class 14. 15. End Namespace

G3. IDE: Alcune semplici funzioni da usare sempre

I capitoli nella sezione G non r iguar dano str ettamente il codice o le tecniche da usar e, ma si occupano di descr iver e l'ambiente di sviluppo, in par ticolar e il compilator e Visual Basic Ex pr ess 2005/2008/2010. Ecco uno scr eenshot del mio scher mo con aper to il pr ogetto che uso per scr iver e gli esempi della guida:

Pano r am ia dell'IDE Nel capitolo pr ender in esame quegli str umenti che possono esser e utili nello sveltir e le oper azioni e aiutar e il pr ogr ammator e nella stesur a del codice.

Solution Explorer
Nella finestr a del Solution Ex plor er vengono visualizzati tutti i file che compongono il pr ogetto cor r ente, o, in caso si tr atti di una soluzione, anche tutti i suoi pr ogetti. Da qui possibile acceder e in modo r apido ad ogni r isor sa possibile. Tutti i sor genti (quindi tutti i file con estensione *.vb) r ipor tati sono r aggiungibili mediante un doppio click e, per i for m, con il men contestuale. Sul lato super ior e della finestr a sono visibili cinque pulsanti, nell'or dine: Pr opr iet del pr ogetto: una scor ciatoia che por ta dir ettamente alle pr opr iet di pr ogetto Show All Files : visualizza tutti i file r ealmente esistenti nella car tella, fr a cui le r isor se, i r ifer imenti e i codici di design dei for m. Ad esempio:

Sho w A ll Files Come si pu facilmente notar e dall'immagine, ci sono molte pi cose di quante ce ne se aspettasse:

My Pr oject : sotto questa voce vengono r aggr uppati tutti i sor genti che for mano il namespace My nelle sue classi modificabili. Infatti nella figur a si osser vano Application.myapp (che for ma My.Application), Resour ces.r esx (che for ma My.Resour ces) e Settings.settings (che for ma My.Settings). Il file manifest usa la sintassi XML per comunicar e al sistema oper ativo altr e infor mazioni sottaciute all'utente, quali i per messi concessi, la ver sione minima r ichiesta e altr i dati sulla sicur ezza Refer ences: contiene tutti i r ifer imenti aggiunti bin : questo elemento solo una r ipr oduzione della ver a car tella bin pr esente nella car tella del pr ogetto. Per mezzo di questa si possono veder e i file contenuti in Debug e Relase, ma non niente di pi che un semplice br ow ser obj : contiene file con l'elenco delle r isor se associate a ogni for m o contr ollo. Inoltr e contiene anche un indice di tutti i file inclusi nel pr ogetto e necessar i al suo funzionamento Resour ces : car tella delle r isor se. Bisogna evidenziar e che color ata in giallo. Car telle di questo tipo sono cr eabili anche dal pr ogr ammator e per mezzo del men contestuale [Nome pr ogetto]->Add->New folder e ser vono per or ganizzar e meglio l'applicazione View Code : se il file selezionato un sor gente, visualizza il codice r elativo View Designer : se il file selezionate un for m o un contr ollo, visualizza l'antepr ima di design

Propriet di progetto
Questa sezione per mette di impostar e tutte le specifiche possibili e immaginabili r iguar danti il pr ogetto cor r ente, dallo splash scr een iniziale, agli aggior namenti, alle infor mazioni dell'assembly. Ecco una panor amica di tutte le schede non ancor a esaminate:

Pr o pr iet - A pplicatio n La scheda "Application" per mette di selezionar e i compor tamenti dell'applicazione. Ecco una lista dei campi con annessa spiegazione: Assembly name : il nome dell'assembly gener ato. Se il pr ogetto una libr er ia di classi, si r ifer isce al nome che la

DLL di output dovr aver e, altr imenti si r ifer isce al nome dell'eseguibile (sia nella car tella Release che in quella Debug) Root namespace : il namespace del pr ogetto. Di default, composto pr endendo il nome iniziale dato al pr ogetto in fase di salvataggio e "nor malizzandolo", ossia eliminando tutti quei car atter i che non possono compor r e un identificator e Visual Basic (ossia una var iabile). Pu esser e utile cambiar lo anche solo per un fatto estetico: una volta modificata la tex tbox , il compilator e esegue una r icer ca nei sor genti e sostituisce tutte le occor r enze, senza quindi dar e alcun fastidio dur ante la sostituzione del nome Application type : il tipo di assembly pr odotto in output. Si possono sceglier e tr e valor i: Window s Application, Class Libr ar y e Console Application Icon : l'icona dell'eseguibile pr odotto (se il pr ogetto di tipo Class Libr ar y, non si potr cambiar e l'icona associata alla DLL). valido qualsiasi file *.ico compatibile, pr efer ibilmente di dimensioni r idotte, come 32x 32 o 48x 48 Enable XP Visual Style : abilita la visualizzazione in stile Window s XP su sistemi oper ativi compatibili Save My.Settings on shutdow n : deter mina se salvar e le impostazioni definite in My.Settings quando l'applicazione in chiusur a. Equivale a impostar e My.Application.SaveMySettingsOnEx it Shutdow n mode : indica quando ter minar e l'applicazione, se alla chiusur a del pr imo for m (ossia del for m pr incipale), oppur e alla chiusur a dell'ultimo for m visibile, indipendentemenete dal suo r uolo Splash scr een : imposta lo splash scr een dell'applicazione. valido un qualsiasi for m definito nel pr ogetto: esso viene visualizzato pr ima di ogni altr a cosa all'avvio dell'applicazione per alcuni secondi

Pr o pr iet - Co m pile La finestr a compile per mette di impostar e alcuni settaggi r iguar danti la compilazione. La pr ima casella di testo in alto specifica dove salvar e l'assembly compilato. Le tr e combobox appena sotto, invece, impostano le opzioni di compilazione, gi esposte nel capitolo r elativo. Il DataGr idView centr ale, invece, fa stabiliar e all'utente come compor tar si in alcuni casi par ticolar i: definisce se una cer ta situazione debba esser e segnalata oppur e no, e se s in for ma di er r or e o di semplice w ar ning. Queste azioni sono, nell'or dine, dall'alto in basso:

Conver sioni implicite fr a tipi. Ad esempio: 1. Dim I As Int32 = 34.78 Late binding, ossia r ichiamar e membr i da una var iabile Object. Ad esempio: 1. Private Click(ByVal sender As Object, ByVal e As EventArgs) 2. 'sender un generico Object: non si sicuri che 3. 'a runtime possa risultare proprio di tipo Control sender.Enabled = False 4. 5. End Sub Dichiar azioni di var iabili senza la specificazione del tipo (nelle ver sioni 2005 e anter ior i, si assume che siano Object). Ad esempio: 1. Dim S La var iabile viene usate pr ima che gli sia stato assegnato un valor e. Vale sia nel caso di valor i Value che Refer ence: 01. 02. 03. 04. 05. 06. 07. 08. 09. 10. 11. Dim I As Int16 'I non inizializzata: sar 0 e produrr un errore Dim K As Int16 = 10 / I '... Dim Str As StringBuilder 'L'oggetto Str non inizializzato, poich manca il suo 'costruttore nella dichiarazione. Questo codice produrr un 'errore NullReferenceException a runtime Str.Append("Ciao")

Il compilator e r iesce ad individuar e anche i casi in cui una str uttur a di contr ollo impedisce a un valor e di esser e sempr e assegnato con cer tezza. Il seguente codice pr oduce un w ar ning in compilazione e potr ebbe pr odur r e un er r or e a r untime: 1. 2. 3. 4. 5. 6. 7. 8. Dim I As Int16 If K >= 26 Then I = 2 * K + 1 End If 'Se K < 26, I varr 0 K /= I

Dichiar azione di funzioni od oper ator e che non r estituiscono alcun tipo. Ad esempio: 1. 2. 3. 4. 5. 6. 7. 8. Public Function GetName(ByVal Text As String) '... End Function 'Oppure Public Shared Operator +(ByVal P1 As Person, ByVal P2 As Person) '... End Operator

Var iabile locale non utilizzata Si accede a un campo statico attr aver so un'istanza di classe anzich attr aver so la classe stessa. Ad esempio: 01. 02. 03. 04. 05. 06. 07. Dim S, K As String '... 'IsNullOrEmpty una funzione statica del tipo String. 'Poich pur sempre un membro pubblico, diventa

08. 09. 10. 11. 12. 13. 14.

'accessibile anche dai singoli oggetti. Ma, come gi 'ripetuto molte volte, non costituisce un membro appartenente 'all'istanza, ma alla classe in s. Qualsiasi valore di S, 'pertanto, verr trascurato. La versione corretta 'If String.IsNullOrEmpty(K) Then ... If S.IsNullOrEmpty(K) Then '... End If

Il compilator e ha r ilevato una chiamata r icor siva, ossia che fa r ifer imento a se stessa. Er r or i del gener e potr ebber o por tar e a loop e cr ash del pr ogr amma dur ante l'esecuzione. Ecco un esempio: 01. Private _Name As String 02. Public ReadOnly Property Name() As String 03. Get 'Questo statement deve ottenere il valore della 04. 'propriet Name, della quale si sta definendo ora 05. 'il corpo. In questo modo si richieder il blocco 06. 07. 'Get, che a sua volta richiamer se stesso un 08. 'numero infinito di volte Return Name 09. End Get 10. 11. End Property Blocchi Catch uguali Le due checkbox in fondo, invece, per mettono o di soppr imer e ogni w ar ning, oppur e di tr attar e ogni w ar ning come se fosse un er r or e. La scheda Debug ha pochissime funzionalit. La pr ima tex tbox per mette di inser ir e dei par ametr i da r iga di comando dir ettamente all'avvio per testar e l'applicazione, mentr e la seconda stabilisce la dir ector y in cui lavor a il pr ogr amma.

Pr o pr iet - Refer ences La scher mata dei r ifer imenti consente al pr ogr ammator e di impor tar e velocementi componenti COM o assembly .NET. La listview centr ale visualizza tutti i r ifer imenti attualmente pr esenti nel pr ogetto, mentr e la CheckedListBox in basso una scor ciatoia per aggiunger e velocemente assembly .NET pr ovenienti dalla Global Assembly Cache. Il pulsante

Refr ence Paths aggiunge un'inter a car tella, dalla quale si inser ir anno tutti i componenti ivi contenuti. Il pulsante di fianco, invece, "Unused r efr ences", tr ova nei sor genti i r ifer imenti inutilizzati e per mette di r imuover li. La scheda Secur ity stabilisce se il pr ogr amma debba esser e consider ato una Full Tr ust Application ("Applicazione completamente affidabile") o una Par tial Tr ust Application ("Application non completamente affidabile"): la pr ima gar antisce una sicur ezza massima, non inter fer isce in meccanismi delicati e per ci ottiene dal sistema oper ativo tutti i per messi necessar i a oper ar e sulla macchina; la seconda agisce su par ti sensibili del sistema e potr ebbe causar e er r or i, per ci Window s non gli concede tutti i per messi. La DataGr idView appena sotto definisce quali per messi siano r ichiesti e quali no, mentr e il pulsante Pr oper ties apr e una finestr a in cui si possono definir e i per messi R-W delle var iabili d'ambiente.

Pr o pr iet - Publish La scheda Publish consente di pubblicar e il pr ogr amma come setup eseguibile: questo viene costr uito dal compilator e sulla base delle infor mazioni immesse. Una volta avviato, installa il pr ogr amma in una locazione sconosciuta e non modifica il men Installazione Applicazioni. In questo modo l'utente non pu disinstallar lo e se si ver ificano pr oblemi, non pu r icor r er e a nessun suppor to poich i file necessar i sono nascosti chiss dove. Per tutti i motivi appena esposti, sconsiglio vivamente la cr eazione del setup con l'editor pr edefinito: maggior i infor mazioni sar anno for nite nel capitolo sui pacchetti d'installazione. Ecco una lista dei campi: Publishing location : per cor so della car tella dove depositar e il setup Installation URL : per cor so della car tella dove installar e il softw ar e Application files : apr e una finestr a di dialogo che per mette di selezionar e quali file includer e nel setup Pr er equisites : seleziona i pr er equisiti ed eventualmente la locazione o il sito da dove scar icar li se non pr esenti sul computer dell'utente Updates : imposta le modalit di contr ollo degli aggior namenti, il per iodo di tempo ogni quanto eseguir e un contr ollo per ver ificar ne l'esistenza, la ver sione minima r ichiesta all'aggior namento e, ovviamente, l'indir izzo dove contr ollar e Options : opzioni dell'assembly Publish ver sion : ver sione di pubblicazione del pr ogr amma

Publish w izar d : uno w izar d guida il pr ogr ammator e attar ver so la cr eazione del setup. Vengono pr oposte le stesse opzioni che possibile impostar e nella scheda Publish Now : cr ea immediatamente il setup

Objec t Brow ser


L'Object br ow ser una ver sione molto (ma molto!) pi sofisticata e dettagliata del nostr o Br ow ser per Assembly, scr itto nell'ultimo capitolo sulla Reflection. Per mette di navigar e tr a gli assembly del pr ogetto, sia quelli cr eati dall'utente (contr olli utente, for m, moduli, classi, namespace), sia quelli gener ati dal compilator e (namespace My e r elative sottoclassi) sia quelli depositati nella GAC (ad esempio System.Data o System.Xml). Visualizza tutte le pr opr iet, i metodi, i campi, gli enumer ator i, le str uttur e, le inter facce, le classi, i costr uttor i, i distr uttor i e gli oper ator i cr eati, ossia ogni possibile infor mazione su un dato tipo. Il r iquadr o in basso mostr a anche la dichiar azione del membr o e, se si tr atta di una pr ocedur a o di una funzione, anche la signatur e, la descr izione del funzionamento e di ogni singolo par ametr o.

Object Br o w ser Dopo aver navigato un p per i tipi, si sar notato che queste infor mazioni non sono disponibili per gli oggetti cr eati dal pr ogr ammator e, ma la spiegazione semplice: non si cr eata una do cum entazio ne adatta per quei membr i. Consultar e il capitolo r elativo per maggior i dettagli.Si pu osser var e che: I membr i statici non vengono r ipor tati, tr anne i moduli Tutta la documantazione viene tr asfer ita cor r ettamente nel r iquadr o della descr izione I tipi vengono esposti fuor i dalla classe con l'oper ator e punto (ad esempio Documentazione.Str uttur a) Str uttur e, delegate, inter facce ed enumer ator i, poich der ivati dalle classi basi System.Str uctur e, System.Delegate e System.Enumer ator , espongono molti metodi addizionali I membr i pr ivati hanno una piccola icona a for ma di lucchetto in basso a destr a I membr i fr iend hanno una piccola icona a for ma di r ombo azzur r o in basso a sinstr a

IntelliSense
C' un altr o modo per r aggiunger e velocemente la documentazione di un membr o, ed usar e l'IntelliSense. Questa tecnologia legge quello che il pr ogr ammator e ha digitato nel codice e, in cor r ispondenza di cer ti simboli (come lo spazio o il punto), for nisce dei sugger imenti tr amite un men a cascata. Ecco un esempio:

IntelliSense in azio ne! Oltr e a visualizzar e infor mazioni sulla classe selezionata, per mette di scor r er e velocemente tutti i membr i con le fr ecce dir ezionali: per attivar e l'autocompletamento, basta digitar e il car atter e punto (se ci si deve addentr ar e pi all'inter no nella ger ar chia), spazio (se si deve continuar e l'espr essione) o invio (se una volta completato, si ter mina l'espr essione). Cos facendo, dichiar o un nuovo Str ingBuilder digitando questi tasti: 1. dim Str as new sys.te.s[Invio] E ottengo lo stesso r isultato in un tempo notevolmente infer ior e. Usando questa tecnica si possono indagar e anche i par ametr i dei metodi apr endo le par entesi:

IntelliSense in azio ne! Oltr e ad ottener e la descr izione del par ametr o e il tipo r ichiesto, possibile veder e anche tutte le ver sioni modificate con over loading scor r endole con le fr eccette dir ezioni indicate (anche da tastier a). Un altr o pr egio dell'IntelliSense consiste nel poter r ilevar e la dichiar azione di una var iabile quando il mouse ci passa sopr a.

Altri strumenti utili


In questo par agr afetto cito due str umenti di minor impor tanza, ma molto utili. Il pr imo il Code Snippet, che per mette di inser ir e all'inter no del sor gente fr ammenti di codice gi scr itti. Per inser ir e un fr ammento esistente, basta selezionar e "Inser t Snippet" dal men contestuale dell'editor di codice. Dopodich appar ir una lista contenente delle car telle con nomi in inglese: sono le categor ie di codice disponibili. Ad esempio, per cr ear e in modo veloce una pr opr iet, si seleziona questo:

Co de Snippet Il compilator e gener er un codice in cui sono evidenziate delle par ti in ver de: se se ne modifica una, quelle cor r ispondenti vengono modificate a lor o volta con lo stesso valor e. L'altr o str umento un tool di r inominazione. Se si clicca col destr o su un indentificator e e si sceglie "Rename", lo si pu r inominar e e il compilator e r inomina anche tutti i suoi r ifer imenti altr ove.

G4. Guida all'uso di IntelliSense

Data la mole di gente che non usa o non sa usar e IntelliSense, ho deciso di scr iver e questo capitolo in pi per spiegar e l'utilizzo di questa funzione vitale del compilator e.

Attivare IntelliSense
Per attivar e IntelliSense in Visual Studio o Visual Basic Ex pr ess, sotto la voce Tools (Str umenti), sceglier e Options (opzioni), quindi selezionar e ed espander e la voce Tex t Editor e apr ir e il pannello Basic, come mostr ato in figur a:

Il pannello Basic, ne lle o pzio ni Assicur atevi che le voci "Auto list member s" e "Par ameter infor mation" siano spuntate, quindi pr emete OK. Or a, ogniqualvolta dovr ete sceglier e un tipo, acceder e ai membr i di una classe, passar e par ametr i ad un metodo e molto altr o, IntelliSense vi dar sugger imenti mostr ando una lista di tutte le possibili scelte che potete far e (for nendo anche una descr izione di ogni possibilit). Alcuni esempi:

Scelta del tipo di una v ar iabile

A ccesso ai m em br i di un o g g etto

Passag g io di par am e tr i a un m eto do

A sseg nazio ne di v alo r i a un enum er ato r e Or a che avete visto le potenzialit di IntelliSense sper o che inizier ete ad utilizzar lo, o almeno, pr oseguiate nella lettur a.

IntelliSense distingue i membri di c lasse


Tutte le icone che IntelliSense usa per contr assegnar e gli oggetti della lista dei sugger imenti non sono assolutamente messe a caso. Hanno un significato ben pr eciso: Campo (var iabile)

Costante o Valor e di enumer ator e Metodo Pr opr iet Str uttur a Enumer ator e Inter faccia Classe Namespace Modulo Delegato Evento Oper ator e Tipo base (Str ing, Byte, Shor t, Integer , Long, Single, Double, Boolean, Date, Object, SByte e Char ). In alcuni casi, ad esempio nell'autocompletamente degli statement "Ex it" (come Ex it For , Ex it Do, Ex it Sub, ecceter a...), indica lo statement stesso. Nell'autocompletamento di VB2008, indica anche ogni par ola chiave Inoltr e, sempr e tr amite le icone, possibile infer ir e il livello di accesso di cui ogni membr o dotato. Infatti, l'icona di ognuna delle categor ie sopr a citate pu esser e modificata con l'aggiunta di un piccolo simbolo in basso a sinistr a: Il piccolo r ombo azzur r o indica che il membr o Fr iend La piccola chiave gialla indica che il membr o Pr otected Il piccolo lucchetto gr igio indica che il membr o Pr ivate Se il membr o Public, non avr nessun simbolo aggiuntivo. Invece, per i membr i Pr otected Fr iend si assume come simbolo lo stesso di quelli Pr otected.

IntelliSense suggerisc e i parametri di ogni metodo


Quando vi stato sugger ito di usar e un metodo, non occor r e che vi facciate dir e anche quali devono esser e i suoi par ametr i, poich IntelliSense ve li sugger isce automaticamente, cor r edandoli, qualor a sia possibile, di una descr izione. Ad esempio, questo codice: Dim B As New Bitmap() r estituir un er r or e: "Over load r esolution failed because no accessible New accepts this number of ar guments", ossia stiamo tentando di usar e un costr uttor e senza par ametr i quando la classe in questione non espone nessun New senza par ametr i. Per ci dobbiamo obbligator iamente passar e un qualche ar gomento al costr uttor e, ma non sapiamo quale. Basta apr ir e la par entesi dopo Bitmap per veder e la lista di sugger imenti for nita da IntelliSense:

come si vede ci sono ben 12 over load, ossia 12 ver sioni dello stesso metodo e ognuna di questa ha una sua descr izione. Alla fine, sicur amente tr over emmo quello che stiamo cer cando, ad esempio l'over load 12, che quello pi semplice e che ci consente di inizializzar e una nuova bitmap con date dimensioni, oppur e il 5, con un solo par ametr o di tipo str inga che contiene il nome del file. Insomma, tutto quello che documentato su msdn lo potete tr ovar e anche usando l'IntelliSense.

IntelliSense permette di trovare c lassi e funzioni nuove


Una funzione meno canonica, ma sicur amente altr ettanto utile, di IntelliSense sta nel fatto che, gr azie alla lista di

sugger imenti for niti nella deter minazione del tipo di una var iabile, potete esplor ar e TUTTE le classi ESISTENTI sul vostr o computer (ovviamente quelle impor tante nell'applicazione cor r ente). Solo guar dandone il nome potete cer car e di capir e la lor o funzione e, con qualche r icer ca, tr ovar e quello che vi inter essa. Ad esempio, gir ovaghiamo per System.Net... tr oviamo un inter essante namespace Netw or kInfor mation e vi scor giamo numer ose classi che iniziano con "Ping": abbiamo tr ovato il luogo giusto per eseguir e un ping da codice. Pr ovando a inizializzar e un nuovo oggetto Net.Netw or kInfor mation.Ping non si ottengono er r or i, quindi se ne possono esplor ar e le funzioni. Salta subito all'occhio la funzione Send, gr azie alla quale possiamo ottener e molte infor mazioni utili sul viaggio dei nostr i pacchetti in r ete. Non c'er a neanche bisogno di cer car e su Inter net!

IntelliSense vostro amic o


Quindi non fate a meno di usar lo e impar ate a sfr uttar ne tutte le potenzialit, sempr e e comunque!

G5. Debugging

Nei capitoli pr ecedenti ho esposto alcuni str umenti da usar e dur ante la scr ittur a del sor gente, ma una volta scr itto, bisogna testar lo ed elimiar e i bug, gli er r or i del pr ogr amma. Questa oper azione si dice debug g ing (dall'inglese de-bug).

Finestra degli errori


Il componente pi impor tante che si ha a disposizione la finestr a degli er r or i, nella quale vengono visualizzati tutti gli er r or i, gli w ar ning e i messaggi. Pr ima di pr oseguir e bisogna far e una distinzione tr a le tr e tipologie di notifiche esistenti: Er r or i : sono er r or i tutte le espr essioni incomplete, l'incongr uenza dei tipi dati con quelli r ichiesti, la mancanza di identificator i, la scr ittur a er r ata di un'istr uzione, ecceter a... Gli er r or i non per mettono all'applicazione di cor r er e in modo sicur o e por tano nel 100% dei casi a un cr ash del pr ogr amma. Il compilator e r iesce ad individuar e in modo semplice tutti quelli basati sulla sintassi, poich si tr atta semplicemente di confr ontar e schemi pr edefiniti con str uttur e date. Tutti gli er r or i vengono sottolineati in viola War ning : sono delle "avver tenze", per cor si di esecuzione che potr ebber o condur r e a un er r or e o a un loop, o semplicemente segnalazioni di metodi obsoleti o di var iabili inutilizzate. Scovar e questo tipo di aber r azioni nel codice meno semplice e pr esuppone il cer car e di consider e un'evenienza e capir e come il codice fluir dur ante l'esecuzione. Il compilator e pu individuar e ad esempio che in una funzione, non tutti i per cor si di codice conducono a un r isultato, oppur e che un metodo r ichiama se stesso in un loop r icor sivo. Tutti gli w ar ning vengono sottolineati in ver de Messages : semplici messaggi del compilator e. Pr aticamente sempr e assenti Si pu abilitar e/disabilitar e la visualizzazione di er r or i, w ar ning o messaggi cliccando sul nome. Nella lista possibile r aggiunger e con un click su un elemento il punto del codice che ha gener ato l'er r or e.

Breakpoint
I br eakpoint sono punti in cui il pr ogr amma si fer ma, si mette in "pausa", mantenendo per i valor i di tutte le var iabili, per consentir e al pr ogr ammator e di studiar e cosa sta avvenendo all'inter no dell'applicazione. Questo per mette di scovar e molti tipi di er r or i, sia di valor e, sia di logica. Per attivar e un br eakpoint, basta cliccar e col mouse sul mar gine sinistr o dell'editor di codice, nella par te gr igia. La r iga selezionata ver r evidenziata e sar posto un pallino r osso di fianco. Quando il codice ar r iva a quel punto, si fer ma pr ima di eseguir e la r iga selezionata e va in pausa, por tando in pr imo piano il sor gente in questione ed evidenziandolo in giallo. Una volta giunti a questo punto, si pu contr ollar e il valor e di ogni var iabile semplicemente posizionandovi sopr a il mouse per alcuni decimi di secondo. Ecco come potr ebbe appar ir e una scher mata in pausa:

In questo modo possibile analizzar e l'or igine di ogni pr oblema o compor tamento str ano. Se i valor i delle var iabili sono di tipo str inga e sono anche piuttosto lunghi possibile, con un doppio click sulla lente d'ingr andimento, visualizzar li in una finestr a separ ata. Il compilator e pr edispone anche due modalit di visualizzazione alter native: XML e HTML. Entr ambe sono attivabili dal men a discesa accessibile cliccando sulla fr eccetta in gi vicino alla lente (al fianco di ogni str inga).

Inoltr e, c' un modo per aggiunger e br eakpoint da codice. Consiste nell'utilizzar e la keyw or d Stop. Ad esempio: 1. '... 2. If I = 50 Then 3. Stop 4. End If

Finestra W atc h
Quando l'applicazione in pausa, in basso a destr a (usualmente), si apr e una finestr a che contiene i valor i delle var iabili in gioco. divisa in tr e schede: la pr ima, Auto, contiene le var iabili pr esenti sul br eakpoint e quelle locali; la seconda, Local, contiene solo le var iabili locali; la ter za, Watch, contiene le var iabili il cui valor e stato for zatamente fatto analizzar e dal pr ogr ammator e. Per aggiunger e una var iabile alla lista Watch bisogna tr ovar si pr ima di tutto in modalit pausa, quindi si clicca con il pulsante destr o del mouse sulla var iabile in questione e si sceglie "Add w atch". Da questo momento in poi, ogni cambiamento della var iabile ver r r egistr ato e monitor ato, anche in pezzi di codice al di fuor i del blocco in cui essa si tr ova. C' anche un altr o modo per ottener e dei valor i pr ima di un br eakpoint, ossia la finestr a Debug.

Finestra Debug
La finestr a Debug anche nota come Immediate Window ed visibile solo in pausa, selezionando l'oppor tuna scheda nell'angolo in basso a destr a. Al suo inter no ci si pu scr iver e di tutto, qualsiasi infor mazione utile al debugging. Infatti si usa l'oggetto singleton Debug come se fosse un oggetto Console, e l'output viene scr itto, appunto, sulla finestr a. Ad esempio: 01. Module Module1 02. Sub Main() 03. Dim P As New Person("Pinco", "Pallino", New Date(2008, 1, 1)) 04. '... 05. Debug.WriteLine("Nome completo: " & P.CompleteName) 06. 07. 'Si possono scrivere messaggi solo se vige una certa condizione 08. Debug.WriteLineIf(P.BirthDay > New Date(2007, 9, 27), _ 09. "Data di nascita posteriore al 27/9/2007") 10. 11. If P IsNot Nothing Then 12. Stop 13. End If 14. 15. Console.ReadKey() 16. End Sub 17. End Module

G6. Documentare il sorgente

Bene, or a che avete pensato, scr itto e testato la vostr a applicazione siete pr onti per lanciar la sul mer cato... o quasi. Nel caso il pr ogetto che avete completato sia pensato per poter esser e utilizzato da altr i pr ogr ammator i, bene (anzi, necessar io) documentar e il codice che avete scr itto. In questa sezione ho intr odotto i pr imi tools per il debugging, ivi compr eso l'IntelliSense. Esso ci per mette di veder e la descr izione di ogni entit, e sar ebbe ver amente comodo se anche le nostr e classi e i nostr i metodi avesser o una lor o descr izione. Per far e ci, bisogna documentar e il codice: in .NET, la documentazione si attua mediante un ver o e pr opr io str umento sintattico basato su XML. Per documentar e una qualsiasi entit la si fa pr eceder e da uno speciale attr ibuto posto dopo tr e apici.

Tag di doc umentazione


Ecco una lista dei pr incipali attr ibuti/tag di documentazione: summar y : una descr izione del membr o e della sua funzione. Ad esempio: 1. 2. 3. 4. 5. 6. ''' ''' ''' Sub <summary> Descrizione del metodo </summary> DoSomething() '... End Sub

ex ample : un esempio di come utilizzar e il membr o in questione. Pu contener e anche dei tag <code>, che visualizzano il testo compr eso come un codice sor gente ex ception : contiene infor mazioni r iguar do al ver ificar si di un'eccezione e magar i anche qualche consiglio su come r isolver e l'er r or e. Poich le eccezioni var iano, accetta un par ametr o di nome cr ef che espone il nome dell'eccezione. Ad esempio: 1. 2. 3. 4. 5. 6. 7. ''' <exception cref="IndexOutOfRangeException"> ''' Si specificato un valore di Iterazions ''' troppo elevato. ''' </exception> Public Sub TransformName(ByVal Name As String, ByVal Iterations As Byte) '... End Sub

Ovviamente, per il par ametr o cr ef, il compilator e offr e l'aiuto dell'IntelliSense nel completamento automatico par am : descr ive cosa debba esser e passato come par ametr o. Dato che un metodo pu aver e pi par ametr i, bisogna indicar e un par ametr o name per discer ner e gli ar gomenti. Ripr endendo l'esempio di pr ima: 1. 2. 3. 4. 5. 6. ''' <param name="Name">Un nome qualsiasi, non nullo.</param> ''' <param name="Iterations">Il numero di volte che ''' la funzione di modifica viene applicata al nome.</param> Public Sub TransformName(ByVal Name As String, ByVal Iterations As Byte) End Sub

typepar am : come par am, ma usato per descr iver e i par ametr i gener ics aper ti: 1. 2. 3. 4. 5. 6. ''' ''' ''' ''' Sub <summary> Fa qualcosa... </summary> <typeparam name="T">Un qualsiasi tipo reference.</typeparam> DoSomething(Of T As Class)()

7. End Sub r emar ks : altr i dettagli utili sul membr o r etur ns : descr izione dell'oggetto r estituito (funzioni/pr opr iet) value : descr izione dell'oggetto r estituito (solo pr opr iet) see, seealso : indicano un r ifer imento ad un altr a entit collegata logicamente a questa (un classico "vedi anche...") Questo sor gente mostr a l'uso dei tag di documentazione applicato ad ogni tipo di membr o possibile: 001. 002. 003. 004. 005. 006. 007. 008. 009. 010. 011. 012. 013. 014. 015. 016. 017. 018. 019. 020. 021. 022. 023. 024. 025. 026. 027. 028. 029. 030. 031. 032. 033. 034. 035. 036. 037. 038. 039. 040. 041. 042. 043. 044. 045. 046. 047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. ''' <summary> ''' Rappresenta un esempio di tutti i tag di documentazione, ''' applicati ad ogni tipo di membro. ''' </summary> ''' <remarks>Servir anche per mostrare le varie ''' icone nell'Object Browser</remarks> Public Class Documentazione ''' <summary> ''' Espone lo scheletro di una classe. ''' </summary> Friend Interface Interfaccia 'Lascio vuoto, poich i membri di un'interfaccia 'sono pur sempre uguali a quelli di una classe, di 'cui sto scrivendo esempi. End Interface ''' <summary> ''' Espone alcune costanti numeriche sotto la forma di ''' identificatori che si ricordano pi facilmente. ''' </summary> ''' <remarks>Anche i singoli valori possono avere ''' dei tag proprio come ogni altro membro.</remarks> Private Enum Enumeratore ''' <summary> ''' Il primo valore dell'enumeratore. Vale 1. ''' </summary> Primo = 1 ''' <summary> ''' Il secondo valore dell'enumeratore. Vale 2. ''' </summary> Secondo ''' <summary> ''' Il terzo valore dell'enumeratore. Vale 3. ''' </summary> Terzo End Enum ''' <summary> ''' Raggruppa al suo interno pi valori di tipo base. ''' </summary> Friend Structure Struttura Dim A, B, C As Int16 End Structure ''' <summary> ''' Rappresenta un puntatore a metodo in modo sicuro. ''' </summary> ''' <param name="A">Un valore interno positivo, compreso tra 0 ''' e 255. Costituisce la trasparenza del messaggio.</param> ''' <param name="B">Un messaggio in forma di stringa.</param> Public Delegate Sub Delegato(ByVal A As Int16, ByVal B As String) ''' <summary> ''' Rappresenta un cambiamento di stato dell'oggetto. ''' </summary> Friend Event Evento As EventHandler ''' <summary>

060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099. 100. 101. 102. 103. 104. 105. 106.

''' Una qualsiasi data. ''' </summary> Friend Shared VariabileStatica As Date ''' <summary> ''' Rappresenta un valore booleano, vero o falso. ''' </summary> Public VariabileIstanza As Boolean ''' <summary> ''' Media l'interazione tra un campo privato e il programmatore. ''' </summary> ''' <value>Un valore che rappresenta VariabileIstanza.</value> ''' <returns>Restituisce un valore Booleano.</returns> Public Property Propriet() As Boolean Get Return Me.VariabileIstanza End Get Set(ByVal Value As Boolean) Me.VariabileIstanza = Value End Set End Property ''' <summary> ''' Esegue un certo insieme di istruzioni. ''' </summary> ''' <param name="C">Il delegate da richiamare alla fine ''' del processo.</param> Public Sub Procedura(ByVal C As Delegato) End Sub ''' <summary> ''' Esegue un certo insieme di istruzioni, o manipola ''' un valore e quindi restituisce un risultato. ''' </summary> ''' <param name="I">Un numero intero qualsiasi.</param> ''' <returns>Restituisce l'antireciproco del numero dato.</returns> Public Function Funzione(ByVal I As Int16) As Single Return -(1 / I) End Function End Class ''' <summary> ''' Un semplice modulo, ossia una classe statica. ''' </summary> Module Modulo End Module

E viene visualizzato cos:

G7. Costruire un pacchetto di installazione

Questo capitolo non spiega come compilar e un setup, ma come cr ear ne uno tr amite un pr ogr amma molto buono scr itto apposta per questo. Si chiama Inno setup ed scar icabile da questo sito .

Creazione di un setup tramite w izard


Inno Setup non un pr ogr amma comunemente inteso, ma un compilator e, pr opr io come Visual Basic Ex pr ess. Nella fattispecie, compila e pr oduce eseguibili di scr ipt che l'utente scr ive: quindi ogni dato va immesso con l'editor di testo. Dato che pu r isultar e molto lungo, questo pr ocedimento viene in par te velocizzato dal Wizar d, un'applicativo con il compito di guidar e l'utente attr aver so un per cor so pr edefinito che r ichiede ad ogni finestr a di aggiunger e altr e infor mazioni. Per attivar e il w izar d, cliccar e su File->New dopo aver aper to il pr ogr amma (o pr emer e CTRL+N):

Fig ur a 1 Cliccar e Nex t per pr oseguir e.

Fig ur a 2 Nella seconda finestr a bisogna specificar e: Il nome dell'applicazione Il nome dell'applicazione, includendo anche l'indicator e di ver sione La societ o la community che pubblica il softw ar e Il sito di r ifer imento per quel softw ar e (ver r inser ito nel men di Installazione Applicazioni di Window s)

Fig ur a 3 Nella ter za finestr a ci sono specifiche r iguar danti la locazione d'installazione: La combobox iniziale deter mina se l'applicazione sar installata nella nor male car tella Pr ogr ammi oppur e in un altr o per cor so. Se si sceglie la seconda opzione ("Custom"), ver r sbloccata la tex tbox sottostante nella quale inser ir e il per cor so adatto La tex tbox centr ale indica il nome della car tella nella quale il pr ogr amma viene installato. Di solito coincide con il nome dello stesso o al massimo con il nome della societ Le altr e due checkbox indicano se lasciar e all'utente la possibilit di cambiar e la car tella oppur e se il softw ar e non necessita di car tella. In quest'ultimo caso, si tr atta di applicazioni con setup XCOPY, ossia delle quali basta una semplice copia sull'har d disk e niente di pi per l'installazione

Fig ur a 4 Nella quar ta finestr a vengono r ichiesti i file da installar e. La pr ima tex tbox r ichiede il per cor so sull'har d disk dell'utente che sta scr ivendo il setup dell'eseguibile pr incipale, mentr e la listbox sottostante per mette di aggiunger e altr i file o altr e car telle. Se si tr atta di una car tella, non ver r copiato solo il suo contenuto, ma ver r anche cr eata la car tella stessa, compr ensiva di ogni sottodir ector y. Le checkbox in mezzo deter minano se si possa lanciar e il softw ar e alla fine del setup e se non ci sia un eseguibile pr incipale.

Fig ur a 5 Nella quinta finestr a ci sono un p di opzioni r iguar do al men Star t. Esse sono, nell'or dine: Il nome della car tella da cr ear e nel men "Tutti i pr ogr ammi" La possibilit di consentir e all'utente la modifica di tale nome La possibilit di consentir e all'utente l'annullamento della cr eazione di una car tella nel men La possibilit di cr ear e un collegamento al sito inter net del softw ar e La possibilit di cr ear e un collegamento al pr ogr amma di disinstallazione La possibilit di posizionar e un link sul desktop La possibilit di aggiunger e un link nel men Quick Launch

La sesta finestr a r ichiede il file della licenza (un file tx t in cui vengono specificate le condizioni sotto le quali il softw ar e viene r ilasciato) e altr i due file visualizzati pr ima e dopo l'installazione: tutti questi sono opzionali. La settima, invece, per mette di selezionar e le lingue suppor tate.

Fig ur a 6 L'ottava finestr a r ichiede: La car tella ove cr ear e il setup eseguibile completo Il nome dell'eseguibile (di solito Setup) Un'icona per l'eseguibile (sono validi tutti i file *.ico) Una passw or d per acceder e al setup Una volta cliccato Finish, appar e nell'editor di testo una ser ie di istr uzioni scr itte con una sintassi simile a quella dei file INI. Inno Setup compiler tali istr uzioni per poi cr ear e il pacchetto di installazione: subito dopo la fine del w izar d ver r chiesto se eseguir e la compilazione subito. Cliccate s se vi basta, altr imenti continuate a legger e.

Aggiunta di dettagli tramite editor


Non consiglio di scr iver e tutto a mano, ma invece di usar e il w izar d per impostar e le opzioni pi comuni e in seguito modificar e lo scr ipt con l'editor per aggiunger e dei dettagli in pi. Per apr ir e la documentazione, cliccar e Help->Inno Setip Documentation. Qui si tr ovano tutte le istr uzioni possibili e i comandi suppor tati. Basta dar e uno sguar do ai nomi per tr ovar e quello adatto alle esigenze dell'utente. Ecco alcune delle esigenze pi comuni non pr oposte dal w izar d:

A g g iung er e un'im m ag ine A me piacciono molto le finestr e con una buona gr afica, e le possibilit di cr ear e il setup con una mia immagine per sonalizzata mi attir a altr ettanto. Di solito questa immagine il logo della societ oppur e del pr ogr amma stesso. Se ne possono aggiunger e due tipi: medie (visualizzate sul lato sinistr o del setup) e piccole (visualizzate nell'angolo in alto a sinistr a del setup). Ecco un esempio: ; Queste sono le istruzioni generate dal wizard se si ; lasciano tutti i campi esattamente come sono. Per aggiungere ; un'immagine bisogna impostare la propriet WizardImageFile [Setup] AppName=My Program AppVerName=My Program 1.5 AppPublisher=My Company, Inc. AppPublisherURL=http://www.example.com/ AppSupportURL=http://www.example.com/ AppUpdatesURL=http://www.example.com/ DefaultDirName={pf}\My Program DefaultGroupName=My Program OutputBaseFilename=setup Compression=lzma SolidCompression=yes ; Carica l'immagine. Sono supportate solo bitmap, massimo 164x314 WizardImageFile=C:\immagine.bmp ; Se l'immagine pi piccola, ma si vuole che occupi ; lo stesso spazio, si pu attivare l'opzione Stretch WizardImageStretch=yes ; Se invece si vuole mantenere l'immagine delle stess dimensioni, ; ma rimpire i buchi che rimangono con un dato colore, si usa ; WizardImageBackColor, che deve essere ipostato a una tripletta di ; valori esadecimali rappresentanti le varie componenti RGB WizardImageBackColor=$FFFFFF A g g iung er e chiav i di r eg istr o Per aggiunger e delle chiavi, si deve apr ir e una nuova sezione [Registr y], al cui inter no vanno specificate le chiavi e i valor i da aggiunger e. Ad esempio, questo codice aggiunge l'applicazione all'esecuzione automatica: [Registry] ; Aggiunge alla chiave dell'esecuzione automatica una valore stringa ; My Program i cui dati corrispondono al percorso dell'eseguibile. ; {app} una costante che definisce la directory di installazione Root: HKLM; Subkey="SOFTWARE\Microsoft\Windows\CurrentVersione\Run"; ValueType: string; ValueName: "My Program"; ValueData: "{app}\myprg.exe" ; Non andate a capo, io l'ho fatto per questioni di spazio

Potrebbero piacerti anche