Date post: | 23-Jul-2015 |
Category: |
Documents |
Upload: | bianchetto |
View: | 1,120 times |
Download: | 15 times |
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 veloce panoramica sulla programmazioneLa pr ogr ammazione è quella disciplina dell'infor matica che si occupa di idear e, costr uir e e mantener e il softwar 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 softwar 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.
Alcuni termini da conoscereCodice sorg ente o sorg 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
Compilatore: il softwar 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 softwar e usato per l'analisi e la r isoluzione degli er r or i (bugs) all'inter no di un pr ogr amma;
Parole r iservate o keywords: 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 sviluppoL'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 icrosoft; 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 downloads), un pr ogr amma gr atis e
molto buono (tr over ete una r ecensione nella sezione Sofwtar 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
Object Oriented ProgrammingI linguaggi .NET sono orientati 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 paradig ma 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'ereditar ietà, il polimorfismo e l'incapsulamento. 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 ClassiCome 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 og g etti, i quali vengono r appr esentati da classi. Una classe non è altr o che
la rappresentazione - ovv iamente astratta - di qualcosa di concreto, 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
passwor 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
esposte dalla classe: ognuna di esse, inoltr e, è r appr esentata da quello che viene chiamato membro. 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ì:
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 corpo; 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:
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
1.2.3.
Class [NomeClasse]...
End Class
1.2.3.
Class Triangolo...
End Class
dei lati, la tipologia, l'ampiezza degli angoli, ecceter a...
I ModuliNonostante 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 NamespacePossiamo definir e classi e moduli come unità funzionali: 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:
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;
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 TriangoliClass Scaleno'...End Class Class Isoscele'...End Class Class Equilatero'...End Class
End Namespace Namespace Quadrilateri
Namespace ParallelogrammiClass Parallelogramma'...End Class Namespace Rombi
Class Rombo'...End Class Class Quadrato'...End Class
End NamespaceEnd Namespace
End Namespace
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
Framework .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 amewor k si pr esente come str atificata in diver si livelli:
1. Sistema operativoIl Fr amewor k .NET pr esenta una str uttur a str atificata, alla base della quale r isiede il sistema oper ativo, Windows. Più
pr ecisamente, si consider a il sistema oper ativo e l'API (Application Pr ogr amming Inter face) di Windows, che espone
tutti i metodi r esi disponibili al pr ogr ammator e per svolger e un dato compito.
2. Common Language RuntimeUn 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 Managed o Safe ("Gestito" o "Sicur o"), poichè questo str ato del Fr amewor 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 LibraryLo str ato successivo è denominato Base Class Libr ar y (BCL): questa par te contiene tutti i tipi e le classi disponibili nel
Fr amewor 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. XMLSuccessivamente tr oviamo i dati, le r isor se. Per salvar e i dati viene usato quasi sempr e il for mato XML (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 Windows Pr esentation Foundation (WPF), intr odotta nella ver sione 3.5 del Fr amewor 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. Windows Forms e ASP.NETAl livello super ior e tr oviamo ASP.NET e Windows 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 Windows. La costr uzione di una Windows 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 ificationsIl penultimo stadio della str atificazione del Fr amewor 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 amewor k.
7. Linguaggi .NETIn cima alla str uttur a ci sono tutti i linguaggi .net: Vb, C#, J#, ecceter a.
Versioni del FrameworkCon il passar e degli anni, a par tir e dal 2002, Micr osoft ha r ilasciato ver sioni successive del Fr amewor 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 amewor 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 amewor k
VB2003: si basa sulla ver sione 1.1 del Fr amewor k
VB2005: si basa sulla ver sione 2.0 del Fr amewor 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 amewor 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 amewor k
A4. Utilizzo base dell'IDE
IDE? Me lo sono dimenticato a casa...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 softwar e che aiuta il pr ogr ammator e nella stesur a del codice. Il softwar 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 keywor 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 compilazione e linking), 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 compilatore .NETIl 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 amewor 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 amewor 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 ApplicationNei 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 console (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 SubEnd 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 keywor 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 finitoUna 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 softwar 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 variabiliUna 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 identificatore (o, più r ar amente, mnemonico), 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 dichiara 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:
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 keywor ds Dim e As:
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
keywor 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 Short: 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 Long : 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;
Double: decimale da cir ca -1,79e+308 a +1,79e+308, con un inter vallo minimo di cir ca 4,9e-324;
Boolean: 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
01.02.03.04.05.06.07.08.09.
Module Module1Sub Main()
Dim Ciao As Int16Ciao = 78Ciao = Ciao + 2Console.WriteLine(Ciao)Console.Readkey()
End SubEnd Module
1. Dim [nome] As [tipo]
un testo;
Object: r appr esenta un qualsiasi tipo (ma non è un tipo base).
I tipi base vengono detti anche atomici o pr im itiv i, poiché non possono esser e ulter ior mente scomposti. Esistono,
quindi, anche tipi der ivati, 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 keywor 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:
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 amewor 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.
1. Ciao = Ciao + 2
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.17.18.19.20.
Module Module1Sub Main()
'InteriDim Intero, Ese As Int16'DecimaleDim Decimale As Single'BooleanoDim Vero As Boolean'StringaDim Frase As String
Intero = 90Ese = Intero * 2 / 68Intero = Ese - Intero * InteroDecimale = 90.76Decimale = Ese / InteroVero = TrueFrase = "Ciao."'L'operatore "+" tra stringhe concatena due stringhe. Dopo la
Esistono poi degli speciali oper ator i di assegnamento, che velocizzano l'assegnazione di valor i, alcuni sono:
Le fr asi poste dopo un apice (') sono dette commenti 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 costantiAbbiamo 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 reco, è 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:
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 keywor d Cons t al posto di Dim; la seconda consiste nell'assegnazione posta subito dopo la dichiar azione. Infatti,
una costante, per esser e tale, deve contener e qualcosa: per questo motivo è obblig ator 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:
21.22.23.24.25.26.27.28.29.
'prossima istruzione, la variabile Frase conterrà:' "Buon giornoCiao"Frase = "Buon giorno" + "Ciao"'L'operatore "&" può concatenare qualsiasi dato e'restituisce una stringa. Dopo la prossima istruzione, la'variabile Frase conterrà:' "Il valore decimale è: -0,0003705076"Frase = "Il valore decimale è: " & Decimale
End SubEnd Module
01.02.03.04.05.06.07.08.09.10.
Module Module1Sub Main()Dim V, B As Int32
V += B 'Equivale a V = V + BB -= V 'Equivale a B = B - VV *= B 'Equivale a V = V * BB /= V 'Equivale a B = B / V
End SubEnd Module
1. Const [nome] As [tipo] = [valore]
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.17.18.19.20.21.
Module Module1Sub Main()Const Pi As Single = 3.1415926535897932Dim Raggio, Area As Double
'Questa istruzione scrive sul monitor il messaggio posto tra'virgolette nelle parentesiConsole.WriteLine("Inserire il raggio di un cerchio:") 'Questa istruzione leggè un valore immesso dalla tastiera e'lo deposita nella variabile RaggioRaggio = Console.ReadLineArea = Raggio * Raggio * Pi
Console.WriteLine("L'Area è: " & Area) 'Questa istruzione ferma il programma in attesa della pressione'di un pulsanteConsole.ReadKey()
End SubEnd 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:
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 è
Le istruzioniTutti 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 procedure o funzioni, in sostanza sottopr ogr ammi già
scr itti. Pr ocedur e e funzioni possono esser e globalmente indicate con la par ola metodo. I metodi accettano dei
parametr 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:
Anche i metodi ver r anno tr attai successivamente in dettaglio.
1. Const Sqrt2 As Single = Math.Sqrt(2)
1. Const Sqrt2 As Single = 1.4142135
01.02.03.04.05.06.07.08.09.10.11.12.
Module Module1Sub Main()
Dim F As Double
'Questa è una funzione che restituisce la radice quadrata di 56F = Math.Sqrt(56)
'Questa è una procedura che scrive a video un messaggioConsole.WriteLine("La radice di 56 è " & F)Console.ReadKey()
End SubEnd Module
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 ticolo sull'utilizzo della memor ia da par te di un
pr ogr amma.
Differenza tra Classi e OggettiAll'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
og 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 tanziare un oggetto significa cr ear lo.
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 ReferenceOgni cosa nel Fr amew or k è un oggetto e la maggior par te di essi sono tipi r efer ence. Si dicono tipi reference 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 amewor 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 manag 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 arbag e collection ("r accolta dei r ifiuti"). Tuttavia, ciò non avviene subito, poichè il
meccanismo del Fr amewor k fa in modo di avviar e la gar bage collection solo quando è necessar io, quindi quando la
1.2.3.
'New serve per creare fisicamente degli oggetti in memoriaDim O1 As New ObjectDim O2 As New Object
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 log icamente distrutti, 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
finalizzazione non deterministica (il ter mine "finalizzazione" non è casule: veder e il capitolo sui distr uttor i per
maggior i infor mazioni).
NothingNothing è 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.
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 ValueOgni tipo value 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:
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 sono lo stesso
og 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:
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
1.2.
Dim O As New Object 'L'oggetto viene creatoO = Nothing 'L'oggetto viene logicamente distrutto
1.2.3.
Dim O1, O2 As Object'...O1 = O2
1.2.3.
'Scrive a schermo se è vero oppure no che'O1 e O2 sono lo stesso oggettoConsole.WriteLine(O1 Is O2)
effettivamente una copia del valor e. Non è possibile utilizzar e Is con i tipi value poichè Is è definito solo per i
r efer ence.
Boxing e UnboxingConsider iamo il seguente codice:
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 boxing . 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 riferimento
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?").
1.2.3.
Dim I As Int32 = 50Dim O As ObjectO = I
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 costrutto di controllo, 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:
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:
If nidificatiQuando 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 Costrutto If Nidificato.
Questo avviene abbastanza spesso, specie se si ha bisogno di far e contr olli multipli:
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.
Module Module1Sub Main()
Dim Lato As Single
Console.WriteLine("Inserire il lato di un quadrato:")Lato = Console.ReadLine
If Lato < 0 Then 'Se Lato è minore di 0...
Console.WriteLine("Il lato non può avere una misura negativa!")Else 'Altrimenti, se non lo è...
Console.WriteLine("L'area del quadrato è: " & Lato * Lato)End If 'Fine controllo
Console.ReadKey()
End SubEnd Module
1.2.3.4.5.
If [Condizione] Then[istruzioni]
Else[istruzioni alternative]
End If
01.02.03.04.05.
Module Module1Sub Main()
Dim Numero As Int16
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 connettiv i log ici.
I connettivi logic iI 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 contemporaneamente
If A Or B : la condizione r isulta ver ificata se è ver a almeno una delle due condizioni
If A Xor B: la condizione r isulta ver a se una sola delle due condizioni è ver a
If Not A: la condizione r isulta ver ificata se è falsa
Un esempio pr atico:
Continuare il controllo: ElseIfNei 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
non 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:
06.07.08.09.10.11.12.13.14.15.16.17.18.
Console.WriteLine("Inserisci un numero:")Numero = Console.ReadLine
If Numero > 0 Then
If Numero < 5 ThenConsole.WriteLine("Hai indovnato il numero!")
End IfElse
Console.WriteLine("Numero errato!")End If
Console.ReadKey()
End SubEnd Module
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.17.
Module Module1Sub Main()
Dim a, b As Double
Console.WriteLine("Inserire i lati di un rettangolo:")a = Console.ReadLineb = Console.ReadLine
'Se tutti e due i lati sono maggiori di 0If a > 0 And b > 0 Then
Console.WriteLine("L'area è: " & a * b)Else
Console.WriteLine("Non esistono lati con misure negative!")End If
Console.Readkey()End Sub
End Module
01.02.03.04.05.06.
Module Module1Sub Main()
Dim Voto As Single
Console.WriteLine("Inserisci il tuo voto:")
E' abbastanza disor dinato... La var iante ElseIf è molto utile per miglior e la leggibilità del codice:
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.
Blocchi di istruzioniFino 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:
07.08.09.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.
Voto = Console.ReadLine
If Voto < 3 ThenConsole.WriteLine("Sei senza speranze!")
ElseIf Voto < 5 Then
Console.WriteLine("Ancora un piccolo sforzo...")Else
If Voto < 7 ThenConsole.WriteLine("Stai andando discretamente")
ElseIf Voto < 9 Then
Console.WriteLine("Molto bene, continua così")Else
Console.WriteLine("Sei praticamente perfetto!")End If
End IfEnd If
End If
Console.ReadKey()End Sub
End Module
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.17.18.19.20.21.22.
Module Module1Sub Main()
Dim Voto As Single
Console.WriteLine("Inserisci il tuo voto:")Voto = Console.ReadLine
If Voto < 3 Then
Console.WriteLine("Sei senza speranze!")ElseIf Voto < 5 Then
Console.WriteLine("Ancora un piccolo sforzo...")ElseIf Voto < 7 Then
Console.WriteLine("Stai andando discretamente")ElseIf Voto < 9 Then
Console.WriteLine("Molto bene, continua così")Else
Console.WriteLine("Sei praticamente perfetto!")End If
Console.ReadKey()
End SubEnd Module
1.2.3.
[Keyword]'Blocco di codice
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 non è 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:
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:
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.50.51.52.53.54.55.56.57.
Module Module1Sub Main()
'a, b e c fanno parte del blocco delimitato da Sub ...'End SubDim a, b, c As Single
'Semplice esempio di risoluzione di equazione di'secondo gradoConsole.WriteLine("Equazione: ax2 + bx + c = 0")Console.WriteLine("Inserisci, in ordine, a, b e c:")a = Console.ReadLineb = Console.ReadLinec = Console.ReadLine
If a = 0 Then
Console.WriteLine("L'equazione si abbassa di grado")Console.ReadKey()'Con Exit Sub si esce dalla Sub, che in questo caso'coincide con il programma. Equivale a terminare'il programma stessoExit Sub
End If
'Anche delta fa parte del blocco delimitato da Sub ...'End SubDim delta As Single = b ^ 2 - 4 * a * c
'Esistono due soluzioni distinteIf delta > 0 Then
'Queste variabili fanno parte del blocco di If ...'ElseIfDim x1, x2 As Single'È possibile accedere senza problemi alla variabile'delta, poiché questo blocco è a sua volta'all'interno del blocco in cui è dichiarato deltax1 = (-b + Math.Sqrt(delta)) / (2 * a)x2 = (-b - Math.Sqrt(delta)) / (2 * a)Console.WriteLine("Soluzioni: ")Console.WriteLine("x1 = " & x1)Console.WriteLine("x2 = " & x2)
'Esiste una soluzione doppiaElseIf delta = 0 Then
'Questa variabile fa parte del blocco ElseIf ... ElseDim x As Singlex = -b / (2 * a)Console.WriteLine("Soluzione doppia: ")Console.WriteLine("x = " & x)
'Non esistono soluzioni in RElse
Console.WriteLine("Non esistono soluzioni in R")End If
Console.ReadKey()
End SubEnd Module
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:
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:
Questo tipo di contr ollo r ende molto più linear e, semplice e veloce il codice sor gente. Un esempio:
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:
01.02.03.04.05.06.07.08.09.10.11.
'...If A = 1 Then'istruzioni
End IfIf A = 2 Then'istruzioni
End IfIf A = 3 Then'istruzioni
End If'eccetera
01.02.03.04.05.06.07.08.09.
'...Select Case [Nome variabile da analizzare]Case [valore1]'istruzioni
Case [valore2]'istruzioni
Case [valore3]'istruzioni
End Select
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.
Module Module 1Sub Main()Dim a, b As Double
Dim C As Byte
Console.WriteLine("Inserire due numeri: ")a = Console.ReadLineb = Console.ReadLineConsole.WriteLine("Inserire 1 per calcolare la somma, 2 per la differenza, 3 per il
prodotto, 4 per il quoziente:")C = Console.ReadLine
Select Case C
Case 1Console.WriteLine(a + b)
Case 2Console.WriteLine(a - b)
Case 3Console.WriteLine(a * b)
Case 4Console.WriteLine(a / b)
End Select
Console.ReadKey()End Sub
End Module
1.
Varianti del costruttoAnche 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 irg ola
La vir gola per mette di definir e non solo uno, ma molti valor i possibili in un solo Case. Ad esempio:
Il codice sopr a pr oposto con Select equivale ad un If scr itto come segue:
Uso di To
Al contr ar io, la keyw or d To per mette di definir e un range 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.
Questo cor r isponde ad un If scr itto come segue:
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.
2.3.4.5.6.7.8.
Dim S As String'...Select Case S
Case "ciao"'...
Case "buongiorno"'...
End Select
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 solo se A'contiene un valore pari a 1, 2 o 3
Case 4, 6, 9'Questo codice viene eseguito solo se A'contiene un valore pari a 4, 6 o 9
End Select
1.2.3.4.5.
If A = 1 Or A = 2 Or A = 3 Then'...
ElseIf A = 4 Or A = 6 Or A = 9 Then'...
End If
1.2.3.4.5.6.7.8.
Select Case ACase 67 To 90
'Questo codice viene eseguito solo se A'contiene un valore compreso tra 67 e 90 (estremi inclusi)
Case 91 To 191'Questo codice viene eseguito solo se A'contiene un valore compreso tra 91 e 191
End Select
1.2.3.4.5.
If A >= 67 And A <= 90 Then'...
ElseIf A >= 91 And A <= 191 Then'...
End If
01.
Il suo equivalente 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:
Uso delle precedenti alternative in com 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:
02.03.04.05.06.07.08.09.10.11.
Select Case ACase Is >= 6
'Questo codice viene eseguito solo se A'contiene un valore maggiore o uguale di 6
Case Is > 1'Questo codice viene eseguito solo se A'contiene un valore maggiore di 1 (e minore di 6,'dato che, se si è arrivati a questo Case,'significa che la condizione del Case precedente non'è stata soddisfatta)
End Select
1.2.3.4.5.
If A >= 6 Then'...
ElseIf A > 1 Then'...
End If
01.02.03.04.05.06.07.08.09.10.11.12.
Select Case ACase 1, 4
'Questo codice viene eseguito solo se A'contiene 1 o 4
Case 9 To 12'Questo codice viene eseguito solo se A'contiene un valore compreso tra 9 e 12
Case Else'Questo codice viene eseguito solo se A'contiene un valore minore di 9 o maggiore di 12,'ma diverso da 1 e 4
End Select
1.2.3.4.5.6.7.8.
Select Case ACase 7, 9, 10 To 15, Is >= 90
'Questo codice viene eseguito solo se A'contiene 7 o 9 o un valore compreso tra 10 e 15'oppure un valore maggiore o uguale di 90
Case Else'...
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:
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:
Le altr e ver sioni del costr utto, invece, sono le seguenti:
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:
1.2.3.
Do'istruzioni
Loop
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.
Module Module1
Sub Main()Dim a, b As Single
Do
'Pulisce lo schermoConsole.Clear()'L'underscore serve per andare a capo nel codiceConsole.WriteLine("Inserire le misure di base e altezza " & _
"di un rettangolo:")a = Console.ReadLineb = Console.ReadLine
'Controlla che a e b non siano nulli. In quel caso, esce'dal ciclo. Se non ci fosse questo If in mezzo al codice,'verrebbe scritto a schermo il messaggio:' "L'area del rettangolo è: 0"'cosa che noi vogliamo evitare. Se si usasse un'altra'variante di Do Loop, questo succederebbe sempre. Ecco'perchè, in questa situazione, è meglio'servirsi del semplice Do LoopIf a = 0 Or b = 0 Then
Exit DoEnd If
Console.WriteLine("L'area del rettangolo è: " & (a * b))Console.ReadKey()
LoopEnd Sub
End Module
1.2.3.
Do'istruzioni
Loop While [condizione]
Il codice scr iver à a scher mo "2".
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:
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).
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:
A scher mo appar ir à "2".
01.02.03.04.05.06.07.08.09.10.11.12.
Module Module1Sub Main()
Dim a As Int32 = 0
Doa += 1
Loop While (a < 2) And (a > 0)Console.WriteLine(a)
Console.ReadKey()
End SubEnd Module
1.2.3.
Do While [condizione]'istruzioni
Loop
01.02.03.04.05.06.07.08.09.10.11.12.
Module Module1Sub Main()
Dim a As Int32 = 0
Do While (a < 2) And (a > 0)a += 1
LoopConsole.WriteLine(a)
Console.ReadKey()
End SubEnd Module
1.2.3.
Do'istruzioni
Loop Until [condizione]
01.02.03.04.05.06.07.08.09.10.11.12.
Module Module1Sub Main()
Dim a As Int32 = 0
Doa += 1
Loop Until (a <> 1)Console.WriteLine(a)
Console.ReadKey()
End SubEnd Module
1.2.3.
Do Until [condizione]'istruzioni
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:
A scher mo appar ir à "0".
Un piccolo esempio finale:
Suggerimento
Per impostar e il valor e di Default (ossia il valor e pr edefinito) di una var iabile si può usar e questa sintassi:
Funziona solo per una var iabile alla volta. Questo tipo di istr uzione si chiama in izializzazione in-line.
Loop
01.02.03.04.05.06.07.08.09.10.11.12.
Module Module1Sub Main()
Dim a As Int32 = 0
Do Until (a <> 1)a += 1
LoopConsole.WriteLine(a)
Console.ReadKey()
End SubEnd Module
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.
Module Module1Sub Main()
Dim a, b, c As Int32Dim n As Int32
Console.WriteLine("-- Successione di Fibonacci --")Console.WriteLine("Inserire un numero oltre il quale terminare:")n = Console.ReadLine
If n = 0 Then
Console.WriteLine("Nessun numero della successione")Console.ReadKey()Exit Sub
End If
a = 1b = 1Console.WriteLine(a)Console.WriteLine(b)Do While c < n
c = a + bb = aa = cConsole.WriteLine(c)
Loop
Console.ReadKey()End Sub
End Module
1. Dim [nome] As [tipo] = [valore]
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:
La var iabile I, usata in questo esempio, viene definita contatore 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:
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:
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.2.3.4.5.
Dim I As Int32 For I = 0 To [numero]'istruzioni
Next
01.02.03.04.05.06.07.08.09.10.11.12.13.
Module Module1Sub Main()
Dim a As Int32
'Scrive 46 volte (da 0 a 45, 0 compreso, sono 46 numeri)'a schermo 'ciao'For a = 0 To 45
Console.WriteLine("ciao")Next Console.ReadKey()
End SubEnd Module
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.17.18.19.20.21.22.
Module Module1Sub Main()
Dim a, b As Int32
Console.WriteLine("Inserisci un numero pari")b = Console.ReadLine
'Se b non è pari, ossia se il resto della divisione'b/2 è diverso da 0If b Mod 2 <> 0 Then
'Lo fa diventare un numero pari, aggiungendo 1b += 1
End If
'Scrive tutti i numeri da b a b+20For a = b To b + 20
Console.WriteLine(a)Next Console.ReadKey()
End SubEnd Module
1.
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:
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.
Suggerimento
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:
2.3.4.5.
For a = b To b + 20If a Mod 2 = 0 Then
Console.WriteLine(a)End If
Next
1.2.3.
For a = b To b + 20 Step 2Console.WriteLine(a)
Next
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 dimensioneFino 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 array è 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:
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
1. Dim [nome]([numero elementi - 1]) As [tipo]
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:
il pr ogr ammator e indica al pr ogr amma che la var iabile A è un ar r ay contenente questi elementi:
che sono per la pr ecisione 6 elementi. Ecco un listato che esemplifica i concetti fin'or a chiar iti:
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
1. Dim A(5) As String
1. A(0), A(1), A(2), A(3), A(4), A(5)
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.
Module Module1Sub Main()
'Array che contiene 10 valori decimali, rappresentanti votiDim Marks(9) As Single'Questa variabile terrà traccia di quanti voti'l'utente avrà immesso da tastiera e permetterà di'calcolarne una mediaDim Index As Int32 = 0
'Mark conterrà il valore temporaneo immesso'da tastiera dall'utenteDim Mark As SingleConsole.WriteLine("Inserisci un altro voto (0 per terminare):")Mark = Console.ReadLine 'Il ciclo finisce quando l'utente immette 0 oppure quando'si è raggiunto l'indice massimo che è'possibile usare per identificare una cella dell'arrayDo While (Mark > 0) And (Index < 10)
'Se il voto immesso è maggiore di 0, lo memorizza'nell'array e incrementa l'indice di 1, così da'poter immagazzinare correttamente il prossimo voto nell'arrayMarks(Index) = MarkIndex += 1 Console.WriteLine("Inserisci un altro voto (0 per terminare):")Mark = Console.ReadLine
Loop'Decrementa l'indice di 1, poiché anche se l'utente'ha immesso 0, nel ciclo precedente, l'indice era stato'incrementato prevedendo un'ulteriore immissione, che,'invece, non c'è stataIndex -= 1
'Totale dei votiDim Total As Single = 0'Usa un ciclo For per scorrere tutte le celle dell'array'e sommarne i valoriFor I As Int32 = 0 To Index
Total += Marks(I)Next
'Mostra la mediaConsole.WriteLine("La tua media è: " & (Total / (Index + 1)))Console.ReadKey()
End SubEnd Module
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:
Ad esempio:
Questa sintassi br eve equivale a questo codice:
Un'ulter ior e sintassi usata per dichiar ar e un ar r ay è la seguente:
Quest'ultima, come vedr emo, sar à par ticolar mente utile nel gestir e il tipo r estituito da una funzione.
Array a più dimensioniGli 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 rang 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 è:
Ecco un esempio che consider a un ar r ay di r ango 2 come una matr ice quadr ata:
1. Dim [nome]() As [tipo] = {elementi dell'array separati da virgole}
1. Dim Parole() As String = {"ciao", "mouse", "penna"}
1.2.3.4.
Dim Parole(2) As StringParole(0) = "ciao"Parole(1) = "mouse"Parole(2) = "penna"
1. Dim [nome] As [tipo]()
1.2.
Dim [nome]( , ) As [tipo] 'array di rango 2Dim [nome]( , , ) As [tipo] 'array di rango 3
01.02.03.04.05.06.07.08.09.10.
Module Module1Sub Main()
'Dichiara e inizializza un array di rango 2. Dato che'in questo caso abbiamo due dimensioni, e non una sola,'non si può specificare una semplice lista di'valori, ma una specie di "tabella" a due entrate.'Nell'esempio che segue, ho creato una semplice'tabella a due righe e due colonne, in cui ogni cella'è 0.
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:
11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.
Dim M(,) As Single = _{{0, 0}, _{0, 0}}
'Bisogna notare il particolare uso delle graffe: si'considera l'array di rango 2 come un array i cui'elementi sono altri array
Console.WriteLine("Inserire gli elementi della matrice:")For I As Int32 = 0 To 1
For J As Int32 = 0 To 1Console.Write("Inserire l'elemento (" & I & ", " & J & "): ")M(I, J) = Console.ReadLine
NextNext
Dim Det As SingleDet = M(0, 0) * M(1, 1) - M(0, 1) * M(1, 0)Console.WriteLine("Il determinante della matrice è: " & Det)
Console.ReadKey()
End SubEnd Module
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 solo e soltanto 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:
E se si pr ova a far e una cosa del gener e:
Si r iceve un er r or e esplicito da par te del compilator e.
Ridimensionare un arrayPuò 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 keywor 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:
La r iga Ar r ay.Resize(A, n) equivale, usando ReDim a:
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. Dim [nome]()() As [tipo] 'array di array
1.2.3.4.
Dim A(,) As Int32Dim B()() As Int32'...A = B
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.17.18.19.
Module Module1Sub Main()
Dim A() As Int32Dim n As Int32
Console.WriteLine("Inserisci un numero")n = Console.ReadLine
'Reimposta la lunghezza di a ad n elementiArray.Resize(A, n)
'Calcola e memorizza i primi n numeri pari (zero compreso)For I As Int16 = 0 To n - 1
A(I) = I * 2Next
Console.ReadKey()
End SubEnd Module
1. ReDim A(n - 1)
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, è:
In questo modo, ogni "r iga" viene aggiustata alla lunghezza giusta.
1.2.3.
ReDim M(0, 2)ReDim M(1, 2)ReDim M(2, 2)
A12. Gli Array - Parte II
Il costrutto iterativo For EachQuesto 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:
Ovviamente anche in questo caso, come nel nor male For , è possibile inizializzar e una var iabile contator e all'inter no del
costr utto:
Esempio:
Per aver e un ter mine di par agone, il semplicissimo codice pr oposto equivale, usando un for nor male, a questo:
Gli array sono un tipo referenceDiver 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.
1.2.3.4.
Dim A As [tipo]For Each A In [array/collezione]
'istruzioniNext
1. For Each A As [tipo] in [array/collezione] ...
01.02.03.04.05.06.07.08.09.10.11.12.13.14.
Module Module1Sub Main()
Dim Words() As String = {"Questo", "è", "un", "array", "di", "stringhe"}
For Each Str As String In WordsConsole.Write(Str & " ")
Next 'A schermo apparirà la frase:' "Questo è un array di stringhe "
Console.ReadKey()
End SubEnd Module
1.2.3.4.5.
'Words.Length restituisce il numero di elementi'presenti nell'array WordsFor I As Int32 = 0 To Words.Length - 1
Console.Write(Words(I) & " ")Next
01.02.03.
Module Module1Sub Main()
Ecco come appar e la memor ia dopo l'assegnazione A = B:
Ed ecco come appar e dopo l'assegnazione B = C:
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.
'A e B sono due array di interiDim A() As Int32 = {1, 2, 3}Dim B() As Int32 = {4, 5, 6}
'Ora A e B sono due oggetti diversi e contengono'numeri diversi. Questa riga stamperà sullo'schermo "False", infatti A Is B = FalseConsole.WriteLine(A Is B)'Adesso poniamo A uguale a B. Dato che gli array'sono un tipo reference, da ora in poi, entrambi'saranno lo stesso oggettoA = B'Infatti questa istruzione stamperà a schermo''"True", poiché A Is B = TrueConsole.WriteLine(A Is B)'Dato che A e B sono lo stesso oggetto, se modifichiamo'un valore dell'array riferendoci ad esso con il nome'B, anche richiamandolo con A, esso mostrerà'che l'ultimo elemento è lo stessoB(2) = 90'Su schermo apparirà 90Console.WriteLine(A(2))
Dim C() As Int32 = {7, 8, 9}B = C'Ora cosa succede?
Console.ReadKey()
End SubEnd Module
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 arrayLa 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 metodoIl Fr amewor 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 invocare 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...
Procedure senza parametriIl 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:
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:
mentr e la pr ossima è SBAGLI ATA:
1.2.3.
Sub [nome]()'istruzioni
End Sub
01.02.03.04.05.06.07.08.09.
Module Module1Sub Esempio()
'istruzioniEnd Sub Sub Main()
'istruzioniEnd Sub
End Module
1.2.3.
Module Module1Sub Main()
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:
E' totalmente e concettualmente sbagliato. Ma or a veniamo al dunque con un esempio:
4.5.6.7.8.
Sub Esempio()'istruzioni
End Sub'istruzioni
End SubEnd Module
1.2.3.4.5.6.
Module Module1Sub Main()
'istruzioniEnd SubConsole.WriteLine()
End Sub
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.50.51.
Module Module1'Dichiarazione di una procedura: il suo nome è "FindDay", il'suo elenco di parametri è vuoto, e il suo corpo è'rappresentato da tutto il codice compreso tra "Sub FindDay()"'ed "End Sub".Sub FindDay()
Dim StrDate As StringConsole.Write("Inserisci giorno (dd/mm/yyyy): ")StrDate = Console.ReadLine
Dim D As Date'La funzione Date.TryParse tenta di convertire la stringa'StrDate in una variabile di tipo Date (che è un tipo'base). Se ci riesce, ossia non ci sono errori nella'data digitata, restituisce True e deposita il valore'ottenuto in D; se, al contrario, non ci riesce,'restituisce False e D resta vuota.'Quando una data non viene inizializzata, dato che è un'tipo value, contiene un valore predefinito, il primo'Gennaio dell'anno 1 d.C. a mezzogiorno in punto.If Date.TryParse(StrDate, D) Then
'D.DayOfWeek contiene il giorno della settimana di D'(lunedì, martedì, eccetera...), ma in un'formato speciale, l'Enumeratore, che vedremo nei'prossimi capitoli.'Il ".ToString()" converte questo valore in una'stringa, ossia in un testo leggibile: i giorni della'settimana, però, sono in ingleseConsole.WriteLine(D.DayOfWeek.ToString())
ElseConsole.WriteLine(StrDate & " non è una data valida!")
End IfEnd Sub
'Altra procedura, simile alla primaSub CalculateDaysDifference()
Dim StrDate1, StrDate2 As StringConsole.Write("Inserisci il primo giorno (dd/mm/yyyy): ")StrDate1 = Console.ReadLineConsole.Write("Inserisci il secondo giorno (dd/mm/yyyy): ")StrDate2 = Console.ReadLine
Dim Date1, Date2 As Date
If Date.TryParse(StrDate1, Date1) And _
Date.TryParse(StrDate2, Date2) Then'La differenza tra due date restituisce il tempo'trascorso tra l'una e l'altra. In questo caso noi'prendiamo solo i giorniConsole.WriteLine((Date2 - Date1).Days)
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
locali o temporanee, 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.
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.
ElseConsole.WriteLine("Inserire due date valide!")
End IfEnd Sub
Sub Main()
'Command è una variabile di tipo char (carattere) che'conterrà una lettera indicante quale compito eseguireDim 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 tastieraCommand = Console.ReadKey().KeyChar'Analizza il valore di CommandSelect Case Command
Case "f"'Invoca la procedura FindDay()FindDay()
Case "d"'Invoca la procedura CalculateDaysDifference()CalculateDaysDifference()
Case "e"'Esce dal cicloExit Do
Case ElseConsole.WriteLine("Comando non riconosciuto!")
End SelectConsole.ReadKey()
LoopEnd Sub
End Module
01.02.03.04.05.06.07.08.09.10.11.
Module Module1'Questa variabile è dichiarata a livello di classe'(o di modulo, in questo caso), perciò è accessibile'a tutti i membri del modulo, sempre seguendo il discorso'dei blocchi di codice fatto in precedenzaDim Total As Single = 0
'Legge un numero da tastiera e lo somma al totaleSub Sum()
Dim Number As Single = Console.ReadLine
Procedure con parametriAvviandoci 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 chiamante: 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.
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
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.
Total += NumberEnd Sub
'Legge un numero da tastiera e lo sottrae al totaleSub Subtract()
Dim Number As Single = Console.ReadLineTotal -= Number
End Sub
'Legge un numero da tastiera e divide il totale per'tale numeroSub Divide()
Dim Number As Single = Console.ReadLineTotal /= Number
End Sub
'Legge un numero da tastiera e moltiplica il totale'per tale numeroSub Multiply()
Dim Number As Single = Console.ReadLineTotal *= Number
End Sub
Sub Main()'Questa variabile conterrà il simbolo matematico'dell'operazione da eseguireDim Operation As CharDo
Console.Clear()Console.WriteLine("Risultato attuale: " & Total)Operation = Console.ReadKey().KeyCharSelect Case Operation
Case "+"Sum()
Case "-"Subtract()
Case "*"Multiply()
Case "/"Divide()
Case "e"Exit Do
Case ElseConsole.WriteLine("Operatore non riconosciuto")Console.ReadKey()
End SelectLoop
End SubEnd Module
1.2.3.
Sub [nome](ByVal [parametro1] As [tipo], ByVal [parametro2] As [tipo], ...)'istruzioni
End Sub
esempio, r iscr ivendo l'ultimo codice pr oposto nel par agr afo pr ecedente con l'aggiunta dei par ametr i:
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 parametro
formale, mentr e N - ossia ciò che viene concr etamente pas s ato al metodo - si dice parametro 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)?
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.50.51.52.53.
Module Module1Dim Total As Single = 0
Sub Sum(ByVal Number As Single)
Total += NumberEnd Sub
Sub Subtract(ByVal Number As Single)
Total -= NumberEnd Sub
Sub Divide(ByVal Number As Single)
Total /= NumberEnd Sub
Sub Multiply(ByVal Number As Single)
Total *= NumberEnd Sub
Sub Main()
'Questa variabile conterrà il simbolo matematico'dell'operazione da eseguireDim Operation As CharDo
Console.Clear()Console.WriteLine("Risultato attuale: " & Total)Operation = Console.ReadKey().KeyCharSelect Case Operation
'Se si tratta di simboli accettabiliCase "+", "-", "*", "/"
'Legge un numero da tastieraDim N As Single = Console.ReadLine'E a seconda dell'operazione, utilizza una'procedura piuttosto che un'altraSelect Case Operation
Case "+"Sum(N)
Case "-"Subtract(N)
Case "*"Multiply(N)
Case "/"Divide(N)
End SelectCase "e"
Exit DoCase Else
Console.WriteLine("Operatore non riconosciuto")Console.ReadKey()
End SelectLoop
End SubEnd Module
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.
Passare parametri al programma da riga di comandoCome 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 comando. In quest'ultimo caso, Main viene dichiar ata con un solo ar gomento, un ar r ay di str inghe:
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.
Module Module1Dim Total As Single = 0
Sub DoOperation(ByVal Number As Single, ByVal Op As Char)
Select Case OpCase "+"
Total += NumberCase "-"
Total -= NumberCase "*"
Total *= NumberCase "/"
Total /= NumberEnd Select
End Sub
Sub Main()Dim Operation As CharDo
Console.Clear()Console.WriteLine("Risultato attuale: " & Total)Operation = Console.ReadKey().KeyCharSelect Case Operation
Case "+", "-", "*", "/"Dim N As Single = Console.ReadLine'A questa procedura vengono passati due'parametri: il primo è il numero da'aggiungere/sottrarre/moltiplicare/dividere'a Total; il secondo è il simbolo'matematico che rappresenta l'operazione'da eseguireDoOperation(N, Operation)
Case "e"Exit Do
Case ElseConsole.WriteLine("Operatore non riconosciuto")Console.ReadKey()
End SelectLoop
End SubEnd Module
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.17.
Module Module1Sub Main(ByVal Args() As String)
If Args.Length = 0 Then'Se la lunghezza dell'array è 0, significa che è vuoto'e quindi non è stato passato nessun parametro a riga'di comando. Scrive a schermo come utilizzare'il programmaConsole.WriteLine("Utilizzo: nomeprogramma.exe tuonome")
Else'Args ha almeno un elemento. Potrebbe anche averne di'più, ma a noi interessa solo il primo.'Saluta l'utente con il nome passato da riga di comandoConsole.WriteLine("Ciao " & Args(0) & "!")
End IfConsole.ReadKey()
End Sub
Per pr ovar lo, potete usar e cmd.ex e, il pr ompt dei comandi. Io ho digitato:
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:
End Module
1.2.
CD "C:\Users\Totem\Documents\Visual Studio 2008\Projects\ConsoleApplication2\bin\Debug"ConsoleApplication2.exe Totem
A14. I Metodi - Parte II
ByVal e ByRefNel capitolo pr ecedente, tutti i par ametr i sono stati dichiar anti anteponendo al lor o nome la keywor 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 copia 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 non sul par ametr o attuale for nito.
Ecco un esempio:
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 indirizzo). 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:
Nel codice, la sola differ enza consiste nella keywor 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 SEMPRE allo stesso modo, anche se
vengono inser iti nell'elenco dei par ametr i accompagnati da ByVal. Eccone una dimostr azione:
01.02.03.04.05.06.07.08.09.10.11.12.
Module Module1Sub Change(ByVal N As Int32)
N = 30End Sub
Sub Main()
Dim A As Int32 = 56Change(A)Console.WriteLine(A)Console.ReadKey()
End SubEnd Module
01.02.03.04.05.06.07.08.09.10.11.12.
Module Module1Sub Change(ByRef N As Int32)
N = 30End Sub
Sub Main()
Dim A As Int32 = 56Change(A)Console.WriteLine(A)Console.ReadKey()
End SubEnd Module
01.02.03.04.05.06.
Module Module1Dim A As New Object
Sub Test(ByVal N As Object)
Console.WriteLine(N Is A)
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 funzioniLe funzioni sono simili alle pr ocedur e, ma possiedono qualche car atter istica in più. La lor o sintassi è la seguente:
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:
E un altr o esempio in cui ci sono più Retur n:
07.08.09.10.11.12.
End Sub
Sub Main()Test(A)Console.ReadKey()
End SubEnd Module
1.2.3.4.
Function [name]([elenco parametri]) As [tipo]'...Return [risultato]
End Function
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.
Module Module1'Questa funzione calcola la media di un insieme'di numeri decimali passati come arrayFunction Average(ByVal Values() As Single) As Single
'Total conterrà la somma totale di tutti'gli elementi di ValuesDim Total As Single = 0'Usa un For Each per ottenere direttamente i valori'presenti nell'array piuttosto che enumerarli'attraverso un indice mediante un For normaleFor Each Value As Single In Values
Total += ValueNext'Restituisce la media aritmetica, ossia il rapporto'tra la somma totale e il numero di elementiReturn (Total / Values.Length)
End Function
Sub Main(ByVal Args() As String)Dim Values() As Single = {1.1, 5.2, 9, 4, 8.34}'Notare che in questo caso ho usato lo stesso nome'per il parametro formale e attualeConsole.WriteLine("Media: " & Average(Values))Console.ReadKey()
End SubEnd Module
01.02.03.04.05.06.07.08.09.
Module Module1Function Potenza(ByVal Base As Single, ByVal Esponente As Byte) As Double
Dim X As Double = 1
If Esponente = 0 ThenReturn 1
ElseIf Esponente = 1 Then
In quest'ultimo esempio, il cor po della funzione contiene ben tr e Retur n, ma ognuno appar tiene a un path di codice
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 Function , 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 particolari delle funzioniCi 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:
ma è possibile scr iver e, come abbiamo fatto fin'or a:
10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.
Return BaseElse
For i As Byte = 1 To EsponenteX *= Base
NextReturn X
End IfEnd If
End Function Sub Main()
Dim F As DoubleDim b As SingleDim e As Byte
Console.WriteLine("Inserire base ed esponente:")b = Console.ReadLinee = Console.ReadLineF = Potenza(b, e)Console.WriteLine(b & " elevato a " & e & " vale " & F)Console.ReadKey()
End SubEnd Module
1. a = Console.ReadLine()
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:
ci siamo limitati a:
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.
Variabili StaticLe 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:
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.
1. Dim b = Console.ReadKey()
1. Console.ReadKey()
01.02.03.04.05.06.07.08.09.10.11.12.13.14.
Module Module1Sub Test()
Static B As Int32 = 0B += 1Console.WriteLine(B)
End Sub
Sub Main(ByVal Args() As String)For I As Int16 = 1 To 6
Test()NextConsole.ReadKey()
End SubEnd Module
A15. I Metodi - Parte III
Parametri opzionaliCome 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 keywor 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:
Parametri indefiniti
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.
Module Module1'Disegna una barra "di caricamento" animata con dei trattini'e dei pipe (|). Length indica la sua lunghezza, ossia quanti'caratterei debbano essere stampati a schermo. AnimationSpeed'è la velocità dell'animazione, di default 1Sub DrawBar(ByVal Length As Int32, _
Optional ByVal AnimationSpeed As Single = 1)'La variabile static tiene conto del punto a cui si è'arrivati al caricamentoStatic Index As Int32 = 1
'Disegna la barraFor I As Int32 = 1 To Length
If I > Index ThenConsole.Write("-")
ElseConsole.Write("|")
End IfNext
'Aumenta l'indice di uno. Notare il particolare'assegnamento che utilizza l'operatore Mod. Finché'Index è minore di Length, questa espressione equivale'banalmente a Index + 1, poiché a Mod b = a se a < b.'Quando Index supera il valore di Length, allora l'operatore'Mod cambia le cose: infatti, se Index = Length + 1,'l'espressione restituisce 0, che, sommato a 1, dà 1.'Il risultato che otteniamo è che Index reinizia'da capo, da 1 fino a Length.Index = (Index Mod (Length + 1)) + 1'Il metodo Sleep, che vedremo approfonditamente solo nella'sezione B, fa attendere al programma un certo numero di'millisecondi.'1000 / AnimationSpeed provoca una diminuzione del tempo'di attesa all'aumentare della velocitàThreading.Thread.CurrentThread.Sleep(1000 / AnimationSpeed)
End Sub
Sub Main()'Disegna la barra con un ciclo infinito. Potete invocare'DrawBar(20) tralasciando l'ultimo argomento e l'animazione'sarà lenta poiché la velocità di default è 1Do
Console.Clear()DrawBar(20, 5)
LoopEnd Sub
End Module
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 keywor d Par amAr r ay inter posta tr a la
clausola ByVal o ByRef e il nome del par ametr o.
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.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.
Module Module1'Somma tutti i valori passati come parametri.Function Sum(ByVal ParamArray Values() As Single) As Single
Dim Result As Single = 0
For I As Int32 = 0 To Values.Length - 1Result += Values(I)
Next
Return ResultEnd Function
Sub Main()
Dim S As Single
'Somma due valoriS = Sum(1, 2)'Somma quattro valoriS = Sum(1.1, 5.6, 98.2, 23)'Somma un array di valoriDim V() As Single = {1, 8, 3.4}S = Sum(V)
End SubEnd Module
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.
Module Module1'Questa funzione calcola un prezzo includendovi anche'il pagamento di alcune tasse (non sono un esperto di'economia, perciò mi mantengono piuttosto sul vago XD).'Il primo parametro rappresenta il prezzo originale, mentre'il secondo è un parametro indefinito che'raggruppa tutte le varie tasse vigenti sul prodotto'da acquistare che devono essere aggiunte all'importo'iniziale (espresse come percentuali)Function ApplyTaxes(ByVal OriginalPrice As Single, _
ByVal ParamArray Taxes() As Single) As SingleDim Result As Single = OriginalPriceFor Each Tax As Single In Taxes
Result += OriginalPrice * Tax / 100NextReturn Result
End Function
Sub Main()Dim Price As Single = 120
'Aggiunge una tassa del 5% a PriceDim Price2 As Single = _
ApplyTaxes(Price, 5)
'Aggiunge una tassa del 5%, una del 12.5% e una'dell'1% a PriceDim Price3 As Single = _
ApplyTaxes(Price, 5, 12.5, 1)
Console.WriteLine("Prezzo originale: " & Price)Console.WriteLine("Presso con tassa 1: " & Price2)Console.WriteLine("Prezzo con tassa 1, 2 e 3: " & Price3)
RicorsioneSi 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 fattor iale:
35.36.37.
Console.ReadKey()
End SubEnd Module
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.17.18.19.20.21.22.
Module Module1'Notare che il parametro è di tipo Byte perchè il'fattoriale cresce in modo abnorme e già a 170! Double non'basta più a contenere il risultatoFunction Factorial(ByVal N As Byte) As Double
If N <= 1 ThenReturn 1
ElseReturn N * Factorial(N - 1)
End IfEnd Function
Sub Main()
Dim Number As Byte
Console.WriteLine("Inserisci un numero (0 <= x < 256):")Number = Console.ReadLineConsole.WriteLine(Number & "! = " & Factorial(Number))
Console.ReadKey()
End SubEnd 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:
Ad esempio:
1.2.3.4.5.
Enum [Nome][Nome valore 1][Nome valore 2]...
End Enum
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.
Module Module1'A seconda di come sono configurati i suoi caratteri, una'stringa può possedere diverse denominazioni, chiamate'Case. Se è costituita solo da caratteri minuscoli'(es.: "stringa di esempio") si dice che è in Lower'Case; al contrario se contiene solo maiuscole (es.: "STRINGA'DI ESEMPIO") sarà Upper Case. Se, invece, ogni'parola ha l'iniziale maiuscola e tutte le altre lettere'minuscole si indica con Proper Case (es.: "Stringa Di Esempio").'In ultimo, se solo la prima parola ha l'iniziale'maiuscola e il resto della stringa è tutto minuscolo'e questa termina con un punto, si ha Sentence Case'(es.: "Stringa di esempio.").'Questo enumeratore indica questi casiEnum StringCase
LowerUpperSentenceProper
End Enum
'Questa funzione converte una stringa in uno dei Case'disponibili, indicati dall'enumeratore. Il secondo parametro'è specificato fra parentesi quadre solamente perchè'Case è una keyword, ma noi la vogliamo usare come'identificatore.Function ToCase(ByVal Str As String, ByVal [Case] As StringCase) As String
'Le funzioni per convertire in Lower e Upper'case sono già definite. E' sufficiente'indicare un punto dopo il nome della variabile'stringa, seguito a ToLower e ToUpperSelect Case [Case]
Case StringCase.LowerReturn Str.ToLower()
Case StringCase.UpperReturn Str.ToUpper()
Case StringCase.Proper'Consideriamo la stringa come array di'caratteri:Dim Chars() As Char = Str.ToLower()'Iteriamo lungo tutta la lunghezza della'stringa, dove Str.Length restituisce appunto'tale lunghezzaFor I As Int32 = 0 To Str.Length - 1
'Se questo carattere è uno spazio oppure'è il primo di tutta la stringa, il'prossimo indicherà l'inizio di una nuova
L'enumer ator e Str ingCase offr e quattr o possibilità: Lower , 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:
A scher mo appar ir à
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:
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.
'parola e dovrà essere maiuscolo.If I = 0 Then
Chars(I) = Char.ToUpper(Chars(I))End IfIf Chars(I) = " " And I < Str.Length - 1 Then
'Char.ToUpper rende maiuscolo un carattere'passato come parametro e lo restituisceChars(I + 1) = Char.ToUpper(Chars(I + 1))
End IfNext'Restituisce l'array modificato (un array di caratteri'e una stringa sono equivalenti)Return Chars
Case StringCase.Sentence'Riduce tutta la stringa a Lower CaseStr = Str.ToLower()'Imposta il primo carattere come maiuscoloDim Chars() As Char = StrChars(0) = Char.ToUpper(Chars(0))Str = Chars'La chiude con un puntoStr = Str & "."Return Str
End SelectEnd 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 puntoConsole.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 SubEnd Module
1.2.3.4.
Console.WriteLine(StringCase.Lower)Console.WriteLine(StringCase.Upper)Console.WriteLine(StringCase.Sentence)Console.WriteLine(StringCase.Proper)
1.2.3.4.
0123
1.2.3.4.5.
Enum StringCaseLower = 5Upper = 10Sentence = 20
Se ad un nome non viene assegnato valor e, esso assumer à il valor e del suo pr ecedente, aumentato di 1:
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:
È 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:
6.Proper = 40
End Enum
1.2.3.4.5.6.
Enum StringCaseLower = 5Upper '= 6Sentence = 20Proper '= 21
End Enum
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.
Module Module1'Usa i nomi inglesi delle note. L'enumerazione inizia'da -9 poiché il Do centrale si trova 9 semitoni'sotto il La centraleEnum Note
C = -9CSharpDDSharpEFFSharpGGSharpAASharpB
End Enum
'Restituisce la frequenza di una nota. N, in concreto,'rappresenta la differenza, in semitoni, di quella nota'dal La centrale. Ecco l'utilittà degli enumeratori,'che danno un nome reale a ciò che un dato indica'indirettamenteFunction GetFrequency(ByVal N As Note) As Single
Return 440 * 2 ^ (N / 12)End Function
'Per ora prendete per buona questa funzione che restituisce'il nome della costante di un enumeratore a partire dal'suo valore. Avremo modo di approfondire nei capitoli'sulla ReflectionFunction GetName(ByVal N As Note) As String
Return [Enum].GetName(GetType(Note), N)End Function
Sub Main()
'Possiamo anche iterare usando gli enumeratori, poiché'si tratta pur sempre di semplici numeriFor I As Int32 = Note.C To Note.B
Console.WriteLine("La nota " & GetName(I) & _" risuona a una frequenza di " & GetFrequency(I) & "Hz")
Next
Console.ReadKey()End Sub
End Module
1.2.3.
Enum StringCase As ByteLower = 5
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 codificati 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:
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:
4.5.6.
Upper = 10Sentence = 20Proper = 40
End Enum
1. 00001101
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.
Module Module1'È convenzione che gli enumeratori che codificano a bit'abbiano un nome al plurale'Questo enumeratore definisce alcuni tipi di filePublic Enum FileAttributes As Byte
'1 = 2 ^ 0'In binario:'00000001Normal = 1
'2 = 2 ^ 1'00000010Hidden = 2
'4 = 2 ^ 2'00000100System = 4
'8 = 2 ^ 3'00001000Archive = 8
End Enum
Sub Main()Dim F As FileAttributes'F all'inizio è 0, non contiene niente:'00000000
F = FileAttributes.Normal'Ora F è 1, ossia Normal'00000001
F = FileAttributes.Hidden Or FileAttributes.System'La situazione diventa complessa:'Il primo valore è 2: 000000010'Il secondo valore è 4: 000000100'Abbiamo già visto l'operatore Or: restituisce True se'almeno una delle condizioni è vera: qui True è'1 e False è 0:
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:
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.
41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.
'000000010 Or'000000100 ='000000110'Come si vede, ora ci sono due campi attivi: 4 e 2, che'corrispondono a Hidden e System. Abbiamo fuso insieme due'attributi con Or
F = FileAttributes.Archive Or FileAttributes.System Or _
FileAttributes.Hidden'La stessa cosa:'00001000 Or'00000100 Or'00000010 ='00001110
End SubEnd Module
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.17.18.19.20.
Module Module1Sub Main()
Dim F As FileAttributes
F = FileAttributes.Archive Or FileAttributes.System Or _FileAttributes.Hidden
'Ora F è 00001110 e bisogna eseguire un'operazione di And'sui bit, confrontando questo valore con Archive, che è 8.'And restituisce Vero solo quando entrambe le condizioni'sono vere:'00001110 And'00001000 ='00001000, ossia Archive!If F And FileAttributes.Archive = FileAttributes.Archive Then
Console.WriteLine("Il file è marcato come 'Archive'")End IfConsole.ReadKey()
End SubEnd Module
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 strutture, 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 tante 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.
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:
Come si vede dall'esempio, la sintassi usata per dichiar ar e una str uttur a è la seguente:
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:
[Ricor date che le dichiar azioni di nuovi tipi di dato (fino ad or a quelli che abbiamo analizzato sono enumer ator i e
1.2.3.
Dim Names() As StringDim Surnames() As StringDim PhoneNumbers() As String
1.2.3.4.5.6.7.8.
Structure ContactDim Name, Surname, PhoneNumber As String
End Structure '... 'Un array di conttati, ognuno rappresentato dalla struttura ContactDim Contacts() As Contact
1.2.3.4.5.
Structure [Nome]Dim [Campo1] As [Tipo]Dim [Campo2] As [Tipo]'...
End Structure
01.02.03.04.05.06.07.08.09.10.11.12.13.
Module Module1Structure Contact
Dim Name, Surname, PhoneNumber As StringEnd Structure
Sub Main()
Dim A As Contact
A.Name = "Mario"A.Surname = "Rossi"A.PhoneNumber = "333 33 33 333"
End SubEnd Module
str uttur e, e le classi solo come intr oduzione) possono esser e fatte solo a livello di classe o di namespace, e mai 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.
Acrobazie con le struttureMa 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.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.50.51.52.53.54.55.56.57.58.59.
Module Module1Structure Contact
Dim Name, Surname, PhoneNumber As StringEnd Structure
Sub Main()
'Contacts(-1) inizializza un array vuoto,'ossia con 0 elementiDim Contacts(-1) As ContactDim Command As Char
Do
Console.Clear()Console.WriteLine("Rubrica -----")Console.WriteLine("Selezionare l'azione desiderata:")Console.WriteLine("N - Nuovo contatto;")Console.WriteLine("T - Trova contatto;")Console.WriteLine("E - Esci.")Command = Char.ToUpper(Console.ReadKey().KeyChar)Console.Clear()
Select Case Command
Case "N"'Usa ReDim Preserve per aumentare le dimensioni'dell'array mentenendo i dati già presenti.'L'uso di array e di redim, in questo caso, è'sconsigliato, a favore delle più versatili'Liste, che però non ho ancora introdotto.'Ricordate che il valore specificato tra'parentesi indica l'indice massimo e non'il numero di elementi.'Se, all'inizio, Contacts.Length è 0,'richiamando ReDim Contacts(0), si aumenta'la lunghezza dell'array a uno, poiché'in questo caso l'indice massimo è 0,'ossia quello che indica il primo e'l'unico elementoReDim Preserve Contacts(Contacts.Length)
Dim N As ContactConsole.Write("Nome: ")N.Name = Console.ReadLineConsole.Write("Cognome: ")N.Surname = Console.ReadLineConsole.Write("Numero di telefono: ")N.PhoneNumber = Console.ReadLine
'Inserisce nell'ultima cella dell'array'l'elemento appena creatoContacts(Contacts.Length - 1) = N
Case "T"
Dim Part As String
Console.WriteLine("Inserire nome o cognome del " & _"contatto da trovare:")
Part = Console.ReadLine
Or a ammettiamo di voler modificar e il codice per per metter e l'inser imento di più numer i di telefono:
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.
For Each C As Contact In Contacts'Il confronto avviene in modalità'case-insensitive: sia il nome/cognome'che la stringa immessa vengono'ridotti a Lower Case, così da'ignorare la differenza tra'minuscole e maiuscole, qualora presenteIf (C.Name.ToLower() = Part.ToLower()) Or _
(C.Surname.ToLower() = Part.ToLower()) ThenConsole.WriteLine("Nome: " & C.Name)Console.WriteLine("Cognome: " & C.Surname)Console.WriteLine("Numero di telefono: " & C.PhoneNumber)Console.WriteLine()
End IfNext
Case "E"
Exit Do
Case ElseConsole.WriteLine("Comando sconosciuto!")
End SelectConsole.ReadKey()
LoopEnd Sub
End Module
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.
Module Module1Structure Contact
Dim Name, Surname As String'Importante: NON è possibile specificare le dimensioni'di un array dentro la dichiarazione di una struttura.'Risulta chiaro il motivo se ci si pensa un attimo.'Noi stiamo dichiarando quali sono i campi della struttura'e quale è il loro tipo. Quindi specifichiamo che'PhoneNumbers è un array di stringhe, punto. Se scrivessimo'esplicitamente le sue dimensioni lo staremmo creando'fisicamente nella memoria, ma questa è una'dichiarazione, come detto prima, e non una'inizializzazione. Vedremo in seguito che questa'differenza è molto importante per i tipi reference'(ricordate, infatti, che gli array sono tipi reference).Dim PhoneNumbers() As String
End Structure
Sub Main()Dim Contacts(-1) As ContactDim Command As Char
Do
Console.Clear()Console.WriteLine("Rubrica -----")Console.WriteLine("Selezionare l'azione desiderata:")Console.WriteLine("N - Nuovo contatto;")Console.WriteLine("T - Trova contatto;")Console.WriteLine("E - Esci.")Command = Char.ToUpper(Console.ReadKey().KeyChar)Console.Clear()
Select Case Command
Case "N"ReDim Preserve Contacts(Contacts.Length)
Dim N As ContactConsole.Write("Nome: ")N.Name = Console.ReadLineConsole.Write("Cognome: ")N.Surname = Console.ReadLine
'Ricordate che le dimensioni dell'array non
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:
Per acceder e ai campi di Home si sar ebbe utilizzato un ulter ior e punto:
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.
'sono ancora state impostate:ReDim N.PhoneNumbers(-1)
'Continua a chiedere numeri di telefono finché'non si introduce più nullaDo
ReDim Preserve N.PhoneNumbers(N.PhoneNumbers.Length)Console.Write("Numero di telefono " & N.PhoneNumbers.Length & ": ")N.PhoneNumbers(N.PhoneNumbers.Length - 1) = Console.ReadLine
Loop Until N.PhoneNumbers(N.PhoneNumbers.Length - 1) = ""'Ora l'ultimo elemento dell'array è sicuramente'vuoto, lo si dovrebbe togliere.
Contacts(Contacts.Length - 1) = N
Case "T"
Dim Part As String
Console.WriteLine("Inserire nome o cognome del " & _"contatto da trovare:")
Part = Console.ReadLine
For Each C As Contact In ContactsIf (C.Name.ToLower() = Part.ToLower()) Or _
(C.Surname.ToLower() = Part.ToLower()) ThenConsole.WriteLine("Nome: " & C.Name)Console.WriteLine("Cognome: " & C.Surname)Console.WriteLine("Numeri di telefono: ")For Each N As String In C.PhoneNumbers
Console.WriteLine(" - " & N)NextConsole.WriteLine()
End IfNext
Case "E"
Exit Do
Case ElseConsole.WriteLine("Comando sconosciuto!")
End SelectConsole.ReadKey()
LoopEnd Sub
End Module
01.02.03.04.05.06.07.08.09.10.11.
Structure AddressDim State, Town As StringDim Street, CivicNumber As StringDim Cap As String
End Structure Structure Contact
Dim Name, Surname As StringDim PhoneNumbers() As StringDim Home As Address
End Structure
01.02.03.
Dim A As Contact
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ì:
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:
1.2.3.
Class [Nome Classe]'...
End Class
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.
Module Module1'Classe che rappresenta un cubo.'Segue la dichiarazione della classe. Da questo momento'in poi, potremo usare Cube come tipo per le nostre variabili.'Notare che una classe si dichiara e basta, non si'"inizializza", perchè non è qualcosa di concreto,'è un'astrazione, c'è, esiste in generale.Class Cube
'Variabile che contiene la lunghezza del latoDim SideLength As Single'Variabile che contiene la densità del cubo, e quindi'ci dice di che materiale è compostoDim Density As Single
'Questa procedura imposta i valori del lato e'della densitàSub SetData(ByVal SideLengthValue As Single, ByVal DensityValue As Single)
SideLength = SideLengthValueDensity = DensityValue
End Sub
'Questa funzione restituisce l'area di una facciaFunction GetSurfaceArea() As Single
Return (SideLength ^ 2)End Function
'Questa funzione restituisce il volume del cuboFunction GetVolume() As Single
Return (SideLength ^ 3)End Function
'Questa funzione restituisce la massa del cuboFunction GetMass() As Single
Return (Density * GetVolume())End Function
End Class
Sub Main()'Variabile di tipo Cube, che rappresenta uno specifico cubo'La riga di codice che segue contiene la dichiarazione'della variabile A. La dichiarazione di una variabile'fa sapere al compilatore, ad esempio, di che tipo'sarà, in quale blocco di codice sarà'visibile, ma nulla di più.'Non esiste ancora un oggetto Cube collegato ad A, ma'potrebbe essere creato in un immediato futuro.
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:
ma solo passando attr aver so un'istanza:
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 semplice costruttoreAnche 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 ":
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.
'N.B.: quando si dichiara una variabile di tipo reference,'viene comunque allocata memoria sullo stack; viene'infatti creato un puntatore, che punta all'oggetto'Nothing, il cui valore simbolico è stato'spiegato precedentemente.Dim A As Cube
'Ed ecco l'immediato futuro: con la prossima linea di'codice, creiamo l'oggetto di tipo Cube che verrà'posto nella variabile A.A = New Cube'Quando New è seguito dal nome di una classe, si crea un'oggetto di quel tipo. Nella fattispecie, in questo momento'il programma si preoccuperà di richiedere della'memoria sull'heap managed per allocare i dati relativi'all'oggetto e di creare un puntatore sullo stack che'punti a tale oggetto. Esso, inoltre, eseguirà'il codice contenuto nel costruttore. New, infatti,'è uno speciale tipo di procedura, detta'Costruttore, di cui parlerò approfonditamente'in seguito
'Come per le strutture, i membri di classe sono accessibili'tramite l'operatore punto ".". Ora imposto le variabili'contenute in A per rappresentare un cubo di alluminio'(densità 2700 Kg/m<sup>3</sup>) di 1.5m di latoA.SetData(1.5, 2700)
Console.WriteLine("Superficie faccia: " & A.GetSurfaceArea() & " m2")Console.WriteLine("Volume: " & A.GetVolume() & " m3")Console.WriteLine("Massa: " & A.GetMass() & " Kg")'It's Over 9000!!!!
Console.ReadKey()
End SubEnd Module
1. Cube.GetVolume()
1.2.3.
Dim B As New Cube'...B.GetVolume()
1.
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:
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.
Una nota sulle StruttureAnche 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
2.3.
Sub New([parametri])'codice
End Sub
1.2.
Sub New()End Sub
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.
Module Module1Class Cube
Dim SideLength As SingleDim Density As Single
'Quasi uguale a SetDataSub New(ByVal SideLengthValue As Single, ByVal DensityValue As Single)
SideLength = SideLengthValueDensity = DensityValue
End Sub
Function GetSurfaceArea() As SingleReturn (SideLength ^ 2)
End Function
Function GetVolume() As SingleReturn (SideLength ^ 3)
End Function
Function GetMass() As SingleReturn (Density * GetVolume())
End FunctionEnd Class
Sub Main()
'Questa è una sintassi più concisa che equivale a:'Dim A As Cube'A = New Cube(2700, 1.5)'Tra parentesi vanno passati i parametri richiesti dal'costruttoreDim A As New Cube(2700, 1.5)
Console.WriteLine("Superficie faccia: " & A.GetSurfaceArea() & " m<sup>2</sup>")Console.WriteLine("Volume: " & A.GetVolume() & " m<sup>3</sup>")Console.WriteLine("Massa: " & A.GetMass() & " Kg") Console.ReadKey()
End SubEnd Module
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.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.
Module Module1Structure Cube
Dim SideLength As SingleDim Density As Single
Sub New(ByVal SideLengthValue As Single, ByVal DensityValue As Single)
SideLength = SideLengthValueDensity = DensityValue
End Sub
Function GetSurfaceArea() As SingleReturn (SideLength ^ 2)
End Function
Function GetVolume() As SingleReturn (SideLength ^ 3)
End Function
Function GetMass() As SingleReturn (Density * GetVolume())
End FunctionEnd Structure
Sub Main()
'Questo codiceDim A As New Cube(2700, 1.5)
'Equivale a questoDim B As CubeB.SideLength = 1.5B.Density = 2700
'A e B sono uguali
Console.ReadKey()
End SubEnd 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
livello 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 specificator 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 praticoRipr endiamo il codice della classe Cube r ipr oposto nel capitolo pr ecedente. Pr oviamo a scr iver e nella Sub Main questo
codice:
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:
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ì:
1.2.3.4.
Sub Main()Dim A As New Cube(2700, 1.5)A.SideLength = 3
End Sub
1.2.
ConsoleApplication2.Module1.Cube.SideLength' is not accessible in thiscontext because it is 'Private'.
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.
Module Module1Class Cube
'Quando gli specificatori di accesso sono anteposti alla'dichiarazione di una variabile, si toglie il "Dim"Private SideLength As SinglePrivate Density As Single
Sub New(ByVal SideLengthValue As Single, ByVal DensityValue As Single)
SideLength = SideLengthValueDensity = DensityValue
End Sub
Function GetSurfaceArea() As SingleReturn (SideLength ^ 2)
End Function
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 keywor 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:
17.18.19.20.21.22.23.24.25.26.
Function GetVolume() As Single
Return (SideLength ^ 3)End Function
Function GetMass() As Single
Return (Density * GetVolume())End Function
End Class'...
End Module
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.
Public Class Classe1Private Class Classe2
'...End Class
Class Classe3
'...End Class
End Class Class Classe4
Public Class Classe5Private Class Classe6
'...
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).
Specificatori di accesso nelle StruttureAnche 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:
che equivale a:
Ovviamente, anche le str uttur e stesse hanno sempr e uno scope, così come qualsiasi altr a entità del .NET.
Un esempio intelligenteEcco 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!
16.17.18.19.20.21.22.23.
End ClassEnd Class
End Class Module Module1
Sub Main()'...
End SubEnd Module
1.2.3.
Structure ContactDim Name, Surname, PhoneNumber As String
End Structure
1.2.3.
Structure ContactPublic Name, Surname, PhoneNumber As String
End Structure
001.002.003.004.005.006.007.008.009.
Module Module1'Dato che usiamo la classe solo in questo programma, possiamo'evitare di dichiararla Public, cosa che sarebbe ideale in'una libreriaClass BubbleSorter
'Enumeratore pubblico: sarà accessibile da tutti. In questo'caso è impossibile dichiararlo come Private, poiché'uno dei prossimi metodi richiede come parametro una
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 'CrescenteDescending 'DecrescenteNone 'Nessun ordinamento
End Enum
'Mantiene in memoria il senso di ordinamento della lista,'per evitare di riordinarla nel caso fosse richiesto due'volte lo stessoPrivate 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'arrayPrivate Buffer() As Double
'Memorizza in Buffer l'array passato come parametroPublic 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 = NothingEnd If'Copia l'array: ricordate come si comportano i tipi'reference e pensate a quali ripercussioni tale'comportamento potrà avere sul codiceBuffer = Array'Annulla CurrentOrderCurrentOrder = SortOrder.None
End Sub
'Procedura che ordina l'array secondo il senso specificatoPublic Sub Sort(ByVal Order As SortOrder)
'Se il senso è None, oppure è uguale a quello corrente,'è inutile proseguire, quindi si ferma ed esceIf (Order = SortOrder.None) Or (Order = CurrentOrder) Then
Exit SubEnd If
'Questa variabile tiene conto di tutti gli scambi'effettuatiDim 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 = 0For I As Int32 = 0 To Buffer.Length - 2
If Buffer(I) > Buffer(I + 1) ThenDim Temp As Double = Buffer(I)Buffer(I) = Buffer(I + 1)Buffer(I + 1) = TempOccurrences += 1
End IfNext
Loop Until Occurrences = 0
'Se l'ordine era discendente, inverte l'arrayIf Order = SortOrder.Descending Then
Array.Reverse(Buffer)End If
'Memorizza l'ordine
Ricapitolando...Ricapitolando, quindi, davanti a ogni membr o si può specificar e una keywor 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à
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.
CurrentOrder = OrderEnd Sub
'Restituisce l'array ordinatoPublic Function PopArray() As Double()
Return BufferEnd Function
End Class
Sub Main()'Crea un array temporaneoDim a As Double() = {1, 6, 2, 9, 3, 4, 8}'Crea un nuovo oggetto BubbleSorterDim b As New BubbleSorter()
'Vi inserisce l'arrayb.PushArray(a)'Invoca la procedura di ordinamentob.Sort(BubbleSorter.SortOrder.Descending)
'E per ogni elemento presente nell'array finale'(quello restituito dalla funzione PopArray), ne stampa'il valore a schermoFor Each n As Double In (b.PopArray())
Console.Write(n & " ")Next
Console.ReadKey()
End SubEnd Module
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:
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.02.03.04.05.06.07.08.09.
Property [Nome]() As [Tipo]Get
'...Return [Valore restituito]
End GetSet(ByVal value As [Tipo])
'...End Set
End Property
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.
Module Module1Class Example
'Campo pubblico di tipo Single.Public _Number As Single
'La proprietà Number media, in questo caso, l'uso'del campo _Number.Public Property Number() As Single
Get'Quando viene chiesto il valore di Number, viene'restituito il valore della variabile _Number. Si'vede che la proprietà non fa altro che manipolare'una variabile esistente e non contiene alcun'dato di per séReturn _Number
End GetSet(ByVal value As Single)
'Quando alla proprietà viene assegnato un valore,'essa modifica il contenuto di _Number impostandolo'esattamente su quel valore_Number = value
End SetEnd Property
End Class
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 nascosta 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:
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.
Sub Main()
Dim A As New Example()
'Il codice di Main sta impostando il valore di A.Number.'Notare che una proprietà si usa esattamente come una'comunissima variabile di istanza.'La proprietà, quindi, richiama il suo blocco Set come'una procedura e assegna il valore 20 al campo A._NumberA.Number = 20 'Nella prossima riga, invece, viene richiesto il valore'di Number per poterlo scrivere a schermo. La proprietà'esegue il blocco Get come una funzione e restituisce al'chiamante (ossia il metodo/oggetto che ha invocato Get,'in questo caso Console.WriteLine) il valore di A._NumberConsole.WriteLine(A.Number) 'Per gli scettici, facciamo un controllo per vedere se'effettivamente il contenuto di A._Number è cambiato.'Potrete constatare che è uguale a 20.Console.WriteLine(A._Number)
Console.ReadLine()
End SubEnd Module
01.02.03.04.05.06.07.08.09.10.11.12.13.
Module Module1'Questa classe rappresenta un semplice sistema inerziale,'formato da un piano orizzontale scabro (con attrito) e'una massa libera di muoversi su di essoClass InertialFrame
Private _DynamicFrictionCoefficient As SinglePrivate _Mass As SinglePrivate _GravityAcceleration As Single
'Coefficiente di attrito radente (dinamico), μPublic Property DynamicFrictionCoefficient() As Single
Get
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:
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.
Return _DynamicFrictionCoefficientEnd GetSet(ByVal value As Single)
_DynamicFrictionCoefficient = valueEnd Set
End Property
'Massa, mPublic Property Mass() As Single
GetReturn _Mass
End GetSet(ByVal value As Single)
_Mass = valueEnd Set
End Property
'Accelerazione di gravità che vale nel sistema, gPublic Property GravityAcceleration() As Single
GetReturn _GravityAcceleration
End GetSet(ByVal value As Single)
_GravityAcceleration = valueEnd Set
End Property
'Calcola e restituisce la forza di attrito che agisce'quando la massa è in motoPublic Function CalculateFrictionForce() As Single
Return (Mass * GravityAcceleration) * DynamicFrictionCoefficientEnd 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.ReadLineConsole.Write("Massa (Kg) = ")F.Mass = Console.ReadLineConsole.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 SubEnd Module
01.02.03.04.05.06.07.08.09.
Module Module1Class InertialFrame
Private _DynamicFrictionCoefficient As SinglePrivate _Mass As SinglePrivate _GravityAcceleration As Single
Public Property DynamicFrictionCoefficient() As Single
Get
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.
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.
Return _DynamicFrictionCoefficientEnd GetSet(ByVal value As Single)
If (value > 0) And (value <= 1) Then_DynamicFrictionCoefficient = value
ElseConsole.WriteLine(value & " non è un valore consentito!")Console.WriteLine("Coefficiente attrito dinamico = 0.1")_DynamicFrictionCoefficient = 0.1
End IfEnd Set
End Property
Public Property Mass() As SingleGet
Return _MassEnd GetSet(ByVal value As Single)
If value > 0 Then_Mass = value
ElseConsole.WriteLine(value & " non è un valore consentito!")Console.WriteLine("Massa = 1")_Mass = 1
End IfEnd Set
End Property
Public Property GravityAcceleration() As SingleGet
Return _GravityAccelerationEnd GetSet(ByVal value As Single)
_GravityAcceleration = Math.Abs(value)End Set
End Property
Public Function CalculateFrictionForce() As SingleReturn (Mass * GravityAcceleration) * DynamicFrictionCoefficient
End Function
End Class
'...End Module
Come esempio user ò questa pr opr ietà:
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 :
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 :
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
oppur e
viene sostituita con la cor r ispondente r iga:
oppur e:
Ad esempio, il seguente codice:
viene tr asfor mato, dur ante la compilazione, in:
01.02.03.04.05.06.07.08.09.10.11.12.
Property Number() As SingleGet
Return _NumberEnd GetSet(ByVal value As Single)
If (value > 30) And (value < 100) Then_Number = value
Else_Number = 31
End IfEnd Set
End Property
1.2.3.
Function get_Number() As SingleReturn _Number
End Function
1.2.3.4.5.6.7.
Sub set_Number(ByVal value As Single)If (value > 30) And (value < 100) Then
_Number = valueElse
_Number = 31End If
End Sub
1. [Proprietà] = [Valore]
1. [Valore] = [Proprietà]
1. set_[Nome Proprietà]([Valore])
1. [Valore] = get_[Nome Proprietà]
1.2.3.
Dim A As New ExampleA.Number = 20Console.WriteLine(A.Number)
1.2.3.
Dim A As New ExampleA.set_Number(20)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 amewor k .NET.
A21. Le Proprietà - Parte II
Proprietà ReadOnly e WriteOnlyFin'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 keywor 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:
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:
1.2.3.4.5.6.
ReadOnly Property [Nome]() As [Tipo]Get
'...Return [Valore]
End GetEnd Property
01.02.03.04.05.06.
Class CubePrivate _SideLength As SinglePrivate _Density As Single Public Property SideLength() As Single
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:
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.
GetReturn _SideLength
End GetSet(ByVal value As Single)
If value > 0 Then_SideLength = value
Else_SideLength = 1
End IfEnd Set
End Property Public Property Density() As Single
GetReturn _Density
End GetSet(ByVal value As Single)
If value > 0 Then_Density = value
Else_Density = 1
End IfEnd Set
End Property Public ReadOnly Property SurfaceArea() As Single
GetReturn (SideLength ^ 2)
End GetEnd Property Public ReadOnly Property Volume() As Single
GetReturn (SideLength ^ 3)
End GetEnd Property Public ReadOnly Property Mass() As Single
GetReturn (Volume * Density)
End GetEnd Property
End Class
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.17.18.19.20.21.
Module Esempio3Class LogFile
Private _FileName As StringPrivate _CreationTime As Date
'Niente deve modificare il nome del file, altrimenti'potrebbero verificarsi errori nella lettura o scrittura'dello stesso, oppure si potrebbe chiudere un file'che non esiste ancoraPublic ReadOnly Property FileName() As String
GetReturn _FileName
End GetEnd Property
'Allo stesso modo non si pu� modificare la data di'creazione di un file: una volta creato, viene'prelevata l'ora e il giorno e impostata la'variabile. Se potesse essere modificata'non avrebbe più alcun significato
Una nota sui tipi referenceC'è 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:
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 egnando 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.
22.23.24.25.26.27.28.29.30.31.32.
Public ReadOnly Property CreationTime() As DateGet
Return _CreationTimeEnd Get
End Property
Public Sub New(ByVal Path As String)_FileName = Path_CreationTime = Date.Now
End SubEnd Class
End Module
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.
Class ASystemPrivate _Box As Cube Public ReadOnly Property Box() As Cube
GetReturn _Box
End GetEnd Property '...
End Class '... Dim S As New ASystem()S.Box.SideLength = 4
A22. Le Proprietà - Parte III
Proprietà con parametriNei 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.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.50.51.52.53.54.55.56.
Module Module1'Classe che rappresenta un estrattore di numeri'casualiClass NumberExtractor
Private _ExtractedNumbers() As Byte'Generatore di numeri casuali. Random è una classe'del namespace SystemPrivate Rnd As Random
'Questa proprietà ha un parametro, Index, che'specifica a quale posizione dell'array si intende recarsi'per prelevarne il valore. Nonostante l'array abbia solo 6'elementi di tipo Byte, l'indice viene comunemente sempre'indicato come intero a 32 bit. È una specie di'convenzione, forse derivante dalla maggior facilità di'elaborazione su macchine a 32 bitPublic ReadOnly Property ExtractedNumbers(ByVal Index As Int32) As Byte
GetIf (Index >= 0) And (Index < _ExtractedNumbers.Length) Then
Return _ExtractedNumbers(Index)Else
Return 0End If
End GetEnd Property
Public Sub New()
'Essendo di tipo reference, si deve creare un nuovo'oggetto Random e assegnarlo a Rnd. La ragione per cui'Rnd è un membro di classe consiste nel fatto'che se fosse stata variabile temporanea del corpo'della procedura ExtractNumbers, sarebbero usciti'gli stessi numeri. Questo perchè la sequenza'pseudocasuale creata dalla classe non cambia se'non glielo si comunica espressamente usando un altro'costruttore. Non tratterò questo argomento oraRnd = New Random()ReDim _ExtractedNumbers(5)
End Sub
Public Sub ExtractNumbers()'Estrae 6 numeri casuali tra 1 e 90 e li pone nell'arrayFor I As Int32 = 0 To 5
_ExtractedNumbers(I) = Rnd.Next(1, 91)Next
End SubEnd Class
Sub Main()
Dim E As New NumberExtractor()
E.ExtractNumbers()Console.WriteLine("Numeri estratti: ")For I As Int32 = 0 To 5
Console.Write(E.ExtractedNumbers(I) & " ")
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:
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 defaultCon 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:
57.58.59.60.
Next
Console.ReadKey()End Sub
End Module
1.2.3.4.5.
Public ReadOnly Property ExtractedNumbers() As Byte()Get
Return _ExtractedNumbersEnd Get
End Property
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.
Module Module1Class NumberExtractor
Private _ExtractedNumbers() As BytePrivate Rnd As Random
'Una proprietà di default si dichiara come una'normalissima proprietà, ma anteponendo allo specificatore'di accesso la keyword DefaultDefault Public ReadOnly Property ExtractedNumbers(ByVal Index As Int32) As Byte
GetIf (Index >= 0) And (Index < _ExtractedNumbers.Length) Then
Return _ExtractedNumbers(Index)Else
Return 0End If
End GetEnd Property
Public Sub New()
Rnd = New Random()ReDim _ExtractedNumbers(5)
End Sub
Public Sub ExtractNumbers()For I As Int32 = 0 To 5
_ExtractedNumbers(I) = Rnd.Next(1, 91)Next
End SubEnd Class
Sub Main()
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 indows 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:
Ebbene, Char s è una pr opr ietà di default, ossia è possibile scr iver e:
Get e Set con spec ificatori di accessoAnche 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 keywor 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
33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.
Dim E As New NumberExtractor()
E.ExtractNumbers()Console.WriteLine("Numeri estratti: ")For I As Int32 = 0 To 5
'Ecco l'utilità delle proprietà di default: si possono'richiamare anche omettendone il nome. In questo caso'E(I) è equivalente a scrivere E.ExtractedNumbers(I),'ma poiché ExtractedNumbers è di default,'viene desunta automaticamenteConsole.Write(E(I) & " ")
Next
Console.ReadKey()End Sub
End Module
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
1.2.3.
Dim S As String = "Ciao"Dim C As Char = S(1)' > C = "i"
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:
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 keywordIn questi ultimi capitoli ho spiegato un bel po' di keywor d diver se, e specialmente nelle pr opr ietà può accader e di dover
specificar e molte keywor 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):
E or a quelle che conoscete sono ancor a poche... pr ovate voi a scr iver e una pr opr ietà del gener e:
N.B.: ovviamente, tutto quello che si è detto fin'or a sulle pr opr ietà nelle classi vale anche per le str uttur e!
1.2.3.4.5.6.7.8.
Public Property A() As ByteGet
'...End GetPrivate Set(ByVal value As Byte)
'...End Set
End Property
1. [Default] [ReadOnly/WriteOnly] [Public/Friend/Private/...] Property ...
1.
2.3.4.5.
Default Protected Friend Overridable Overloads ReadOnly Property A(ByVal Index As Int32) AsByteGet
'...End Get
End Property
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 uno 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 shared, o condiv 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.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.50.
Module Module1
Class CubePrivate _SideLength As SinglePrivate _Density As Single
'Questo campo è Shared, condiviso. Come vedete,'per dichiarare un membro come tale, si pone la'keyword Shared dopo lo specificatore di accesso. Questa'variabile conterrà il numero di cubi creati'dal nostro programma.'N.B.: I campi Shared sono di default Private...Private Shared _CubesCount As Int32 = 0
Public Property SideLength() As Single
GetReturn _SideLength
End GetSet(ByVal value As Single)
If value > 0 Then_SideLength = value
Else_SideLength = 1
End IfEnd Set
End Property
Public Property Density() As SingleGet
Return _DensityEnd GetSet(ByVal value As Single)
If value > 0 Then_Density = value
Else_Density = 1
End IfEnd Set
End Property
Public ReadOnly Property SurfaceArea() As SingleGet
Return (SideLength ^ 2)End Get
End Property
Public ReadOnly Property Volume() As SingleGet
Return (SideLength ^ 3)
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;
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 GetEnd Property
Public ReadOnly Property Mass() As Single
GetReturn (Volume * Density)
End GetEnd Property
'Allo stesso modo, la proprietà che espone il membro'shared deve essere anch'essa sharedPublic Shared ReadOnly Property CubesCount() As Int32
GetReturn _CubesCount
End GetEnd 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 memoriaSub New()
_CubesCount += 1End Sub
End Class Sub Main()
Dim Cube1 As New Cube()Cube1.SideLength = 1Cube1.Density = 2700
Dim Cube2 As New Cube()Cube2.SideLength = 0.9Cube2.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'modoConsole.WriteLine(Cube.CubesCount)
Console.ReadKey()
End SubEnd Module
Quando si tr ova in un modulo, come spiegher ò nel pr ossimo par agr afo.
Classi SharedCome!?!? 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é:
Il codice sopr a r ipor tato pr ovocher à il seguente er r or e:
Inoltr e, è anche possibile acceder e a membr i di un modulo senza specificar e il nome del modulo, ad esempio:
Questo fenomeno è anche noto col nome di Imports 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:
1.2.3.4.5.6.7.
Module Module1Shared Sub Hello() End Sub '...
End Sub
1. Methods in a Module cannot be declared 'Shared'.
01.02.03.04.05.06.07.08.09.10.11.12.
Module Module2Sub Hello()
Console.WriteLine("Hello!")End Sub
End Module Module Module1
Sub Main()Hello() ' = Module2.Hello()Console.ReadKey()
End SubEnd Module
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.
Module Module2Sub Hello()
Console.WriteLine("Hello Module2!")End Sub
End Module Class Class2
Shared Sub Hello()Console.WriteLine("Hello Class2!")
End SubEnd Class Module Module1
Sub Main()
16.17.18.19.20.21.22.23.
'Per richiamare l'Hello di Class2, è sempre'necessaria questa sintassi:Class2.Hello()'Per invocare l'Hello di Module2, invece, basta'questa, a causa dell'Imports StaticoHello()Console.ReadKey()
End SubEnd 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.
ArrayListSi 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ì:
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 espande 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:
1. Dim A(100) As Int32
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.
Module Module1
Class Cube'...
End Class Sub Main()
'Crea un nuovo arraylistDim Cubes As New ArrayList
Console.WriteLine("Inserismento cubi:")Console.WriteLine()Dim Cmd As CharDo
Console.WriteLine()Dim C As New Cube'Scrive il numero del cuboConsole.Write((Cubes.Count + 1) & " - ")Console.Write("Lato (m): ")C.SideLength = Console.ReadLine
Console.Write(" Densità (kg/m<sup>3</sup>): ")C.Density = Console.ReadLine
'Aggiunge un nuovo cubo alla collezioneCubes.Add(C)
Console.WriteLine("Termina inserimento? y/n")
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.
HashtableL'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 chiave (key) con un valore (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:
Notar e che è anche possibile far e il contr ar io, ossia:
30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.
Cmd = Console.ReadKey().KeyCharLoop Until Char.ToLower(Cmd) = "y"
'Calcola la massa totale di tutti i cubi nella listaDim TotalMass As Single = 0'Notate che l'ArrayList si può usare come un'normale array. L'unica differenza sta nel fatto che'esso espone la proprietà Count al posto di Length.'In genere, tutte le liste espongono Count, che comunque'ha sempre lo stesso significato: restituisce il numero'di elementi nella listaFor I As Int32 = 0 To Cubes.Count - 1
TotalMass += Cubes(I).MassNext
Console.WriteLine("Massa totale: " & TotalMass)Console.ReadKey()
End SubEnd Module
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 stringaH.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"))
1.2.
Dim H As New Hashtable
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 nulla 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:
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.
SortedListSi compor ta esattamente come un Hashtable, solo che gli elementi vengono mantenuti sempr e in or dine secondo la
chiave.
3.4.5.
H.Add(1000, "Acqua")H.Add(2700, "Alluminio")H.Add(10490, "Argento")H.Add(8800, "Nichel")
1.2.3.4.
For Each V As String In H.Values'Enumera tutti gli elementi di H' V = "Acqua", "Alluminio", "Argento", ...
Next
1.2.3.4.
For Each K As Int32 In H.Keys'Enumera tutte le chiavi'K = 1000, 2700, 10490, ...
Next
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 ima 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.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.
Module Module1Class Document
'Campo statico che contiene tutti i documenti'aperi fin'oraPrivate Shared Documents As New Hashtable'Identificatore del documento: un paragrafo nel prossimo'capitolo spiegherà in dettaglio i significato e'l'utilità delle variabili ReadOnlyPrivate ReadOnly _ID As Int16'Nome del file e testo contenuto in essoPrivate ReadOnly _FileName, _Text As String
Public ReadOnly Property ID() As Int16
GetReturn _ID
End GetEnd Property
Public ReadOnly Property FileName() As String
GetReturn _FileName
End GetEnd Property
Public ReadOnly Property Text() As String
GetReturn _Text
End GetEnd Property
'Da notare il costruttore Private: nessun client al di'fuori della classe può inizializzare il nuovo'oggetto. Solo il metodo factory lo può farePrivate Sub New(ByVal ID As Int16, ByVal Path As String)
Me._ID = IDMe._FileName = PathMe._Text = IO.File.ReadAllText(Path)'Me fa riferimento alla classe stessaDocuments.Add(ID, Me)
End Sub
'Il metodo factory crea un documento se non esiste l'ID'e se il percorso su disco è diverso, altrimenti'restituisce il documento che esiste giàPublic Shared Function Create(ByVal ID As Int16, _
ByVal Path As String) As DocumentIf Documents.ContainsKey(ID) Then
'Ottiene il documento già esistente con questo ID
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:
Classi factory e oggetti immutabiliUna 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 immutabili: 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:
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.
Dim D As Document = Documents(ID)'Se coincidono sia l'ID che il nome del file,'allora restituisce l'oggetto già esistenteIf D.FileName = Path Then
Return DElse
'Altrimenti restituisce Nothing, dato che non'possono esistere due documenti con uguale ID,'o si farebbe confusioneReturn Nothing
End IfEnd If'Se non esiste un documento con questo ID, lo creaReturn New Document(ID, Path)
End FunctionEnd Class
Sub Main()
Dim D As Document = Document.Create(0, "C:\testo.txt")Dim E As Document = Document.Create(0, "C:\testo.txt")Dim F As Document = Document.Create(0, "C:\file.txt")Dim G As Document = Document.Create(1, "C:\file.txt")
'Dimostra che se ID e Path coincidono, i due oggetti'sono la stessa istanzaConsole.WriteLine(E Is D)'Dimostra che se l'ID esiste già, ma il Path differisce,'l'oggetto restituito è NothingConsole.WriteLine(F Is Nothing)Console.ReadKey()
End SubEnd Module
1.2.
TrueTrue
1.2.3.4.5.
'Questa stringa è immutabileDim S As String = "Ciao"'Viene creata una nuova stringa temporanea con valore "Buongiorno"'e assegnata a S. "Ciao" verrà distrutta dal Garbage ColletcionS = "Buongiorno"
A26. Costruttori
Come si è accennato nelle pr ecedenti lezioni, i costr uttor i ser vono a cr ear e un oggetto, un'istanza mater iale della
classe. Ogni costr uttor e, poichè ce ne può esser e anche più di uno, è sempr e dichiar ato usando la keywor 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:
Quando si fa cor r er e il pr ogr amma si ha questo output:
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.
Variabili ReadOnlyHo 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,
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.
Module Module1'ClasseClass Esempio
'Costante pubblicaPublic Const Costante As Byte = 56'Variabile pubblica che non pu� essere modificataPublic ReadOnly Nome As String'Variabile privataPrivate Variabile As Char
'Costruttore della classe: accetta un parametroSub New(ByVal Nome As String)
Console.WriteLine("Sto inizializzando un oggetto Esempio...")'Le variabili ReadOnly sono assegnabli solo nel'costruttore della classeMe.Nome = NomeMe.Variabile = "c"
End SubEnd Class
'Costruttore del ModuloSub New()
Console.WriteLine("Sto inizializzando il Modulo...")End Sub
Sub Main()
Dim E As New Esempio("Ciao")E.Nome = "Io" ' Sbagliato: Nome è ReadOnlyConsole.ReadKey()
End SubEnd Module
1.2.
Sto inizializzando il Modulo...Sto inizializzando un oggetto Esempio...
cosa più impor tante, possono assumer e il valor e der ivante da un'espr essione o da una funzione. Ad esempio:
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 SharedI costr uttor i Shar ed sono detti costruttor i statici e vengono eseguiti solamente quando è cr eata la pr ima istanza di
una data classe: per questo sono detti anche costruttor 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:
L'ouput è:
1.2.
Public Const Data_Creazione_C As Date = Date.Now 'Sbagliato!Public ReadOnly Data_Creazione_V As Date = Date.Now ' Giusto
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.
Module EsempioClass Errors
'Variabile statica che rappresenta un oggetto in grado'di scrivere su un filePublic Shared File As IO.StreamWriter
'Costruttore statico che inizializza l'oggetto StreamWriter'Da notare è che un costruttore statico NON può avere'parametri: il motivo è semplice. Se li potesse avere'e ci fossero più costruttori normali il compilatore'non saprebbe cosa fare, poichè Shared Sub New'potrebbe avere parametri diversi dagli altriShared Sub New()
Console.WriteLine("Costruttore statico: sto creando il log...")File = New IO.StreamWriter("Errors.log")
End Sub
'Questo è il costruttore normaleSub New()
Console.WriteLine("Costruttore normale: sto creando un oggetto...")End Sub
Public Sub WriteLine(ByVal Text As String)
File.WriteLine(Text)End Sub
End Class
Sub Main()'Qui viene eseguito il costruttore statico e quello normaleDim E1 As New Errors'Qui solo quello normaleDim E2 As New Errors
E1.WriteLine("Nessun errore")
Console.ReadKey()
End SubEnd Module
1.2.3.
Costruttore statico: sto creando il log...Costruttore normale: sto creando un oggetto...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 PrivateI 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 amewor 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:
Come si vede, la sintassi è molto simile a quella usata per dichiar ar e una funzione, ad eccezione della keywor 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 SEMPRE 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 massimo 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 deve 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)
1.2.3.4.
Shared Operator [Simbolo]([Parametri]) As [Tipo Restituito]'...Return [Risultato]
End Operator
< (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.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.
Module Module1
Public Structure Fraction'Numeratore e denominatorePrivate _Numerator, _Denumerator As Int32
Public Property Numerator() As Int32
GetReturn _Numerator
End GetSet(ByVal value As Int32)
_Numerator = valueEnd Set
End Property
Public Property Denumerator() As Int32Get
Return _DenumeratorEnd GetSet(ByVal value As Int32)
If value <> 0 Then_Denumerator = value
Else'Il denominatore non può mai essere 0'Dovremmo lanciare un'eccezione, ma vedremo più'avanti come si fa. Per ora lo impostiamo a uno_Denumerator = 1
End IfEnd Set
End Property
'Costruttore con due parametri, che inizializza numeratore'e denominatoreSub New(ByVal N As Int32, ByVal D As Int32)
Me.Numerator = NMe.Denumerator = D
End Sub
'Restituisce la Fraction sottoforma di stringaFunction Show() As String
Return Me.Numerator & " / " & Me.DenumeratorEnd Function
'Semplifica la FractionSub Semplify()
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 divisoreX = 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 -1If (Me.Numerator < 0) And (Me.Denumerator < 0) Then
Me.Numerator /= -1Me.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 MCDDo Until ((Me.Numerator Mod X = 0) And (Me.Denumerator Mod X = 0))
X -= 1Loop
'Divide numeratore e denominatore per l'MCDMe.Numerator /= XMe.Denumerator /= X
End Sub
'Somma due frazioni e restituisce la sommaShared Operator +(ByVal F1 As Fraction, ByVal F2 As Fraction) _
As FractionDim F3 As Fraction
'Se i denumeratori sono uguali, si limita a sommare'i numeratoriIf F1.Denumerator = F2.Denumerator Then
F3.Denumerator = F1.DenumeratorF3.Numerator = F1.Numerator + F2.Numerator
Else'Altrimenti esegue tutta l'operazione'x a x*b + a*y'- + - = ---------'y b y*bF3.Denumerator = F1.Denumerator * F2.DenumeratorF3.Numerator = F1.Numerator * F2.Denumerator + F2.Numerator * F1.Denumerator
End If
'Semplifica la FractionF3.Semplify()Return F3
End Operator
'Sottrae due Fraction e restituisce la differenzaShared Operator -(ByVal F1 As Fraction, ByVal F2 As Fraction) _
As Fraction'Somma l'opposto del secondo membroF2.Numerator = -F2.NumeratorReturn F1 + F2
End Operator
'Moltiplica due frazioni e restituisce il prodottoShared 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'denominatoriDim F3 As Fraction = New Fraction(F1.Numerator * F2.Numerator, _
F1.Denumerator * F2.Denumerator)
CTypeCType è 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:
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 sempre se una conver sione è Widening (di espansione, ossia senza per dita di dati) o Narrowing (di
r iduzione, con possibile per dita di dati). Per questo motivo si deve specificar e una delle suddette keywor 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:
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.
F3.Semplify()Return F3
End Operator
'Divide due frazioni e restituisce il quozienteShared Operator /(ByVal F1 As Fraction, ByVal F2 As Fraction) _
As Fraction'Inizializza F3 eseguendo l'operazione:'a x a y'- / - = - * -'b y b xDim F3 As Fraction = New Fraction(F1.Numerator * F2.Denumerator, _
F1.Denumerator * F2.Numerator)F3.Semplify()Return F3
End OperatorEnd 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 + BConsole.WriteLine("A + B = " & C.Show())
Console.ReadKey()
End SubEnd Module
1.2.3.
Dim F As Fraction'...Dim D As Double = CType(F, Double)
1.2.3.
Shared Widening Operator CType(ByVal F As Fraction) As DoubleReturn F.Numerator / F.Denumerator
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:
Operatori di confrontoGli 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.
È 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.
Shared Narrowing Operator CType(ByVal F As Fraction) As Int32'Notare l'operatore \ di divisione intera (per maggiori'informazioni sulla divisione intera, vedere capitolo A6)Return F.Numerator \ F.Denumerator
End Operator
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 valoriReturn (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
1.2.3.4.5.
'...a = 10b = 20Console.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 amewor 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è.
MemorizzazioneIniziamo 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
sempre 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, valor 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 oggettiEd 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 ivata, 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 ereditar ietà indiretta.
Passiamo or a a veder e come si dichiar a una classe der ivata:
La keywor 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à:
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
1.2.3.4.
Class [Nome]Inherits [Classe base]'Membri della classe
End Class
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
GetReturn FirstName & " " & LastName
End GetEnd Property
End Class 'Lo studente, ovviamente, è una personaClass Student
'Student eredita da PersonInherits Person
'In più, definisce anche questi campi pubblici'La scuola frequentataPublic School As String'E l'anno di corsoPublic Grade As Byte
End Class
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 ivate 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 Protected 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.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.
Module EsempioClass Person
'Due campi protectedProtected _FirstName, _LastName As String'Un campo private readonly: non c'è ragione di rendere'questo campo Protected poichè la data di nascita non'cambia ed è sempre accessibile tramite la proprietà'pubblica BirthDayPrivate ReadOnly _BirthDay As Date
Public Property FirstName() As String
GetReturn _FirstName
End GetSet(ByVal Value As String)
If Value <> "" Then_FirstName = Value
End IfEnd Set
End Property
Public Property LastName() As StringGet
Return _LastNameEnd GetSet(ByVal Value As String)
If Value <> "" Then
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 = ValueEnd If
End SetEnd Property
Public ReadOnly Property BirthDay() As Date
GetReturn _BirthDay
End GetEnd Property
Public ReadOnly Property CompleteName() As String
GetReturn _FirstName & " " & _LastName
End GetEnd Property
'Costruttore che accetta tra parametri obbligatoriSub New(ByVal FirstName As String, ByVal LastName As String, _
ByVal BirthDay As Date)Me.FirstName = FirstNameMe.LastName = LastNameMe._BirthDay = BirthDay
End SubEnd Class
'Lo studente, ovviamente, è una personaClass Student
'Student eredita da PersonInherits Person
'La scuola frequentataPrivate _School As String'E l'anno di corsoPrivate _Grade As Byte
Public Property School() As String
GetReturn _School
End GetSet(ByVal Value As String)
If Value <> "" Then_School = Value
End IfEnd Set
End Property
Public Property Grade() As ByteGet
Return _GradeEnd GetSet(ByVal Value As Byte)
If Value > 0 Then_Grade = Value
End IfEnd 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 studentePublic 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 baseReturn _FirstName & " " & _LastName & ", nato il " & _BirthDay.ToShortDateString & " frequenta l'anno " & __Grade & " alla scuola " & _School
End GetEnd Property
L'output:
(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 sempre r ichiamar e il costr uttor e della classe base, e questo avviene tr amite la keywor 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.
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.
'Altra clausola importante: il costruttore della classe'derivata deve sempre richiamare il costruttore della'classe baseSub 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 = SchoolMe.Grade = Grade
End SubEnd 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 spiegateConsole.WriteLine(S.CompleteName)'E in più ha anche i suoi nuovi membriConsole.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 PersonP = SConsole.WriteLine(P.CompleteName)
Console.ReadKey()
End SubEnd Module
1.2.3.4.5.
Pinco PallinoTizio CaioTizio Caio, nato il 23/5/1992 frequenta l'anno 2 alla scuola Liceo Classico UgoFoscoloTizio Caio
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 keywor 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 nome
ug uale e sig nature 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 ShowTex 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:
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:
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
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.
Module Module1Class Person
Protected _FirstName, _LastName As StringPrivate ReadOnly _BirthDay As Date
Public Property FirstName() As String
GetReturn _FirstName
End GetSet(ByVal Value As String)
If Value <> "" Then_FirstName = Value
End IfEnd Set
End Property
'Questa proprietà sarà ridefinita nella classe TeacherPublic Overridable Property LastName() As String
GetReturn _LastName
End GetSet(ByVal Value As String)
If Value <> "" Then_LastName = Value
End IfEnd Set
End Property
Public ReadOnly Property BirthDay() As DateGet
Return _BirthDayEnd Get
End Property
'Questa proprietà sarà ridefinita nella classe TeacherPublic Overridable ReadOnly Property CompleteName() As String
GetReturn _FirstName & " " & _LastName
End Get
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 obbligatoriSub New(ByVal FirstName As String, ByVal LastName As String, _
ByVal BirthDay As Date)Me.FirstName = FirstNameMe.LastName = LastNameMe._BirthDay = BirthDay
End SubEnd Class
Class Teacher
Inherits PersonPrivate _Subject As String
Public Property Subject() As String
GetReturn _Subject
End GetSet(ByVal Value As String)
If Value <> "" Then_Subject = Value
End IfEnd Set
End Property
'Ridefinisce la proprietà LastName in modo da aggiungere'anche il titolo di Professore al cognomePublic Overrides Property LastName() As String
GetReturn "Prof. " & _LastName
End GetSet(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 ValueMyBase.LastName = Value
End SetEnd Property
'Ridefinisce la proprietà CompleteName in modo da'aggiungere anche la materia insegnata e il titolo di'ProfessorePublic Overrides ReadOnly Property CompleteName() As String
Get'Anche qui viene richiamata la vecchia versione di'CompleteName, che restituisce semplicemente il'nome completoReturn "Prof. " & MyBase.CompleteName & _", dottore in " & Subject
End GetEnd 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 SubEnd Class
Sub Main()
Dim T As New Teacher("Mario", "Rossi", Date.Parse("01/01/1950"), _"Letteratura italiana")
'Usiamo le nuove proprietà, ridefinite nella classe'derivataConsole.WriteLine(T.LastName)'> "Prof. Rossi"
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:
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:
È 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:
Ultime due pr ecisazioni: le var iabili non possono subir e polimor fismo, così come i membr i statici.
113.114.115.116.117.
Console.WriteLine(T.CompleteName)'> "Prof. Mario Rossi, dottore in Letteratura italiana"
Console.ReadKey()
End SubEnd Module
1.2.3.
Dim P As Person = TConsole.WriteLine(P.LastName)Console.WriteLine(P.CompleteName)
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
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.17.18.19.20.21.
Class APublic 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 basePublic Overrides Sub ShowText()
Console.WriteLine("B: Testo di prova")End Sub
End Class
ShadowingSe 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 keywor d da
usar e è Shadows, 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:
Come si vede, la sintassi è come quella di Over r ides: Shadows 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 Shadows, viene omessa la keywor d Dim.
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.
Module EsempioClass Base
Friend Control As ByteEnd Class
Class Deriv
Inherits BasePublic Shadows Sub Control(ByVal Msg As String)
Console.WriteLine("Control, seconda versione: " & Msg)End Sub
End Class
Sub Main()Dim B As New BaseDim D As New Deriv
'Entrambe le classe hanno lo stesso membro di nome'"Control", ma nella prima è un campo friend,'mentre nella seconda è una procedura pubblicaConsole.WriteLine(B.Control)D.Control("Ciao")
Console.ReadKey()
End SubEnd Module
A31. Conversioni di dati
Il Fr amewor 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 widening (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, narrowing
(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.
CTypeCType è 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:
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.
DirectCastDir 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:
1.2.3.
Dim I As Int32 = 50'Converte I in un valore ByteDim B As Byte = CType(I, Byte)
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.
TryCastTr 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.
ConvertEsiste, poi, una classe statica definita del namespace System - il namespace più impor tante di tutto il Fr amewor 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:
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 wikipedia.
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.
01.02.03.04.05.06.07.08.09.
Dim I As Int32 = 34Dim D As Double = Convert.ToDouble(I)' D = 34.0Dim S As String = Convert.ToString(D)' S = "34"Dim N As Single = Convert.ToSingle(S)' N = 34.0Dim K As String = "31/12/2008"Dim A As Date = Convert.ToDate(K)
ParseUn'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 amewor 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:
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:
Per fetto: niente er r or i di conver sione e tutto come ci si aspettava!
TryParseUna 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:
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.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!
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
1. TryParse(ByVal s As String, ByRef result As [Tipo]) As Boolean
01.02.03.04.05.06.07.08.09.
Dim S As String = "56/0/1000"'S contiene una data non validaDim D As Date If Date.TryParse(S, D) Then
Console.WriteLine(D.ToLongDateString())Else
Console.WriteLine("Data non valida!")End If
TypeOfTypeOf 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:
Oppur e:
Ed infine un esempio sulle inter facce, che potr ete tor nar e a guar dar e da qui a qualche capitolo:
1.2.3.4.
Dim I As Int32If TypeOf I Is Int32 Then'Questo blocco viene eseguito poichè I è di tipo Int32
End If
1.2.3.4.5.6.
Dim T As StudentIf TypeOf T Is Person Then
'Questo blocco viene eseguito perchè T, essendo Student, è'anche di tipo Person, in quanto Student è una sua classe'derivata
End If
1.2.3.4.5.
Dim K(9) As Int32If TypeOf Is IEnumerable Then
'Questo blocco viene eseguito poiché gli array implementano'sempre l'interfaccia IEnumerable
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:
Il pr imo par ametr o pr ende il nome di str ing a di formato, 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:
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:
1. Console.WriteLine("stringa", arg0, arg1, arg2, arg3, ...)
1.2.3.4.
A = 1B = 3Console.WriteLine("La somma di {0} e {1} è {2}.", A, B, A + B)'> "La somma di 1 e 3 è 4."
01.02.03.04.05.06.07.08.09.10.11.12.
Module Module1
'Restituisce il numero di secondi passati dalla data D a oggiPrivate Function GetElapsed(ByVal D As Date) As Single
Return (Date.Now - D).TotalSecondsEnd Function
'Come sopra, ma il parametro è di tipo intero e indica'un anno qualsiasiPrivate Function GetElapsed(ByVal Year As Int32) As Single
'Utilizza Year per costruire un nuovo valore Date
Come avr ete notato, nell'esempio pr ecedente non ho usato la keywor 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 keywor d; eccone un esempio:
13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.
'e usa la precedente variante del metodo per'ottenere il risultatoReturn GetElapsed(New Date(Year, 1, 1))
End Function
'Come le due sopra, ma il parametro è di tipo stringa'e indica la dataPrivate Function GetElapsed(ByVal D As String) As Single
Return GetElapsed(Date.Parse(D))End Function
Sub Main()
'GetElapsed viene chiamata con tre tipi di parametri'diversi, ma sono tutti lecitiDim El1 As Single = GetElapsed(New Date(1987, 12, 4))Dim El2 As Single = GetElapsed(1879)Dim El3 As Single = GetElapsed("12/12/1991")
Console.ReadKey()
End Sub End Module
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.
Module Module1Class Person
Protected _FirstName, _LastName As StringPrivate ReadOnly _BirthDay As Date
Public Property FirstName() As String
GetReturn _FirstName
End GetSet(ByVal Value As String)
If Value <> "" Then_FirstName = Value
End IfEnd Set
End Property
Public Overridable Property LastName() As StringGet
Return _LastNameEnd GetSet(ByVal Value As String)
If Value <> "" Then_LastName = Value
End IfEnd Set
End Property
Public ReadOnly Property BirthDay() As DateGet
Return _BirthDayEnd Get
End Property
Public Overridable ReadOnly Property CompleteName() As StringGet
Return _FirstName & " " & _LastNameEnd Get
End Property
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 keywor 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.
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.
'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 pocoPublic Overloads Overrides Function ToString() As String
Return CompleteNameEnd 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 modificarePublic Overloads Function ToString(ByVal FormatString As String) _
As StringDim Temp As String = FormatString'Sostituisce {F} con FirstNameTemp = Temp.Replace("{F}", _FirstName)'Sostituisce {L} con LastNameTemp = Temp.Replace("{L}", _LastName)
Return Temp
End Function
Sub New(ByVal FirstName As String, ByVal LastName As String, _ByVal BirthDay As Date)Me.FirstName = FirstNameMe.LastName = LastNameMe._BirthDay = BirthDay
End SubEnd 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 SubEnd Module
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, Eccezioni 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:
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:
1.2.3.4.5.
Try'Codice che potrebbe generare l'eccezione
Catch [Variabile] As [Tipo Eccezione]'Gestisce l'eccezione [Tipo Eccezione]
End Try
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.
Module Module1Sub Main()
Dim a, b As Single'ok controlla se a e b sono coerentiDim ok As Boolean = False
Do
'Tenta di leggere i numeri da tastieraTry
Console.WriteLine("Inserire due numeri non nulli: ")a = Console.ReadLineb = Console.ReadLine'Se il codice arriva fino a questo punto, significa'che non si sono verificate eccezioniok = True
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:
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'".
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.
Catch Ex As InvalidCastException'Se, invece, il programma arriva in questo blocco,'vuol dire che abbiamo "preso" (catch) un'eccezione'di tipo InvalidCastException, che è stata'"lanciata" dal codice precedente. Tutti i dati'relativi a quella eccezione sono ora conservati'nella variabile Ex.'Possiamo accedervi oppure no, come in questo caso,'ma sono in ogni caso informazioni utili, come'vedremo fra pocoConsole.WriteLine("I dati inseriti non sono numeri!")'I dati non sono coerenti, quindi ok = Falseok = False
End Try'Richiede gli stessi dati fino a che non si tratta'di due numeri
Loop Until ok
'Esegue il controllo su b e poi effettua la divisioneIf b <> 0 Then
Console.WriteLine("{0} / {1} = {2}", a, b, a / b)Else
Console.WriteLine("Divisione impossibile!")End If
Console.ReadKey()
End SubEnd Module
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.
Module Module1Sub Main()
'a e b sono interi short, ossia possono assumere'valori da -32768 a +32767Dim a, b As Int16Dim ok As Boolean = False
Do
TryConsole.WriteLine("Inserire due numeri non nulli: ")a = Console.ReadLineb = Console.ReadLineok = True
Catch Ex As Exception'Catturiamo una qualsiasi eccezione e stampiamo il'messaggio'ad essa relativo. Il messaggio è contenuto nella'proprietà Message dell'oggetto Ex.Console.WriteLine(Ex.Message)ok = False
End TryLoop Until ok
If b <> 0 Then
Console.WriteLine("{0} / {1} = {2}", a, b, a / b)Else
Console.WriteLine("Divisione impossibile!")End If
Console.ReadKey()
End SubEnd Module
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 FinallyIl 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:
Le istr uzioni eseguite sar anno:
Lanciare un'eccezione e creare eccezioni personalizzateAmmettiamo 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 amewor 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.02.03.04.05.06.07.08.09.10.11.12.13.14.
TryABCDEF
Catch Ex As ExceptionGH
FinallyIL
End Try
01.02.03.04.05.06.07.08.09.
ABC'Eccezione: salta nel blocco CatchGH'Alla fine esegue comunque il FinallyIL
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.
Module Module1'Questa classe rappresenta l'errore lanciato quando una'password imessa è sbagliata. Per convenzione, tutte le'classi che rappresentano un'eccezione devono terminare'con la parola "Exception" Class IncorrectPasswordException
Inherits System.Exception 'Eredita da Exception
'Queste proprietà ridefiniscono quelle della classe'Exception tramite polimorfismo, perciò sono'dichiarate Overrides 'Sovrascrive il messaggio di errorePublic Overrides ReadOnly Property Message() As String
Get
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 ectPasswor dEx ception nello stessa linea di codice in cui
l'abbiamo lanciato.
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.
Return "La password inserita � sbagliata!"End Get
End Property
'Modifica il link di aiutoPublic Overrides Property HelpLink() As String
GetReturn "http://totem.altervista.org"
End GetSet(ByVal Value As String)
MyBase.HelpLink = valueEnd 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
Try
Console.WriteLine("Inserire la password:")NewPass = Console.ReadLineIf NewPass <> Pass Then
'Lancia l'eccezione usando la keyword ThrowThrow New IncorrectPasswordException
End IfCatch IPE As IncorrectPasswordException
'Visualizza il messaggioConsole.WriteLine(IPE.Message)'E il link d'aiutoConsole.WriteLine("Help: " & IPE.HelpLink)
End Try
Console.ReadKey()End Sub
End Module
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 amewor k è molto diver so, e or a vediamo come oper a.
Garbage CollectionQuesto è il nome del pr ocesso sul quale si basa la gestione della memor ia del Fr amewor 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.
FinalizeIl 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 mai 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.HasShutdownStar 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 non deve mai esser e fatto in
un'applicazione r eale, poichè potr ebbe compr ometter ne le pr estazioni.
L'output sar à:
Come si vede, l'oggetto viene distr utto dopo 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:
Ciò che appar ir à sullo scher mo è:
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.
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.
Module Module1Class Oggetto
Sub New()Console.WriteLine("Un oggetto sta per essere creato.")
End Sub'La procedura Finalize è definita in System.Object, quindi,'per ridefinirla dobbiamo usare il polimorfismo. Inoltre'deve essere dichiarata Protected, poichè non può'essere richiamata da altro ente se non dal GC e allo'stesso tempo è ereditabileProtected Overrides Sub Finalize()
Console.WriteLine("Un oggetto sta per essere distrutto.")'Blocca il programma per 4 secondi circa, consentendoci'di vedere cosa viene scritto a schermoSystem.Threading.Thread.CurrentThread.Sleep(4000)
End SubEnd Class
Sub Main()
Dim O As New OggettoConsole.WriteLine("Oggetto = Nothing")Console.WriteLine("L'applicazione sta per terminare.")
End SubEnd Module
1.2.3.4.
Un oggetto sta per essere creato.Oggetto = NothingL'applicazione sta per terminare.Un oggetto sta per essere distrutto.
01.02.03.04.05.06.07.08.09.10.11.12.13.14.
Sub Main()Dim O As New OggettoO = NothingConsole.WriteLine("Oggetto = Nothing")
'NON PROVATECI A CASA!'Forza una garbage collectionGC.Collect()'Attende che tutti i metodi Finalize siano stati eseguitiGC.WaitForPendingFinalizers()
Console.WriteLine("L'applicazione sta per terminare.")Console.ReadKey()
End Sub
1.2.3.4.
Un oggetto sta per essere creato.Oggetto = NothingUn oggetto sta per essere distrutto.L'applicazione sta per terminare.
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 resurrezione
dell'og 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.
DisposeSi 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.
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:
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 FinalizeCi 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
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.
Class Oggetto'Implementa l'interfaccia IDisposableImplements IDisposable'File da scrivere:Dim W As IO.StreamWriter
Sub New()
'Inizializza l'oggettoW = New IO.StreamWriter("C:\test.txt")
End Sub
Public Sub Dispose() Implements IDisposable.Dispose'Chiude il fileW.Close()
End SubEnd Class
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
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.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.
Module Module1Class FileWriter
Implements IDisposable
Private Writer As IO.StreamWriter'Indica se l'oggetto è già stato distrutto con DisposePrivate Disposed As Boolean'Indica se il file è apertoPrivate Opened As Boolean
Sub New()
Disposed = FalseOpened = FalseConsole.WriteLine("FileWriter sta per essere creato.")'Questa procedura comunica al GC di non richiamare più'il metodo Finalize per questo oggetto. Scriviamo ciò'perchè se file non viene esplicitamente aperto con'Open non c'è alcun bisogno di chiuderloGC.SuppressFinalize(Me)
End Sub
'Apre il filePublic Sub Open(ByVal FileName As String)
Writer = New IO.StreamWriter(FileName)Opened = TrueConsole.WriteLine("FileWriter sta per essere aperto.")'Registra l'oggetto per eseguire Finalize: ora il file'è aperto e può quindi essere chiusoGC.ReRegisterForFinalize(Me)
End Sub
'Scrive del testo nel filePublic Sub Write(ByVal Text As String)
If Opened ThenWriter.Write(Text)
End IfEnd Sub
'Una procedura analoga a Open aiuta a impostare meglio'l'oggetto e non fa altro che richiamare Dispose: è'più una questione di completezza
L'output:
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.
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 correttamenteProtected Overridable Overloads Sub Dispose(ByVal Disposing _
As Boolean)'Esegue il codice solo se l'oggetto esiste ancoraIf Disposed Then
'Se è distrutto, esce dalla proceduraExit Sub
End If If Disposing Then
'Qui possiamo chiamare altri oggetti con la'sicurezza che esistano ancoraConsole.WriteLine("FileWriter sta per essere distrutto.")
ElseConsole.WriteLine("FileWriter sta per essere finalizzato.")
End If
'Chiude il fileWriter.Close()
Disposed = TrueOpened = False
End Sub
Public Overloads Sub Dispose() Implements IDisposable.Dispose'L'oggetto è stato distruttoDispose(True)'Quindi non deve più essere finalizzatoGC.SuppressFinalize(Me)
End Sub
Protected Overrides Sub Finalize()'Processo di finalizzazione:Dispose(False)
End SubEnd Class
Sub Main()
Dim F As New FileWriter'Questo blocco mostra l'esecuzione di DisposeF.Open("C:\test.txt")F.Write("Ciao")F.Close()
'Questo mostra l'esecuzione di FinalizeF = New FileWriterF.Open("C:\test2.txt")F = Nothing
GC.Collect()GC.WaitForPendingFinalizers()
Console.ReadKey()
End SubEnd Module
1.2.
FileWriter sta per essere creato.
3.4.5.6.
FileWriter sta per essere aperto.FileWriter sta per essere distrutto.FileWriter sta per essere creato.FileWriter sta per essere aperto.FileWriter sta per essere finalizzato.
A34. I Delegate
Con il ter mine Deleg ate si indica un par ticolar e tipo di dato che è in gr ado di "contener e" un metodo, ossia una
pr ocedur a o una funzione. Ho messo di pr oposito le vir golette sul ver bo "contener e", poiché non è pr opr iamente
esatto, ma ser ve per r ender e più incisiva la definizione. Come esistono tipi di dato per gli inter i, i decimali, le date, le
str inghe, gli oggetti, ne esistono anche per i metodi, anche se può sembr ar e un po' str ano. Per chi avesse studiato
altr i linguaggi pr ima di appr occiar si al VB.NET, possiamo assimilar e i Delegate ai tipi pr ocedur ali del Pascal o ai
puntator i a funzione del C. Ad ogni modo, i delegate sono legger mente diver si da questi ultimi e pr esentano alcuni
tr atti par ticolar i:
Un delegate non può contener e quals ias i metodo, ma he dei limiti. Infatti, è in gr ado di contener e solo metodi
con la stessa signatur e specificata nella definizione del tipo. Fr a br eve vedr emo in cosa consiste questo punto;
Un delegate può contener e sia metodi di istanza sia metodi statici, a patto che questi r ispettino la r egole di cui
al punto sopr a;
Un delegate è un tipo r efer ence, quindi si compor ta come un comunissimo oggetto, seguendo quelle r egole che
mi sembr a di aver già r ipetuto fino alla noia;
Un oggetto di tipo delegate è un oggetto immutabile, ossia, una volta cr eato, non può esser e modificato. Per
questo motivo, non espone alcuna pr opr ietà (tr anne due in sola lettur a). D'altr a par te, questo compor tamento
er a pr evedibile fin dalla definizione: infatti, se un delegate contiene un r ifer imento ad un metodo - e quindi un
metodo già esistente e magar i definito in un'altr a par te del codice - come si far ebbe a modificar lo? Non si
potr ebbe modificar e la signatur e per chè questo andr ebbe in conflitto con la sua natur a, e non si potr ebbe
modificar ne il cor po per chè si tr atta di codice già scr itto (r icor date che gli oggetti esistono solo a r un-time,
per chè vengono cr eati solo dopo l'avvio del pr ogr amma, e tutto il codice è già stato compilato e tr asfor mato in
linguaggio macchina inter medio);
Un delegate è un tipo s afe, ossia non può mai contener e r ifer imenti ad indir izzi di memor ia che non indichino
espr essamente un metodo (al contr ar io dei per icolosi puntator i del C).
Mi r endo conto che questa intr oduzione può appar ir e un po' tr oppo teor ica e fumosa, ma ser ve per compr ender e il
compor tamento dei delegate.
Dichiarazione di un delegateUn nuovo tipo delegate viene dichiar ato con questa sintassi:
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 sempre il costr uttor e di default, ossia quello pr edefinito, che
1. Delegate [Sub/Function] [Nome]([Elenco parametri])
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":
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:
La signatur e di un delegate non 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:
1. AddressOf [NomeMetodo]
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.
Module Module1'Dichiarazione di un tipo delegate Sub che accetta un parametro'di tipo stringa.Delegate Sub Display(ByVal Message As String)
'Una procedura dimostrativaSub Write1(ByVal S As String)
Console.WriteLine("1: " & S)End Sub
'Un'altra procedura dimostrativaSub Write2(ByVal S As String)
Console.WriteLine("2: " & S)End Sub
Sub Main()
'Variabile D di tipo Display, ossia il nuovo tipo'delegate appena definito all'inizio del moduloDim D As Display 'Inizializa D con un nuovo oggetto delegate contenente'un riferimento al metodo Console.WriteLineD = New Display(AddressOf Console.WriteLine)
'Invoca il metodo referenziato da D: in questo caso'equivarrebbe a scrivere Console.WriteLine("Ciao")D("Ciao")
'Reinizializza D, assegnandogli l'indirizzo di Write1D = New Display(AddressOf Write1)'è come chiamare Write1("Ciao")D("Ciao")
'Modo alternativo per inizializzare un delegate: si omette'New e si usa solo AddressOf. Questo genera una conversione'implicita che dà errore di cast nel caso in cui Write1'non sia compatibile con la signature del delegateD = AddressOf Write2D("Ciao")
'Notare che D può contenere metodi di istanza'(come Console.WriteLine) e metodi statici (come Write1'e Write2) Console.ReadKey()
End SubEnd Module
001.002.
Module Module1
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 SingleDelegate 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 BooleanDelegate 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 ParamArrayFunction CalculateAverage(ByVal ParamArray Data() As Single) As Single
Dim Total As Single = 0
For I As Int32 = 0 To Data.Length - 1Total += 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 è indefinitoFunction 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) ^ 2Next
Return (Result / Data.Length)
End Function
'Procedura che stampa i valori di un array in ordine normale'o inverso. Notare che il secondo parametro è opzionaleSub PrintNormal(ByVal Data() As Single, _
Optional ByVal ReverseOrder As Boolean = False)If ReverseOrder Then
For I As Int32 = Data.Length - 1 To 0 Step -1Console.WriteLine(Data(I))
NextElse
For I As Int32 = 0 To Data.Length - 1Console.WriteLine(Data(I))
NextEnd 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'è opzionaleSub PrintIndexed(ByVal Data() As Single, _
Optional ByVal ReverseOrder As Boolean = False)If ReverseOrder Then
For I As Int32 = Data.Length - 1 To 0 Step -1Console.WriteLine("{0}) {1}", Data.Length - I, Data(I))
NextElse
For I As Int32 = 0 To Data.Length - 1Console.WriteLine("{0}) {1}", (I + 1), Data(I))
NextEnd If
End Sub
Sub Main()Dim Process As ProcessDataDim Print As PrintDataDim Data() As SingleDim Len As Int32Dim Cmd As Char
Un esempio più significativoI delegate sono par ticolar mente utili per r ispar miar e spazio nel codice. Tr amite i delegate, infatti, possiamo usar e lo
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.
Console.WriteLine("Quanti valori inserire?")Len = Console.ReadLine
ReDim Data(Len - 1)For I As Int32 = 1 To Len
Console.Write("Inserire il valore " & I & ": ")Data(I - 1) = Console.ReadLine
Next
Console.Clear()
Console.WriteLine("Scegliere l'operazione da eseguire: ")Console.WriteLine("m - Calcola la media dei valori;")Console.WriteLine("v - Calcola la varianza dei valori;")Cmd = Console.ReadKey().KeyCharSelect Case Cmd
Case "m"Process = New ProcessData(AddressOf CalculateAverage)
Case "v"Process = New ProcessData(AddressOf CalculateVariance)
Case ElseConsole.WriteLine("Comando non valido!")Exit Sub
End SelectConsole.WriteLine()Console.WriteLine("Scegliere il metodo di stampa: ")Console.WriteLine("s - Stampa i valori;")Console.WriteLine("i - Stampa i valori con il numero ordinale a fianco.")Cmd = Console.ReadKey().KeyCharSelect Case Cmd
Case "s"Print = New PrintData(AddressOf PrintNormal)
Case "i"Print = New PrintData(AddressOf PrintIndexed)
Case ElseConsole.WriteLine("Comando non valido!")Exit Sub
End Select
Console.Clear()
Console.WriteLine("Valori:")'Eccoci arrivati al punto. Come detto prima, i delegate'non possono definire una signature che comprenda parametri'opzionali o indefiniti, ma si'può aggirare questa limitazione semplicemente dichiarando'un array di valori al posto del ParamArray (in quanto si'tratta comunque di due vettori) e lo stesso parametro'non opzionale al posto del parametro opzionale.'L'inconveniente, in questo ultimo caso, è che il'parametro, pur essendo opzionale va sempre specificato'quando il metodo viene richiamato attraverso un oggetto'delegate. Questo escamotage permette di aumentare la'portata dei delegate, includendo anche metodi che'possono essere stati scritti tempo prima in un'altra'parte inaccessibile del codice: così'non è necessario riscriverli!Print(Data, False)Console.WriteLine("Risultato:")Console.WriteLine(Process(Data))
Console.ReadKey()
End Sub End Module
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.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.50.51.52.53.54.55.56.57.58.59.60.61.62.63.64.65.66.
Module Module2'Nome del file da cercareDim File As String
'Questo delegate referenzia una funzione che accetta un'parametro stringa e restituisce un valore booleanoDelegate Function IsMyFile(ByVal FileName As String) As Boolean
'Funzione 1, stampa il contenuto del file a schermoFunction PrintFile(ByVal FileName As String) As Boolean
'Io.Path.GetFileName(F) restituisce solo il nome del'singolo file F, togliendo il percorso delle cartelleIf IO.Path.GetFileName(FileName) = File Then
'IO.File.ReadAllText(F) restituisce il testo contenuto'nel file F in una sola operazioneConsole.WriteLine(IO.File.ReadAllText(FileName))Return True
End IfReturn False
End Function
'Funzione 2, copia il file sul desktopFunction CopyFile(ByVal FileName As String) As Boolean
If IO.Path.GetFileName(FileName) = File Then'IO.File.Copy(S, D) copia il file S nel file D:'se D non esiste viene creato, se esiste viene'sovrascrittoIO.File.Copy(FileName, _My.Computer.FileSystem.SpecialDirectories.Desktop & _
"\" & File)Return True
End IfReturn False
End Function
'Procedura ricorsiva che cerca il fileFunction SearchFile(ByVal Dir As String, ByVal IsOK As IsMyFile) _
As Boolean'Ottiene tutte le sottodirectoryDim Dirs() As String = IO.Directory.GetDirectories(Dir)'Ottiene tutti i filesDim Files() As String = IO.Directory.GetFiles(Dir)
'Analizza ogni file per vedere se è quello cercatoFor Each F As String In Files
'È il file cercato, basta cercareIf IsOK(F) Then
'Termina la funzione e restituisce Vero, cosicché'anche nel for sulle cartelle si termini'la ricercaReturn True
End IfNext
'Analizza tutte le sottocartelleFor Each D As String In Dirs
If SearchFile(D, IsOK) Then'Termina ricorsivamente la ricercaReturn True
End IfNext
End Function
Sub Main()Dim Dir As String
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.
67.68.69.70.71.72.73.74.75.76.77.78.79.80.81.
Console.WriteLine("Inserire il nome file da cercare:")File = Console.ReadLine
Console.WriteLine("Inserire la cartella in cui cercare:")Dir = Console.ReadLine
'Cerca il file e lo scrive a schermoSearchFile(Dir, AddressOf PrintFile)
'Cerca il file e lo copia sul desktopSearchFile(Dir, AddressOf CopyFile)
Console.ReadKey()
End SubEnd Module
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 invocation 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:
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:
N.B.: Quando un delegate multicast contiene delle funzioni e viene r ichiamato, il valor e r estituito è quello della pr ima
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.
Module Module2'Vedi esempio precedenteSub Main()
Dim Dir As StringDim D As IsMyFile
Console.WriteLine("Inserire il nome file da cercare:")File = Console.ReadLine
Console.WriteLine("Inserire la cartella in cui cercare:")Dir = Console.ReadLine
'Crea un delegate multicast, unendo PrintFile e CopyFile.'Da notare che in questa espressione è necessario usare'delle vere e proprie variabili delegate, poiché'l'operatore AddressOf da solo non è valido in questo casoD = System.Delegate.Combine(New IsMyFile(AddressOf PrintFile), _
New IsMyFile(AddressOf CopyFile))'Per la cronaca, Combine è un metodo factory
'Ora il file trovato viene sia visualizzato che copiato'sul desktopSearchFile(Dir, D)
'Se si vuole rimuovere uno o più riferimenti a metodi del'delegate multicast si deve utilizzare il metodo statico Remove:D = System.Delegate.Remove(D, New IsMyFile(AddressOf CopyFile))'Ora D farà visualizzare solamente il file trovato
Console.ReadKey()
End SubEnd Module
1. DirectCast(System.Delegate.Combine(A, B), IsMyFile)
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 tipoDelegate Function FileFilter(ByVal FileName As String) As Boolean'Il prossimo delegate rappresenta un'operazione su un fileDelegate 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 fileDim RenameFormat As String'Posizione di un file nella cartellaDim FileIndex As Int32'Directory in cui copiare i fileDim CopyDirectory As String'File in cui scrivere. Il tipo StreamWriter permette di scrivere'facilmente stringhe su un file usando WriteLine come in ConsoleDim LogFile As IO.StreamWriter'Limitazioni sulla data di creazione del fileDim CreationDateFrom, CreationDateTo As Date'Limitazioni sulla data di ultimo accesso al fileDim LastAccessDateFrom, LastAccessDateTo As Date'Limitazioni sulla dimensioneDim SizeFrom, SizeTo As Int64
'Rinomina un fileSub Rename(ByVal Path As String)
'Ne prende il nome semplice, senza estensioneDim Name As String = IO.Path.GetFileNameWithoutExtension(Path)'Apre un oggetto contenente le informazioni sul file'di percorso PathDim Info As New IO.FileInfo(Path)
'Formatta il nome secondo la stringa di formato RenameFormatName = String.Format(RenameFormat, _
Name, FileIndex, Info.Length, Info.LastAccessTime, Info.CreationTime)'E aggiunge ancora l'estensione al nome modificatoName &= IO.Path.GetExtension(Path)'Copia il vecchio file nella stessa cartella, ma con il nuovo nomeIO.File.Copy(Path, IO.Path.GetDirectoryName(Path) & "\" & Name)'Elimina il vecchio fileIO.File.Delete(Path) 'Aumenta l'indice di unoFileIndex += 1
End Sub
'Funzione che richiede i dati necessari per far funzionare'il metodo RenameSub 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 PathSub Delete(ByVal Path As String)
IO.File.Delete(Path)End Sub
'Copia il file da Path alla nuova cartellaSub 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 creaSub InputCopyDirectory()
Console.WriteLine("Inserire una cartella valida in cui copiare i file:")CopyDirectory = Console.ReadLineIf Not IO.Directory.Exists(CopyDirectory) Then
IO.Directory.CreateDirectory(CopyDirectory)End If
End Sub
'Scrive il nome del file sul file apertoSub Archive(ByVal Path As String)
LogFile.WriteLine(IO.Path.GetFileName(Path))End Sub
'Chiede il nome di un file su cui scrivere tutte le informazioniSub 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 fissatiFunction 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 fileSub 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 fissatiFunction 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 fileSub 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 fissatiFunction 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 fileSub InputSizeLimit()
Console.WriteLine("Verranno considerati solo i file con dimensione compresa:")Console.Write("Tra (bytes):")SizeFrom = Console.ReadLineConsole.Write("E (bytes):")SizeTo = Console.ReadLine
End Sub
'Classe che rappresenta un'operazione eseguibile su fileClass Operation
Private _Description As StringPrivate _Execute As MassFileOperationPrivate _RequireData As AskForDataPrivate _Enabled As Boolean
'DescrizionePublic Property Description() As String
GetReturn _Description
End GetSet(ByVal value As String)
_Description = valueEnd Set
End Property
'Variabile che contiene l'oggetto delegate associato'a questa operazione, ossia un riferimento a una delle Sub'definite poco sopraPublic Property Execute() As MassFileOperation
GetReturn _Execute
End GetSet(ByVal value As MassFileOperation)
_Execute = valueEnd 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
GetReturn _RequireData
End GetSet(ByVal value As AskForData)
_RequireData = valueEnd Set
End Property
'Determina se l'operazione va eseguita oppure noPublic Property Enabled() As Boolean
GetReturn _Enabled
End GetSet(ByVal value As Boolean)
_Enabled = valueEnd 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 = DescriptionMe.Execute = ExecuteMethodMe.RequireData = RequireDataMethodMe.Enabled = False
End SubEnd Class
'Classe che rappresenta una condizione a cui sottoporre'i file nella cartella: verranno elaborati solo quelli che'soddisfano tutte le condizioniClass Condition
Private _Description As StringPrivate _Verify As FileFilterPrivate _RequireData As AskForDataPrivate _Enabled As Boolean
Public Property Description() As String
GetReturn _Description
End GetSet(ByVal value As String)
_Description = valueEnd Set
End Property
'Contiene un oggetto delegate associato a una delle'precedenti funzioniPublic Property Verify() As FileFilter
GetReturn _Verify
End GetSet(ByVal value As FileFilter)
_Verify = valueEnd Set
End Property
Public Property RequireData() As AskForDataGet
Return _RequireDataEnd GetSet(ByVal value As AskForData)
_RequireData = valueEnd Set
End Property
Public Property Enabled() As BooleanGet
Return _EnabledEnd GetSet(ByVal value As Boolean)
_Enabled = valueEnd Set
End Property
Sub New(ByVal Description As String, _ByVal VerifyMethod As FileFilter, _ByVal RequireDataMethod As AskForData)Me.Description = DescriptionMe.Verify = VerifyMethodMe.RequireData = RequireDataMethod
End SubEnd Class
Sub Main()
'Contiene tutte le operazioni da eseguire: sarà, quindi, un'delegate multicastDim DoOperations As MassFileOperation'Contiene tutte le condizioni da verificareDim 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 fileDim Folder As String'Hashtable di caratteri-Operation o carattri-Condition. Il'carattere indica quale tasto è necessario'premere per attivare/disattivare l'operazione/condizioneDim Operations As New HashtableDim Conditions As New HashtableDim 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 dilunghezza 1 e caratteri, al contrario di come accade in CWith 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'altracartella;", _
New MassFileOperation(AddressOf Copy), _New AskForData(AddressOf InputCopyDirectory)))
.Add("a"c, New Operation("Scrive il nome di tutti i file nella cartella su unfile;", _
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 esistentiWith Conditions
.Add("r"c, New Condition("Seleziona i file da elaborare in base alla data dicreazione;", _
New FileFilter(AddressOf IsCreationDateValid), _New AskForData(AddressOf InputCreationDates)))
.Add("l"c, New Condition("Seleziona i file da elaborare in base all'ultimoaccesso;", _
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)
DoConsole.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 IfConsole.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.
Console.Write(Key)Console.Write(" - ")Console.WriteLine(Operations(Key).Description)
NextCmd = Console.ReadKey().KeyCharIf Operations.ContainsKey(Cmd) Then
Operations(Cmd).Enabled = Not Operations(Cmd).EnabledEnd If
Loop Until Cmd = "e"c
DoConsole.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 IfConsole.Write("] ")Console.Write(Key)Console.Write(" - ")Console.WriteLine(Conditions(Key).Description)
NextCmd = Console.ReadKey().KeyCharIf Conditions.ContainsKey(Cmd) Then
Conditions(Cmd).Enabled = Not Conditions(Cmd).EnabledEnd 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 formaIf Op.RequireData IsNot Nothing Then
Op.RequireData.Invoke()End If'Se DoOperations non contiene ancora nulla, vi inserisce Op.ExecuteIf DoOperations Is Nothing Then
DoOperations = Op.ExecuteElse
'Altrimenti, combina gli oggetti delegate già memorizzati'con il nuovoDoOperations = System.Delegate.Combine(DoOperations, Op.Execute)
End IfEnd If
Next
For Each C As Condition In Conditions.ValuesIf C.Enabled Then
If C.RequireData IsNot Nothing ThenC.RequireData.Invoke()
End IfIf VerifyConditions Is Nothing Then
VerifyConditions = C.VerifyElse
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 amewor k, e scopr ir emo nella sezione B che i delegate sono il meccanismo fondamentale alla base di tutto il sistema
degli eventi.
Alcuni membri importanti per i delegate multicastLa 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
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.458.459.460.461.462.463.464.465.
VerifyConditions = System.Delegate.Combine(VerifyConditions, C.Verify)End If
End IfNext
FileIndex = 0For Each File As String In IO.Directory.GetFiles(Folder)
'Ok indica se il file ha passato le condizioniDim Ok As Boolean = True'Se ci sono condizioni da applicare, le verificaIf VerifyConditions IsNot Nothing Then
'Dato che nel caso di delegate multicast contenenti'rifermenti a funzione, il valore restituito è'solo quello della prima funzione e a noi interessano'<b>tutti</b> i valori restituiti, dobbiamo enumerare'ogni singolo oggetto delegate presente nel'delegate multicast e invocarlo singolarmente.'Ci viene in aiuto il metodo di istanza GetInvocationList,'che restituisce un array di delegate singoli.For Each C As FileFilter In VerifyConditions.GetInvocationList()
'Tutte le condizioni attive devono essere verificate,'quindi bisogna usare un AndOk = Ok And C(File)
NextEnd If'Se le condizioni sono verificate, esegue le operazioniIf Ok Then
TryDoOperations(File)
Catch Ex As ExceptionConsole.WriteLine("Impossibile eseguire l'operazione: " & Ex.Message)
End TryEnd If
Next'Chiude il file di log se era apertoIf LogFile IsNot Nothing Then
LogFile.Close()End If
Console.WriteLine("Operazioni eseguite con successo!")Console.ReadKey()
End Sub End Module
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 AstratteLe 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 keywor 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 uno s cheletro o una
base di as trazione per altr e classi. Pr endiamo come esempio uno dei miei pr ogr ammi, che potete tr ovar e nella
sezione download, 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:
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:
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
1.2.
<Serializable()> _Public MustInherit Class Chart
1. Public MustOverride Sub Draw()
adottar e: eccone una dimostr azione nel pr ossimo 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.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.
Module Module1
'Classe astratta che rappresenta un risolutore di equazioni.'Dato che di equazioni ce ne possono essere molte tipologie'differenti, non ha senso rendere questa classe istanziabile.'Provando a scrivere qualcosa come:' Dim Eq As New EquationSolver()'Vi verrà comunicato un errore, in quanto le classi'astratte sono per loro natura non istanziabiliMustInherit Class EquationSolver
'Per lo stesso discorso fatto prima, se non conosciamo come'è fatta l'equazione che questo tipo contiene non'possiamo neppure tentare di risolverla. Perciò'ci limitiamo a dichiarare una funzione Solve come MustOverride.'Notate che il tipo restituito è un array di Single,'in quanto le soluzioni saranno spesso più di una.Public MustOverride Function Solve() As Single()
End Class
'La prossima classe rappresenta un risolutore di equazioni'polinomiali. Dato che la tipologia è ben definita,'avremmo potuto anche <i>non</i> rendere astratta la classe'e, nella funzione Solve, utilizzare un Select Case per'controllare il grado dell'equazione. Ad ogni modo, è'utile vedere come si comporta l'erediterietà attraverso'più classi astratte.'Inoltre, ci ritornerà molto utile in seguito disporre'di questa classe astratta intermediaMustInherit Class PolynomialEquationSolver
Inherits EquationSolver
Private _Coefficients() As Single
'Array di Single che contiene i coefficienti dei'termini di i-esimo grado all'interno dell'equazione.'L'elemento 0 dell'array indica il coefficiente del'termine a grado massimo.Public Property Coefficients() As Single()
GetReturn _Coefficients
End GetSet(ByVal value As Single())
_Coefficients = valueEnd Set
End Property
'Ecco quello a cui volevo arrivare. Se un metodo astratto'lo si vuole mantenere tale anche nella classe derivata,'non basta scrivere:' MustOverride Function Solve() As Single()'Percè in questo caso verrebbe interpretato come'un membro che non c'entra niente con MyBase.Solve,'e si genererebbe un errore in quanto stiamo tentando'di dichiarare un nuovo membro con lo stesso nome'di un membro della classe base.'Per questo motivo, dobbiamo comunque usare il polimorfismo'come se si trattasse di un normale metodo e dichiararlo'Overrides. In aggiunta a questo, deve anche essere'astratto, e perciò aggiungiamo MustOverride:Public MustOverride Overrides Function Solve() As Single()
'Anche in questo caso usiamo il polimorfismo, ma ci riferiamo'alla semplice funzione ToString, derivata dalla classe base'di tutte le entità esistenti, System.Object.'Questa si limita a restituire una stringa che rappresenta'l'equazione a partire dai suoi coefficienti. Ad esempio:' 3x^2 + 2x^1 + 4x^0 = 0'Potete modificare il codice per eliminare le forme ridondanti'x^1 e x^0.Public Overrides Function ToString() As String
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 - 1If I > 0 Then
Result &= " + "End IfResult &= String.Format("{0}x^{1}", _
Me.Coefficients(I), Me.Coefficients.Length - 1 - I)Next
Result &= " = 0"
Return Result
End FunctionEnd 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 biancoClass NonPolynomialEquationSolver
Inherits EquationSolver
Public Overrides Function Solve() As Single()Return Nothing
End FunctionEnd 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 = 0Class LinearEquationSolver
Inherits PolynomialEquationSolver
Public Property a() As SingleGet
Return Me.Coefficients(0)End GetSet(ByVal value As Single)
Me.Coefficients(0) = valueEnd Set
End Property
Public Property b() As SingleGet
Return Me.Coefficients(1)End GetSet(ByVal value As Single)
Me.Coefficients(1) = valueEnd 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 sviluppoPublic Overrides Function Solve() As Single()
If a <> 0 ThenReturn 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.
ElseReturn Nothing
End IfEnd Function
End Class
'Risolutore di equazioni di secondo grado:' ax<sup>2</sup> + bx + c = 0Class QuadraticEquationSolver
Inherits LinearEquationSolver
Public Property c() As SingleGet
Return Me.Coefficients(2)End GetSet(ByVal value As Single)
Me.Coefficients(2) = valueEnd 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}
ElseReturn Nothing
End IfEnd 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 SinglePrivate _IntervalLowerBound, _IntervalUpperBound As Single
'Errore desiderato: l'algoritmo si fermerà una volta'raggiunta una precisione inferiore a EpsilonPublic Property Epsilon() As Single
GetReturn _Epsilon
End GetSet(ByVal value As Single)
_Epsilon = valueEnd Set
End Property
'Limite inferiore dell'intervallo in cui cercare la soluzionePublic Property IntervalLowerBound() As Single
GetReturn _IntervalLowerBound
End GetSet(ByVal value As Single)
_IntervalLowerBound = valueEnd Set
End Property
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 soluzionePublic Property IntervalUpperBound() As Single
GetReturn _IntervalUpperBound
End GetSet(ByVal value As Single)
_IntervalUpperBound = valueEnd 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 xPrivate Function EvaluateFunction(ByVal x As Single) As Single
Dim Result As Single = 0
For I As Int16 = 0 To Me.Coefficients.Length - 1Result += Me.Coefficients(I) * x ^ (Me.Coefficients.Length - 1 - I)
Next
Return ResultEnd Function
Public Overrides Function Solve() As Single()
Dim a, b, c As SingleDim fa, fb, fc As SingleDim Interval As Single = 100Dim I As Int16 = 0Dim Result As Single
a = IntervalLowerBoundb = IntervalUpperBound
'Non esiste uno zero tra a e b se f(a) e f(b) hanno'lo stesso segnoIf EvaluateFunction(a) * EvaluateFunction(b) > 0 Then
Return NothingEnd If
Do
'c è il punto medio tra a e bc = (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 cicloIf fa = 0 Then
c = aExit Do
End IfIf fb = 0 Then
c = bExit Do
End IfIf fc = 0 Then
Exit DoEnd 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 questiIf fa * fc < 0 Then
b = cElse
a = cEnd 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 FunctionEnd 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 EquationSolverDim x() As SingleDim 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().KeyCharConsole.Clear()
If Cmd <> "e" Then
'Ancora, sappiamo che si tratta di un'equazione polinomiale'ma non di quale gradoDim Poly As PolynomialEquationSolver
'Ottiene i dati relativi a ciascuna equazioneSelect 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 Int16Console.WriteLine("Inserire il numero di coefficienti: ")CoefNumber = Console.ReadLineReDim High.Coefficients(CoefNumber - 1)Console.WriteLine("Inserire i limti dell'intervallo in cui cercare gli
zeri:")High.IntervalLowerBound = Console.ReadLineHigh.IntervalUpperBound = Console.ReadLineConsole.WriteLine("Inserire la precisione (epsilon):")High.Epsilon = Console.ReadLinePoly = 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
Eccovi un'immagine dell'ultimo commento:
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.
'caso più particolare al più generale:' - Abbiamo creato un oggetto specifico per un certo grado' di un'equazione polinomiale (Linear, Quadratic, High);' - Abbiamo messo quell'oggetto in uno che si riferisce' genericamente a tutti i polinomi;' - Infine, abbiamo posto quest'ultimo in uno ancora più' generale che si riferisce a tutte le equazioni;'Questo percorso porta da oggetto molto specifici e ricchi di membri'(tante proprietà e tanti metodi), a tipi molto generali'e poveri di membri (nel caso di Eq, un solo metodo).Eq = Poly
Else'Inseriamo in Eq un nuovo oggetto per risolvere equazioni non'polinomiali, anche se il codice è al momento vuotoEq = New NonPolynomialEquationSolverConsole.WriteLine("Non implementato")
End If
'Risolviamo l'equazione. Richiamare la funzione Solve da un oggetto'EquationSolver potrebbe non dirvi nulla, ma ricordate che dentro Eq'è memorizzato un oggetto più specifico in cui'è stata definita la funzione Solve(). Per questo motivo,'anche se Eq è di tipo classe base, purtuttavia contiene'al proprio interno un oggetto di tipo classe derivata, ed'è questo che conta: viene usato il metodo Solve della classe'derivata.'Se ci pensate bene, vi verrà più spontaneo capire,'poiché noi, ora, stiamo guardando ATTRAVERSO il tipo'EquationSolver un oggetto di altro tipo. È come osservare'attraverso filtri via via sempre più fitti (cfr'immagine seguente)x = Eq.Solve()
If x IsNot Nothing Then
Console.WriteLine("Soluzioni trovate: ")For Each s As Single In x
Console.WriteLine(s)Next
ElseConsole.WriteLine("Nessuna soluzione")
End If
Console.ReadKey()End Sub
End Module
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 SigillateLe classi sigillate sono esattamente l'opposto di quelle astr atte, ossia non possono mai esser e er editate. Si dichiar ano
con la keywor d NotInher itable:
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.
Inoltr e, le classi sigillate non 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 ParzialiUna 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 keywor d associate
alle classi in un solo capitolo. Semplicemente, una classe par ziale si dichiar a in questo modo:
È 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:
1.2.3.
NotInheritable Class Example'...
End Class
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.
Class ASub 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 stessoNotOverridable Overrides Sub DoSomething()
'...End Sub
End Class
1.2.3.
Partial Class [Nome]'...
End Class
01.02.03.04.
'Nel file Codice1.vb :Partial Class A
Sub One()
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.vbClass A
Sub Two()'...
End SubEnd Class 'Nel file Codice3.vbClass A
Sub Three()'...
End SubEnd 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
Scopo delle InterfacceLe inter facce sono un'entità davver o singolar e all'inter no del .NET Fr amewor 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 implementare 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:
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 amewor 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 implementano 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".
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 AlFor Each K As Int32 In Al
Console.WriteLine(K)Next
Dichiarazione e implementazioneLa sintassi usata per dichiar ar e un'inter faccia è la seguente:
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:
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:
Si capisce meglio con un esempio:
1.2.3.
Interface [Nome]'Membri
End Interface
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'uomoInterface IIdentifiable
ReadOnly Property Id() As Int32Function 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 GenericsInterface IResettable
Property DefaultValue() As ObjectSub Reset()
End Interface 'Come avete visto, i nomi di interfaccia iniziano per convenzione'con la lettera I maiuscola
1.2.3.4.5.
Class ExampleImplements [Nome Interfaccia] [Membro] Implements [Nome Interfaccia].[Membro]
End Class
01.02.03.04.05.06.07.
Module Module1Interface IIdentifiable
ReadOnly Property Id() As Int32Function ToString() As String
End Interface
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 spedireClass Pack
'Implementa l'interfaccia IIdentifiable, in quanto un pacco'dovrebbe poter essere ben identificatoImplements IIdentifiable 'Notate bene che l'interfaccia ci obbliga a definire una'proprietà, ma non ci obbliga a definire un campo'ad essa associatoPrivate _Id As Int32Private _Destination As StringPrivate _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 XDPublic ReadOnly Property Id() As Integer Implements IIdentifiable.Id
GetReturn _Id
End GetEnd Property
'Destinazione del pacco.'Il fatto che l'interfaccia ci obblighi a definire quei due'membri non significa che non possiamo definirne altriPublic Property Destination() As String
GetReturn _Destination
End GetSet(ByVal value As String)
_Destination = valueEnd Set
End Property
'Piccolo ripasso delle proprietà indicizzate e'della gestione degli erroriPublic Property Dimensions(ByVal Index As Int32) As Single
GetIf (Index >= 0) And (Index < 3) Then
Return _Dimensions(Index)Else
Throw New IndexOutOfRangeException()End If
End GetSet(ByVal value As Single)
If (Index >= 0) And (Index < 3) Then_Dimensions(Index) = value
ElseThrow New IndexOutOfRangeException()
End IfEnd Set
End Property
Public Overrides Function ToString() As String Implements IIdentifiable.ToStringReturn 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 FunctionEnd Class Sub Main()
'...End Sub
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:
End Module
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.
Module Module1
Interface IIdentifiableReadOnly Property Id() As Int32Function ToString() As String
End Interface
Class PackImplements IIdentifiable
Private _Id As Int32Private _Destination As StringPrivate _Dimensions(2) As Single
Public ReadOnly Property Id() As Integer Implements IIdentifiable.Id
GetReturn _Id
End GetEnd Property
Public Property Destination() As String
GetReturn _Destination
End GetSet(ByVal value As String)
_Destination = valueEnd Set
End Property
Public Property Dimensions(ByVal Index As Int32) As SingleGet
If (Index >= 0) And (Index < 3) ThenReturn _Dimensions(Index)
ElseThrow New IndexOutOfRangeException()
End IfEnd GetSet(ByVal value As Single)
If (Index >= 0) And (Index < 3) Then_Dimensions(Index) = value
ElseThrow New IndexOutOfRangeException()
End IfEnd Set
End Property
Sub New(ByVal Id As Int32)_Id = Id
End Sub
Public Overrides Function ToString() As String Implements IIdentifiable.ToStringReturn 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 FunctionEnd Class
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 TelegramImplements IIdentifiable
Private _Id As Int32Private _Recipient As StringPrivate _Message As String
Public ReadOnly Property Id() As Integer Implements IIdentifiable.Id
GetReturn _Id
End GetEnd Property
Public Property Recipient() As String
GetReturn _Recipient
End GetSet(ByVal value As String)
_Recipient = valueEnd Set
End Property
Public Property Message() As StringGet
Return _MessageEnd GetSet(ByVal value As String)
_Message = valueEnd Set
End Property
Sub New(ByVal Id As Int32)_Id = Id
End Sub
Public Overrides Function ToString() As String Implements IIdentifiable.ToStringReturn String.Format("{0:0000}: Telegramma per {1} ; Messaggio = {2}", _
Me.Id, Me.Recipient, Me.Message)End Function
End Class
Class MoneyOrderImplements IIdentifiable
Private _Id As Int32Private _Recipient As StringPrivate _Money As Single
Public ReadOnly Property Id() As Integer Implements IIdentifiable.Id
GetReturn _Id
End GetEnd Property
Public Property Recipient() As String
GetReturn _Recipient
End GetSet(ByVal value As String)
_Recipient = valueEnd Set
End Property
Public Property Money() As SingleGet
Return _MoneyEnd GetSet(ByVal value As Single)
_Money = valueEnd 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.ToStringReturn 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 interfacciaClass PostalProcessor
'Tanto per tenersi allenati coi delegate, ecco una'funzione delegate che funge da filtro per i vari idPublic Delegate Function IdSelector(ByVal Id As Int32) As Boolean
Private _StorageCapacity As Int32Private _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 elementoPublic Property StorageCapacity() As Int32
GetReturn _StorageCapacity
End GetSet(ByVal value As Int32)
_StorageCapacity = valueReDim Preserve Storage(value)
End SetEnd Property
'Modifica od ottiene un riferimento all'Index-esimo'oggetto nell'array StoragePublic Property Item(ByVal Index As Int32) As IIdentifiable
GetIf (Index >= 0) And (Index < Storage.Length) Then
Return Me.Storage(Index)Else
Throw New IndexOutOfRangeException()End If
End GetSet(ByVal value As IIdentifiable)
If (Index >= 0) And (Index < Storage.Length) ThenMe.Storage(Index) = value
ElseThrow New IndexOutOfRangeException()
End IfEnd 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'arrayPublic ReadOnly Property FirstPlaceAvailable() As Int32
GetFor 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.
Return IEnd If
NextReturn (-1)
End GetEnd Property
'Tutti gli oggetti che inizializzeremo avranno bisogno'di un id: ce lo fornisce la stessa classe Processor'tramite questa proprietà che si autoincrementaPublic ReadOnly Property NextId() As Int32
Get_NextId += 1Return _NextId
End GetEnd 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 magazzinoPublic Sub New(ByVal Capacity As Int32)
Me.StorageCapacity = CapacityEnd 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 StorageIf K Is Nothing Then
Continue ForEnd IfIf Selector.Invoke(K.Id) Then
Console.WriteLine(K.ToString())End If
NextEnd Sub
'Stampa l'oggetto con Id specificatoPublic Sub PrintById(ByVal Id As Int32)
For Each K As IIdentifiable In StorageIf K Is Nothing Then
Continue ForEnd IfIf K.Id = Id Then
Console.WriteLine(K.ToString())Exit For
End IfNext
End Sub
'Cerca tutti gli elementi che contemplano all'interno'della propria descrizione la stringa Str e li'restituisce come array di IdPublic Function SearchItems(ByVal Str As String) As Int32()
Dim Temp As New ArrayList
For Each K As IIdentifiable In StorageIf K Is Nothing Then
Continue ForEnd IfIf 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 Int32For I As Int32 = 0 To Temp.Count - 1
Result(I) = Temp(I)Next
Temp.Clear()Temp = Nothing
Return Result
End FunctionEnd Class
Private Processor As New PostalProcessor(10)Private Cmd As CharPrivate 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.ReadLineConsole.Write("Larghezza: ")P.Dimensions(0) = Console.ReadLineConsole.Write("Lunghezza: ")P.Dimensions(1) = Console.ReadLineConsole.Write("Altezza: ")P.Dimensions(2) = Console.ReadLineProcessor.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.ReadLineConsole.Write("Messaggio: ")T.Message = Console.ReadLineProcessor.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.ReadLineConsole.Write("Somma: ")M.Money = Console.ReadLineProcessor.Item(Place) = M
Case ElseConsole.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.
Cmd = Console.ReadKey().KeyChar
Console.Clear()Select Case Cmd
Case "c"Dim Str As StringConsole.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().KeyCharConsole.WriteLine()
If Cmd = "y" Then
For Each Id As Int32 In IdsProcessor.PrintById(Id)
NextEnd If
Case "v"Console.WriteLine("Visualizzare gli elementi")Console.Write("Da Id: ")IdFrom = Console.ReadLineConsole.Write("A Id: ")IdTo = Console.ReadLineProcessor.PrintByFilter(AddressOf SelectId)
Case ElseConsole.WriteLine("Comando sconosciuto.")
End Select
Console.ReadKey()End Sub
Sub Main()
DoConsole.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 IfCase "m"
Console.WriteLine("Attuale capacità: " & Processor.StorageCapacity)Console.WriteLine("Inserire una nuova dimensione: ")Processor.StorageCapacity = Console.ReadLineConsole.WriteLine("Operazione effettuata.")Console.ReadKey()
Case "p"ProcessData()
End SelectConsole.Clear()
Loop Until Cmd = "e"End Sub
End Module
Avevo in mente di definir e anche un'altr a inter faccia, IPayable, per calcolar e anche il costo di spedizione di ogni pezzo:
volevo far notar e come, sebbene il costo vada calcolato in manier a diver sa per i tr e tipi di oggetto (in base alle
dimensioni per il pacco, in base al numer o di par ole per il telegr amma e in base all'ammontar e inviato per il vaglia),
bastasse r ichiamar e una funzione attr aver so l'inter faccia per ottener e il r isultato. Poi ho consider ato che un esempio
di 400 r ighe er a già abbastanza. Ad ogni modo, user ò adesso quel'idea in uno spezzone tr atto dal pr ogr amma appena
scr itto per mostr ar e l'uso di inter facce multiple:
Definizione di tipi in un'interfacc iaCosì 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.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.
Module Module1'... Interface IPayable
Function CalculateSendCost() As SingleEnd Interface Class Telegram
'Nel caso di più interfacce, le si separa con la virgolaImplements IIdentifiable, IPayable
'...
Public Function CalculateSendCost() As Single Implements IPayable.CalculateSendCost
'Come vedremo nel capitolo dedicato alle stringhe,'la funzione Split(c) spezza la stringa in tante'parti, divise dal carattere c, e le restituisce'sottoforma di array. In questo caso, tutte le sottostringhe'separate da uno spazio sono all'incirca tante'quanto il numero di parole nella fraseSelect Case Me.Message.Split(" ").Length
Case Is <= 20Return 4.39
Case Is <= 50Return 6.7
Case Is <= 100Return 10.3
Case Is <= 200Return 19.6
Case Is <= 500Return 39.75
End SelectEnd Function
End Class '...
End Class
01.02.03.04.05.06.07.08.09.10.11.12.13.
Interface ISaveableStructure FileInfo
'Assumiamo per brevità che queste variabili Public'siano in realtà proprietàPublic Path As String'FileAttribues è un enumeratore su bit che contiene'informazioni sugli attributi di un file (nascosto, a sola'lettura, archivio, compresso, eccetera...)Public Attributes As FileAttributes
End StructureProperty SaveInfo() As FileInfoSub Save()
Ereditarietà, polimorfismo e overloading per le interfacceAnche 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:
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.
Perchè preferire un'interfacc ia a una c lasse astrattaLa differ enza sostanziale tr a una classe astr atta e un'inter faccia è che la pr ima definisce l'es s enza di un oggetto (che
cosa è), mentr e la seconda ne indica il comportamento (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
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
1.2.3.4.5.6.7.8.
Interface AProperty PropA() As Int32
End Interface Interface B
Inherits ASub SubB()
End Interface
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 IComparerUn oggetto che implementa ICompar able comunica implicitamente al .NET Fr amewor 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.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.
Module Module1Sub Main()
Dim A As Int32
Console.WriteLine("Inserisci un numero intero:")A = Console.ReadLine
'Tutti i tipi di base espongono il metodo CompareTo, poichè'tutti implementano l'interfaccia IComparable:If A.CompareTo(10) = 1 Then
Console.WriteLine(A & " è maggiore di 10")ElseIf A.CompareTo(10) = 0 Then
Console.WriteLine(A & " è uguale a 10")Else
Console.WriteLine(A & " è minore di 10")End If
'Il fatto che i tipi di base siano confrontabili implica'che si possano ordinare tramite il metodo Sort di una'qualsiasi collezione o array di elementiDim B() As Int32 = {1, 5, 2, 8, 10, 56}'Ordina l'arrayArray.Sort(B)'E visualizza i numeri in ordine crescenteFor I As Int16 = 0 To UBound(B)
Console.WriteLine(B(I))Next
'Anche String espone questo metodo, quindi si può ordinare'alfabeticamente un insieme di stringhe:Dim C As New ArrayListC.Add("Banana")C.Add("Zanzara")C.Add("Anello")C.Add("Computer")'Ordina l'insiemeC.Sort()For I As Int16 = 0 To C.Count - 1
Console.WriteLine(C(I))Next
Console.ReadKey()
Dopo aver immesso un input, ad esempio 8, avr emo la seguente scher mata:
Inserire un numero intero:88 è minore di 1012581056AnelloBananaComputerZanzara
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:
44.End Sub
End Module
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.
Module Module1Class Person
Implements IComparablePrivate _FirstName, _LastName As StringPrivate ReadOnly _BirthDay As Date
Public Property FirstName() As String
GetReturn _FirstName
End GetSet(ByVal Value As String)
If Value <> "" Then_FirstName = Value
End IfEnd Set
End Property
Public Property LastName() As StringGet
Return _LastNameEnd GetSet(ByVal Value As String)
If Value <> "" Then_LastName = Value
End IfEnd Set
End Property
Public ReadOnly Property BirthDay() As DateGet
Return _BirthDayEnd Get
End Property
Public ReadOnly Property CompleteName() As StringGet
Return _FirstName & " " & _LastNameEnd Get
End Property
'Per definizione, purtroppo, CompareTo deve sempre usare'un parametro di tipo Object: risolveremo questo problema
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:
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.
'più in là utilizzando i GenericsPublic 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 1End If'Tenta di convertire obj in PersonDim P As Person = DirectCast(obj, Person)'E restituisce il risultato dell'operazione di'comparazione tra stringhe dei rispettivi nomiReturn String.Compare(Me.CompleteName, P.CompleteName)
End Function
Sub New(ByVal FirstName As String, ByVal LastName As String, _ByVal BirthDay As Date)Me.FirstName = FirstNameMe.LastName = LastNameMe._BirthDay = BirthDay
End SubEnd Class Sub Main()
'Crea un array di oggetti PersonDim 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.CompareToArray.Sort(Persons)
For I As Int16 = 0 To UBound(Persons)
Console.WriteLine(Persons(I).CompleteName)Next
Console.ReadKey()
End SubEnd Module
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.17.18.
Module Module2'Questa classe fornisce un metodo per comparare oggetti Person'utilizzando la proprietà BirthDay.'Per convenzione, classi che implementano IComparer dovrebbero'avere un suffisso "Comparer" nel nome.'Altra osservazione: se ci sono molte interfacce il cui nome'termina in "-able", definendo una caratteristica dell'oggetto'che le implementa (ad es.: un oggetto enumerabile,'comparabile, distruggibile, ecc...), ce ne sono altrettante'che terminano in "-er", indicando, invece, un oggetto'che "fa" qualcosa di specifico.'Nel nostro esempio, oggetti di tipo BirthDayComparer'hanno il solo scopo di comparare altre oggettiClass BirthDayComparer
'Implementa l'interfacciaImplements IComparer
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.
IDisposableNel 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.
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.
'Anche questa funzione deve usare parametri objectPublic 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'ugualiIf x Is Nothing And y Is Nothing Then
Return 0ElseIf x Is Nothing Then
'Se x è Nothing, y è maggioreReturn -1
ElseIf y Is Nothing Then'Se y è Nothing, x è maggioreReturn 1
ElseDim P1 As Person = DirectCast(x, Person)Dim P2 As Person = DirectCast(y, Person)'Compara le dateReturn Date.Compare(P1.BirthDay, P2.BirthDay)
End IfEnd 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 BirthDayComparerArray.Sort(Persons, New BirthDayComparer())
For I As Int16 = 0 To UBound(Persons)
Console.WriteLine(Persons(I).CompleteName)Next
Console.ReadKey()
End SubEnd Module
A39. Utilizzo delle Interfacce - Parte II
IEnumerable e IEnumeratorUna classe che implementa IEnumer able diventa enumerabile agli occhi del .NET Fr amewor 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.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.50.51.52.53.54.55.
Module Module1Class PersonCollection
Implements IEnumerable'La lista delle personePrivate _Persons As New ArrayList
'Teoricamente, si dovrebbero ridefinire tutti i metodi'di una collection comune, ma per mancanza di spazio,'accontentiamociPublic ReadOnly Property Persons() As ArrayList
GetReturn _Persons
End GetEnd Property
'Restituisce l'età media. TimeSpan è una struttura che si'ottiene sottraendo fra loro due oggetti date e indica un'intervallo di tempoPublic ReadOnly Property AverageAge() As String
Get'Variabile temporaneaDim Temp As TimeSpan'Somma tutte le etàFor Each P As Person In _Persons
Temp = Temp.Add(Date.Now - P.BirthDay)Next'Divide per il numero di personeTemp = TimeSpan.FromSeconds(Temp.TotalSeconds / _Persons.Count)
'Dato che TimeSpan può contenere al massimo'giorni e non mesi o anni, dobbiamo fare qualche'calcoloDim Years As Int32'Gli anni, ossia il numero dei giorni fratto 365'Divisione interaYears = Temp.TotalDays \ 365'Sottrae gli anni: da notare che'(Temp.TotalDays \ 365) * 365) non è un passaggio'inutile. Infatti, per determinare il numero di'giorni che rimangono, bisogna prendere la'differenza tra il numero totale di giorni e'il multiplo più vicino di 365Temp = _Temp.Subtract(TimeSpan.FromDays((Temp.TotalDays \ 365) * 365)) Return Years & " anni e " & CInt(Temp.TotalDays) & " giorni"
End GetEnd Property
'La funzione GetEnumerator restituisce un oggetto di tipo'IEnumerator che vedremo fra breve: esso permette di'scorrere ogni elemento ordinatamente, dall'inizio'alla fine. In questo caso, poichè non abbiamo ancora'analizzato questa interfaccia, ci limitiamo a restituisce
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:
56.57.58.59.60.61.62.63.64.65.66.67.68.69.70.71.72.73.74.75.76.77.78.79.
'l'IEnumerator predefinito per un ArrayListPublic Function GetEnumerator() As IEnumerator _
Implements IEnumerable.GetEnumeratorReturn _Persons.GetEnumerator
End FunctionEnd Class
Sub Main()
Dim Persons As New PersonCollectionWith 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)NextConsole.WriteLine("Età media: " & Persons.AverageAge)'> 41 anni e 253 giorni
Console.ReadKey()
End SubEnd Module
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.
Module Module1Class PersonCollection
Implements IEnumerable'La lista delle personePrivate _Persons As New ArrayList
'Questa classe ha il compito di scorrere ordinatamente gli'elementi della lista, dal più vecchio al più giovanePrivate Class PersonAgeEnumerator
Implements IEnumerator
'Per enumerare gli elementi, la classe ha bisogno di un'riferimento ad essi: perciò si deve dichiarare ancora'un nuovo ArrayList di Person. Questo passaggio è'facoltativo nelle classi nidificate come questa, ma è'obbligatorio in tutti gli altri casiPrivate Persons As New ArrayList'Per scorrere la collezione, si userà un comune indicePrivate Index As Int32
'Essendo una normalissima classe, è lecito definire un'costruttore, che in questo caso inizializza la'collezioneSub New(ByVal Persons As ArrayList)
'Ricordate: poichè ArrayList deriva da Object, è'un tipo reference. Assegnare Persons a Me.Persons'equivale ad assegnarne l'indirizzo e quindi ogni'modifica su questo arraylist privato si rifletterà'su quello passato come parametro. Si può'evitare questo problema clonando la lista
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 -1Index = -1
'Dato che l'enumeratore deve scorrere la lista'secondo l'anno di nascita, bisogna prima ordinarlaMe.Persons.Sort(New BirthDayComparer)
End Sub
'Restituisce l'elemento correntePublic ReadOnly Property Current() As Object _
Implements System.Collections.IEnumerator.CurrentGet
Return Persons(Index)End Get
End Property
'Restituisce True se esiste l'elemento successivo e lo'imposta, altrimenti FalsePublic Function MoveNext() As Boolean _
Implements System.Collections.IEnumerator.MoveNextIf Index = Persons.Count - 1 Then
Return FalseElse
Index += 1Return True
End IfEnd Function
'Resetta il cicloPublic Sub Reset() _
Implements System.Collections.IEnumerator.ResetIndex = -1
End SubEnd Class
Public ReadOnly Property Persons() As ArrayList
GetReturn _Persons
End GetEnd Property
Public ReadOnly Property AverageAge() As String
GetDim Temp As TimeSpanFor Each P As Person In _Persons
Temp = Temp.Add(Date.Now - P.BirthDay)NextTemp = TimeSpan.FromSeconds(Temp.TotalSeconds / _Persons.Count)
Dim Years As Int32Years = Temp.TotalDays \ 365Temp = _Temp.Subtract(TimeSpan.FromDays((Temp.TotalDays \ 365) * 365))Return Years & " anni e " & CInt(Temp.TotalDays) & " giorni"
End GetEnd 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ù giovanePublic Function GetEnumerator() As IEnumerator _
Implements IEnumerable.GetEnumeratorReturn New PersonAgeEnumerator(_Persons)
End FunctionEnd Class
Sub Main()
ICloneableCome 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 nuovo 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:
L'inter faccia, invece, come accadeva per IEnumer able e ICompar able, indica al .NET Fr amewor k che l'oggetto è clonabile.
Questo codice mostr a la funzione Close all'oper a:
104.105.106.107.108.109.110.111.112.113.114.115.116.117.118.119.120.121.122.123.124.
Dim Persons As New PersonCollectionWith 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
'Enumera ora per data di nascita, ma senza modificare'l'ordine degli elementiFor Each P As Person In Persons
Console.WriteLine(P.BirthDay.ToShortDateString & ", " & _P.CompleteName)
Next
'Stampa la prima persona, dimostrando che l'ordine'della lista è intattoConsole.WriteLine(Persons.Persons(0).CompleteName)
Console.ReadKey()
End SubEnd Module
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.17.18.19.20.21.22.
Module EsempioSub Main()
'Il tipo ArrayList espone il metodo CloneDim S1 As New ArrayListDim S2 As New ArrayList
S2 = S1
'Verifica che S1 e S2 puntano lo stesso oggettoConsole.WriteLine(S1 Is S2)'> True
'Clona l'oggettoS2 = S1.Clone'Verifica che ora S2 referenzia un oggetto differente,'ma di valore identico a S1Console.WriteLine(S1 Is S2)'> False
Console.ReadKey()
End SubEnd Module
01.02.03.04.05.06.07.08.09.10.
Module Module1Class UnOggetto
Implements ICloneablePrivate _Campo As Int32
Public Property Campo() As Int32
GetReturn _Campo
End Get
Or a, è impor tante distinguer e due tipi di copia: quella Shallow 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.
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.
Set(ByVal Value As Int32)_Campo = Value
End SetEnd Property
'Restituisce una copia dell'oggettoPublic 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 casoReturn Me.MemberwiseClone
End Function
'L'operatore = permette di definire de due oggetti hanno un'valore ugualeShared Operator =(ByVal O1 As UnOggetto, ByVal O2 As UnOggetto) As _
BooleanReturn O1.Campo = O2.Campo
End Operator
Shared Operator <>(ByVal O1 As UnOggetto, ByVal O2 As UnOggetto) As _BooleanReturn Not (O1 = O2)
End OperatorEnd Class
Sub Main()
Dim O1 As New UnOggettoDim O2 As UnOggetto = O1.Clone
'I due oggetti NON sono lo stesso oggetto: il secondo'è solo una copia, disgiunta da O1Console.WriteLine(O1 Is O2)'> False
'Tuttavia hanno lo stesso identico valoreConsole.WriteLine(O1 = O2)'> True
Console.ReadKey()
End SubEnd Module
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
tipizzazione debole, 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 tipizzazione for 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 Download: 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 lassiPer 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:
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:
1.2.3.
Class Class1 End Class
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.
Namespace PostalManagement
Public Interface IIdentifiableReadOnly Property Id() As Int32Function ToString() As String
End Interface
Public Class PackImplements IIdentifiable
Private _Id As Int32Private _Destination As StringPrivate _Dimensions(2) As Single
Public ReadOnly Property Id() As Integer Implements IIdentifiable.Id
GetReturn _Id
End GetEnd Property
Public Property Destination() As String
GetReturn _Destination
End GetSet(ByVal value As String)
_Destination = valueEnd Set
End Property
Public Property Dimensions(ByVal Index As Int32) As SingleGet
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) ThenReturn _Dimensions(Index)
ElseThrow New IndexOutOfRangeException()
End IfEnd GetSet(ByVal value As Single)
If (Index >= 0) And (Index < 3) Then_Dimensions(Index) = value
ElseThrow New IndexOutOfRangeException()
End IfEnd Set
End Property
Public Sub New(ByVal Id As Int32)_Id = Id
End Sub
Public Overrides Function ToString() As String Implements IIdentifiable.ToStringReturn 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 FunctionEnd Class
Public Class Telegram
Implements IIdentifiable
Private _Id As Int32Private _Recipient As StringPrivate _Message As String
Public ReadOnly Property Id() As Integer Implements IIdentifiable.Id
GetReturn _Id
End GetEnd Property
Public Property Recipient() As String
GetReturn _Recipient
End GetSet(ByVal value As String)
_Recipient = valueEnd Set
End Property
Public Property Message() As StringGet
Return _MessageEnd GetSet(ByVal value As String)
_Message = valueEnd Set
End Property
Public Sub New(ByVal Id As Int32)_Id = Id
End Sub
Public Overrides Function ToString() As String Implements IIdentifiable.ToStringReturn String.Format("{0:0000}: Telegramma per {1} ; Messaggio = {2}", _
Me.Id, Me.Recipient, Me.Message)End Function
End Class
Public Class MoneyOrderImplements IIdentifiable
Private _Id As Int32Private _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.IdGet
Return _IdEnd Get
End Property
Public Property Recipient() As StringGet
Return _RecipientEnd GetSet(ByVal value As String)
_Recipient = valueEnd Set
End Property
Public Property Money() As SingleGet
Return _MoneyEnd GetSet(ByVal value As Single)
_Money = valueEnd Set
End Property
Public Sub New(ByVal Id As Int32)_Id = Id
End Sub
Public Overrides Function ToString() As String Implements IIdentifiable.ToStringReturn String.Format("{0:0000}: Vaglia postale per {1} ; Ammontare = {2}€", _
Me.Id, Me.Recipient, Me.Money)End Function
End Class
Public Class PostalProcessorPublic Delegate Function IdSelector(ByVal Id As Int32) As Boolean
Private _StorageCapacity As Int32Private _NextId As Int32 = 0Private Storage() As IIdentifiable
Public Property StorageCapacity() As Int32
GetReturn _StorageCapacity
End GetSet(ByVal value As Int32)
_StorageCapacity = valueReDim Preserve Storage(value)
End SetEnd Property
Public Property Item(ByVal Index As Int32) As IIdentifiable
GetIf (Index >= 0) And (Index < Storage.Length) Then
Return Me.Storage(Index)Else
Throw New IndexOutOfRangeException()End If
End GetSet(ByVal value As IIdentifiable)
If (Index >= 0) And (Index < Storage.Length) ThenMe.Storage(Index) = value
ElseThrow New IndexOutOfRangeException()
End IfEnd Set
End Property
Public ReadOnly Property FirstPlaceAvailable() As Int32Get
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 - 1If Me.Storage(I) Is Nothing Then
Return IEnd If
NextReturn (-1)
End GetEnd Property
Public ReadOnly Property NextId() As Int32
Get_NextId += 1Return _NextId
End GetEnd 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 ThenContinue For
End IfIf Selector.Invoke(K.Id) Then
Console.WriteLine(K.ToString())End If
NextEnd Sub
Public Sub PrintById(ByVal Id As Int32)
For Each K As IIdentifiable In StorageIf K Is Nothing Then
Continue ForEnd IfIf K.Id = Id Then
Console.WriteLine(K.ToString())Exit For
End IfNext
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 ThenContinue For
End IfIf K.ToString().Contains(Str) Then
Temp.Add(K.Id)End If
Next
Dim Result(Temp.Count - 1) As Int32For I As Int32 = 0 To Temp.Count - 1
Result(I) = Temp(I)Next
Temp.Clear()Temp = Nothing
Return Result
End FunctionEnd Class
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 progettoUna 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 imento, 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"):
249. End Namespace
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 Download):
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:
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:
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
1. Imports [Nome Libreria].[Nome Namespace]
1. Imports PostalManagement.PostalManagement
tr ovar e il name Pack nel contesto cor r ente). Ecco un esempio:
che equivale a:
Nella scheda ".NET" che vedete nella seconda immagine di sopr a, ci sono molte libr er ie facenti par te del Fr amewor k che
user emo nelle pr ossime sezioni della guida.
01.02.03.04.05.06.07.08.09.10.11.12.13.
Imports PostalManagement.PostalManagement Module Module1
Sub Main()Dim P As New PostalProcessor(10)Dim Pk As New Pack(P.NextId)
P.Item(P.FirstPlaceAvailable) = Pk'...
End Sub End Module
01.02.03.04.05.06.07.08.09.10.11.
Module Module1
Sub Main()Dim P As New PostalManagement.PostalManagement.PostalProcessor(10)Dim Pk As New PostalManagement.PostalManagement.Pack(P.NextId)
P.Item(P.FirstPlaceAvailable) = Pk'...
End Sub End Module
A41. I Generics - Parte I
Panoramica sui GenericsI 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:
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:
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:
1. [NomeEntità](Of [NomeTipo])
01.02.03.04.05.06.07.08.09.10.
Dim A As New ArrayListA.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 EachFor Each V As Int32 In AConsole.WriteLine(V)
Next
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)
E questa è una dimostr azione dell'incr emento delle pr estazioni:
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.
Generics StandardUna 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:
06.07.08.09.10.11.
A.Add(1)A.Add(4)A.Add(8)'A.Add("C") '<- ImpossibileFor Each V As Int32 In AConsole.WriteLine(V)
Next
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.
Module Module1Sub Main()
Dim TipDebole As New ArrayListDim TipForte As New List(Of Int32)Dim S As New Stopwatch
'Cronometra le operazioni su ArrayListS.Start()For I As Int32 = 1 To 1000000
TipDebole.Add(I)NextS.Stop()Console.WriteLine(S.ElapsedMilliseconds & _
" millisecondi per ArrayList!")
'Cronometra le operazioni su ListS.Reset()S.Start()For I As Int32 = 1 To 1000000
TipForte.Add(I)NextS.Stop()Console.WriteLine(S.ElapsedMilliseconds & _
" millisecondi per List(Of T)!")
Console.ReadKey()End Sub
End Module
001.002.003.004.005.006.007.008.009.010.011.
Module Module1'Collezione generica che contiene un qualsiasi tipo T di'oggetto. T si dice "tipo generic aperto"Class Collection(Of T)
'Per ora limitiamoci a dichiarare un array interno'alla classe.'Vedremo in seguito che è possibile ereditare da'una collezione generics già esistente.'Notate che la variabile è di tipo T: una volta che'abbiamo dichiarato la classe come generics su un tipo T,
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
GetIf (Index >= 0) And (Index < _Values.Length) Then
Return _Values(Index)Else
Throw New IndexOutOfRangeException()End If
End GetSet(ByVal value As T)
If (Index >= 0) And (Index < _Values.Length) Then_Values(Index) = value
ElseThrow New IndexOutOfRangeException()
End IfEnd Set
End Property
'Proprietà che restituiscono il primo e l'ultimo'elemento della collezionePublic ReadOnly Property First() As T
GetReturn _Values(0)
End GetEnd Property
Public ReadOnly Property Last() As T
GetReturn _Values(_Values.Length - 1)
End GetEnd 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 _ValuesIf V IsNot Nothing Then
Console.WriteLine(V.ToString())End If
NextEnd Sub
'Inizializza la collezione con Count elementi, tutti del'valore DefaultValueSub New(ByVal Count As Int32, ByVal DefaultValue As T)
If Count < 1 ThenThrow New ArgumentOutOfRangeException()
End If
ReDim _Values(Count - 1)
For I As Int32 = 0 To _Values.Length - 1_Values(I) = DefaultValue
NextEnd 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
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:
Metodi Generics e tipi generics collegati implic itiSe 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...).
Ecco un semplice esempio:
084.085.086.087.088.089.090.091.092.093.094.095.096.097.098.099.100.101.102.103.104.
'generico T, si dicono "tipi generic collegati",'poiché collegano il tipo fittizio T con un'reale tipo esistenteDim Strings As New Collection(Of String)(10, "null")Dim Integers As New Collection(Of Int32)(5, 12)Dim Dates As New Collection(Of Date)(7, Date.Now)Dim Persons As New Collection(Of Person)(10, Nothing)
Strings.Values(0) = "primo"Integers.Values(3) = 45Dates.Values(6) = New Date(2009, 1, 1)Persons.Values(3) = New Person("Mario", "Rossi", Dates.Last)
Strings.PrintAll()Integers.PrintAll()Dates.PrintAll()Persons.PrintAll()
Console.ReadKey()
End Sub End Module
1.2.
Console.WriteLine(Strings.GetType() Is Integers.GetType())'Output : False
1.2.3.4.5.6.7.
Sub [NomeProcedura](Of T)([Parametri])'...
End Sub Function [NomeFunzione](Of T)([Parametri]) As [TipoRestituito]
'...End Function
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.17.
Module Module1
'Scambia i valori di due variabili, passate'per indirizzoPublic Sub Swap(Of T)(ByRef Arg1 As T, ByRef Arg2 As T)
Dim Temp As T = Arg1Arg1 = Arg2Arg2 = Temp
End Sub
Sub Main()Dim X, Y As DoubleDim Z As SingleDim A, B As String
X = 90.0
Generics multipliQuando, 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:
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.
Y = 67.58Z = 23.01A = "Ciao"B = "Mondo"
'Nelle prossime chiamate, Swap non presenta un'tipo generics collegato: il tipo viene dedotto dai'tipi degli argomenti 'X e Y sono Double, quindi richiama il metodo con'T = DoubleSwap(X, Y)'A e B sono String, quindi richiama il metodo con'T = StringSwap(A, B) 'Qui viene generato un errore: nonostante Z sia'convertibile in Double implicitamente senza perdita'di dati, il suo tipo non corrisponde a quello di X,'dato che c'è un solo T, che può assumere'un solo valore-tipo. Per questo è necessario'utilizzare una scappatoia'Swap(Z, X) 'Soluzione 1: si esplicita il tipo generic collegatoSwap(Of Double)(Z, X)'Soluzione 2: si converte Z in double esplicitamenteSwap(CDbl(Z), X)
Console.ReadKey()
End SubEnd Module
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.
Module Module2'Una relazione qualsiasi fra due oggetti di tipo indeterminatoPublic Class Relation(Of T1, T2)
Private Obj1 As T1Private Obj2 As T2
Public ReadOnly Property FirstObject() As T1
GetReturn Obj1
End GetEnd Property
Public ReadOnly Property SecondObject() As T2
GetReturn Obj2
End GetEnd Property
Sub New(ByVal Obj1 As T1, ByVal Obj2 As T2)
Me.Obj1 = Obj1Me.Obj2 = Obj2
End SubEnd Class
Sub Main()
'Crea una relazione fra uno studente e un insegnante,'utilizzando le classi create nei capitoli precedentiDim R As Relation(Of Student, Teacher)Dim S As New Student("Pinco", "Pallino", Date.Parse("25/06/1990"), _
"Liceo Scientifico N. Copernico", 4)Dim T As New Teacher("Mario", "Rossi", Date.Parse("01/07/1950"), _
Notate che è anche possibile cr ear e una r elazione tr a due r elazioni (e la cosa diventa complicata):
Alcune regole per l'uso dei Generics
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:
Tuttavia si può er editar e da una classe gener ics specificando come tipo gener ics collegato lo stesso tipo aper to:
Allo stesso modo, non si può implementar e T come se fosse un'inter faccia:
Ma si può implementar e un'inter faccia gener ics di tipo T:
33.34.35.36.37.38.39.40.41.
"Matematica")
'Crea una nuova relazione tra lo studente e l'insegnanteR = New Relation(Of Student, Teacher)(S, T)Console.WriteLine(R.FirstObject.CompleteName)Console.WriteLine(R.SecondObject.CompleteName)
Console.ReadKey()
End SubEnd Module
01.
02.03.04.05.06.07.08.09.
10.11.12.13.14.15.16.17.18.
Dim S As New Student("Pinco", "Pallino", Date.Parse("25/06/1990"), "Liceo Scientifico N.Copernico", 4)
Dim T As New Teacher("Mario", "Rossi", Date.Parse("01/07/1950"), "Matematica")Dim StudentTeacherRelation As Relation(Of Student, Teacher)Dim StudentClassRelation As Relation(Of Student, String)Dim Relations As Relation(Of Relation(Of Student, Teacher), Relation(Of Student, String)) StudentTeacherRelation = New Relation(Of Student, Teacher)(S, T)StudentClassRelation = New Relation(Of Student, String)(S, "5A")Relations = New Relation(Of Relation(Of Student, Teacher), Relation(Of Student, String))
(StudentTeacherRelation, StudentClassRelation) 'Relations.FirstObject.FirstObject' > Student "Pinco Pallino"'Relations.FirstObject.SecondObject' > Teacher "Mario Rossi"'Relations.SecondObject.FirstObject' > Student "Pinco Pallino"'Relations.SecondObject.SecondObject' > String "5A"
1.2.3.4.
Class Example(Of T)Inherits T' SBAGLIATO
End Class
1.2.3.4.
Class Example(Of T)Inherits List(Of T)' CORRETTO
End Class
1.2.3.4.
Class Example(Of T)Implements T' SBAGLIATO
End Class
1.2.3.4.
Class Example(Of T)Implements IEnumerable(Of T)' CORRETTO
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:
End Class
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
Interfacce GenericsPr 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.
I VincoliI 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:
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
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.
Module Module1'Questa classe implementa un comaparatore di oggetti Student'in base al loro anno di corsoClass StudentByGradeComparer
Implements IComparer(Of Student)
'Come potete osservare, in questo metodo non viene eseguito'nessun tipo di cast, poiché l'interfaccia IComparer(Of T)'prevede un metodo Compare a tipizzazione forte. Dato che'abbiamo specificato come tipo generic collegato Student,'anche il tipo a cui IComparer si riferisce sarà'Student. Possiamo accedere alle proprietà di x e y'senza nessun late binding (per ulteriori informazioni,'vedere i capitoli sulla reflection)Public Function Compare(ByVal x As Student, ByVal y As Student) As Integer Implements
IComparer(Of Student).CompareReturn x.Grade.CompareTo(y.Grade)
End FunctionEnd Class
Sub Main()
'Crea un nuovo array di oggeti StudentDim S(2) As Student
'Inizializza ogni oggettoS(0) = New Student("Mario", "Rossi", New Date(1993, 2, 3), "Liceo Classico Ugo
Foscolo", 2)S(1) = New Student("Luigi", "Bianchi", New Date(1991, 6, 27), "Liceo Scientifico
Fermi", 4)S(2) = New Student("Carlo", "Verdi", New Date(1992, 5, 12), "ITIS Cardano", 1)
'Ordina l'array con il comparer specificatoArray.Sort(S, New StudentByGradeComparer())
'Stampa il profilo di ogni studente: vedrete che essi sono'in effetti ordinati in base all'anno di corsoFor Each St As Student In S
Console.WriteLine(St.Profile)NextConsole.ReadKey()
End Sub End Module
1. If A = B Then '...
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 incolo, 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.
Vincolo di Interfacc iaIl 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:
Ecco un esempio:
1. (Of T As [Interfaccia])
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.
Module Module1
'Questa classe rappresenta una collezione di'elementi che possono essere comparati. Per questo'motivo, il tipo T espone un vincolo di interfaccia'che obbliga tutti i tipi generics collegati ad'implementare tale interfaccia.'Notate bene che in questo caso particolare ho usato'un generics doppio, poiché il vincolo non'si riferisce a IComparable, ma a IComparable(Of T).'D'altra parte, è abbastanza ovvio che se'una collezione contiene un solo tipo di dato,'basterà che la comparazione sia possibile'solo attraverso oggetti di quel tipoClass ComparableCollection(Of T As IComparable(Of T))
'Ereditiamo direttamente da List(Of T), acquisendone'automaticamente tutti i membri base e le caratteristiche.'In questo modo, godremo di due grandi vantaggi:' - non dovremo definire tutti i metodi per aggiungere,' rimuovere o cercare elementi, in quanto vengono tutti' ereditati dalla classe base List;' - non dovremo neanche implementare l'interfaccia' IEnumerable(Of T), poiché la classe base la' implementa di per sé.Inherits List(Of T)
'Dato che gli oggetti contenuti in oggetti di'questo tipo sono per certo comparabili, possiamo'trovarne il massimo ed il minimo. 'Trova il massimo elementoPublic ReadOnly Property Max() As T
Get
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 ThenDim Result As T = Me(0)
For Each Element As T In Me
'Ricordate che A.CompareTo(B) restituisce'1 se A > BIf Element.CompareTo(Result) = 1 Then
Result = ElementEnd If
Next
Return ResultElse
Return NothingEnd If
End GetEnd Property
'Trova il minimo elementoPublic ReadOnly Property Min() As T
GetIf Me.Count > 0 Then
Dim Result As T = Me(0)
For Each Element As T In MeIf Element.CompareTo(Result) = -1 Then
Result = ElementEnd If
Next
Return ResultElse
Return NothingEnd If
End GetEnd Property
'Trova tutti gli elementi uguali ad A e ne restituisce'gli indiciPublic Function FindEquals(ByVal A As T) As Int32()
Dim Result As New List(Of Int32)
For I As Int32 = 0 To Me.Count - 1If 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 elementiReturn Result.ToArray()
End Function
End Class Sub Main()
'Tre collezioni, una di interi, una di stringhe e'una di dateDim 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())' > 4Console.WriteLine(A.Max())' > 90Console.WriteLine(B.Min())
Vincolo 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:
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:
Vincoli di c lasse e strutturaIl 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:
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:
106.107.108.109.110.111.112.113.114.115.116.117.118.119.120.121.122.
' > accaConsole.WriteLine(B.Max())' > zenConsole.WriteLine(C.Min().ToShortDateString)' > 31/12/1999Console.WriteLine(C.Max().ToShortDateString)' > 12/4/2100
'Trova la posizione degli elementi uguali a 4Dim AEqs() As Int32 = A.FindEquals(4)' > 0 6 8Dim BEqs() As Int32 = B.FindEquals("casa")' > 1 4
Console.ReadKey()
End Sub End Module
1.2.3.4.
Class IsARelation(Of T, U As T)Public Base As TPublic Derived As U
End Class
1.2.3.4.
Dim P As PersonDim S As Student'...Dim A As New IsARelation(Of Person, Student)(P, S)
1.2.
(Of T As Class)(Of T As Structure)
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.
Vincolo NewQuesto 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:
Vincoli multipliUn 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:
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.
Module Module1
'Con molta fantasia, il vincolo New si dichiara postponendo'"As New" al tipo generic aperto.Function CreateArray(Of T As New)(ByVal Count As Int32) As T()
Dim Result(Count - 1) As T
For I As Int32 = 0 To Count - 1'Possiamo usare il costruttore perchè il'vincolo ce lo assicuraResult(I) = New T()
Next
Return ResultEnd Function
Sub Main()
'Crea 10 flussi di dati in memoria. Non abbiamo'mai usato questa classe perchè rientra in'un argomento che tratterò più avanti, ma'è una classe particolarmente utile e versatile'che trova applicazioni in molte situazioni.'Avere un bel metodo generics che ne crea 10 in una'volta è una gran comodità.'Ovviamente possiamo fare la stessa cosa con tutti'i tipi che espongono almeno un New senza parametriDim Streams As IO.MemoryStream() = CreateArray(Of IO.MemoryStream)(10)
'...
End Sub
End Module
1. (Of T As {Vincolo1, Vincolo2, ...})
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.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.
Module Module1
'Classe che filtra dati di qualsiasi naturaClass DataFilter(Of T)
Delegate Function FilterData(ByVal Data As T) As Boolean
'La signature chilometrica è fatta apposta per'farvi impazzire XD Vediamo le parti una per una:' - TSerach: deve essere un tipo uguale a T o derivato' da T, in quanto stiamo elaborando elementi di tipo T;' inoltre deve anche essere clonabile, poiché' salveremo solo una copia dei valor trovati.' Questo implica che TSearch sia un tipo reference, e che' quindi lo sia anche T: questa complicazione è solo' per mostrare dei vincoli multipli e potete anche' rimuoverla se vi pare;' - TList: deve essere un tipo reference, esporre un' costruttore senza parametri ed implementare' l'interfaccia IList(Of TSearch), ossia deve' essere una lista;' - ResultList: lista in cui riporre i risultati (passata' per indirizzo);' - Filter: delegate che punta alla funzione usata per' selezionare i valori;' - Data: paramarray contenente i valori da filtrare.Sub Filter(Of TSearch As {ICloneable, T}, TList As {IList(Of TSearch), New, Class}) _
(ByRef ResultList As TList, ByVal Filter As FilterData, ByVal ParamArray Data() AsTSearch)
'Se la lista è Nothing, la inizializza.'Notare che non avremmo potuto compararla a Nothing'senza il vincolo Class, né inizializzarla'senza il vincolo NewIf ResultList Is Nothing Then
ResultList = New TList()End If
'Itera sugli elementi di dataFor Each Element As TSearch In Data
'E aggiunge una copia di quelli che'soddisfano la condizioneIf Filter.Invoke(Element) Then
'Aggiunge una copia dell'elemento alla lista.'Anche in questo non avremmo potuto richiamare'Add senza il vincolo interfaccia su IList, né'clonare Element senza il vincolo interfaccia ICloneableResultList.Add(Element.Clone())
End IfNext
End SubEnd Class
'Controlla se la stringa A è palindromaFunction IsPalindrome(ByVal A As String) As Boolean
Dim Result As Boolean = True
For I As Int32 = 0 To (A.Length / 2) - 1If A.Chars(I) <> A.Chars(A.Length - 1 - I) Then
Result = FalseExit For
End IfNext
Return Result
End Function
Sub Main()Dim DF As New DataFilter(Of String)'Lista di stringhe: notare che la variabile non'contiene nessun oggetto perchè non abbiamo usato New.
71.72.73.74.75.76.77.78.79.80.81.82.83.84.85.86.
'Serve per mostrare che verrà inizializzata'da DF.Filter.Dim L As List(Of String)
'Analizza le stringhe passate, trova quelle palindrome'e le pone in LDF.Filter(L, AddressOf IsPalindrome, _
"casa", "pane", "anna", "banana", "tenet", "radar")
For Each R As String In LConsole.WriteLine(R)
Next
Console.ReadKey()End Sub
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:
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:
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.02.03.04.05.06.07.08.09.
If Cell.Value IsNot DBNull.Value ThenVariable = Cell.Value
ElseVariable = 0'Per impostare il valore di default, bisognava ripetere'questi If tante volte quanti erano i tipi in gioco, poiché'non c'era modo di assegnare un valore Null a tutti'in un solo colpo
End If
1.2.3.4.5.
'Dichiarazione esplicita:Dim [Nome] As Nullable(Of [Tipo]) 'Dichiarazione implicita:Dim [Nome] As [Tipo]?
01.02.03.04.05.06.07.08.
Module Module1
Sub Main()'Tre variabili di tipo value dichiarate come'nullable nei due modi diversi consentitiDim Number As Integer?Dim Data As Nullable(Of Date)
Logica booleana a tre valoriUn valor e nullable Boolean può assumer e vir tualmente tr e valor i: ver o (Tr ue), falso (False) e null (senza valor e). Usando
una var iabile booleana nullable come oper ando per gli oper ator i logici, si otter r anno r isultati diver si a seconda che
essa abbia o non abbia un valor e. Le nuove combinazioni che possono esser e eseguite si vanno ad aggiunger e a quelle
già esistenti per cr ear e un nuovo tipo di logica elementar e, detta, appunto, "logica booleana a tr e valor i". Essa segue
questo schema nei casi in cui un oper ando sia null:
Valor e 1 Oper ator e Valor e 2 Risultato
Tr ue And Null Null
False And Null False
Tr ue Or Null Tr ue
False Or Null Null
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.
Dim Cost As Double?Dim Sent As Nullable(Of Boolean)
'Ammettiamo di star controllando un database:'questo array di oggetti rappresenta il contenuto'di una rigaDim RowValues() As Object = {DBNull.Value, New Date(2009, 7, 1), 67.99, DBNull.Value}
'Con un solo ciclo trasforma tutti i DBNull.Value'in Nothing, poiché i nullable supportano solo'Nothing come valore nulloFor I As Int16 = 0 To RowValues.Length - 1
If RowValues(I) Is DBNull.Value ThenRowValues(I) = Nothing
End IfNext
'Assegna alle variabili i valori contenuti nell'array:'non ci sono mai problemi in questo codice, poiché,'trattandosi di tipi nullable, questi oggetti possono'accettare anche valori Nothing. In questo esempio,'Number e Sent riceveranno un Nothing come valore: la'loro proprietà HasValue varrà False.Number = RowValues(0)Data = RowValues(1)Cost = RowValues(2)Sent = RowValues(3)
'Scrive a schermo il valore di ogni variabile, se ne'contiene uno, oppure il valore di default se non'contiene alcun valore.Console.WriteLine("{0} {1} {2} {3}", _
Number.GetValueOrDefault, _Data.GetValueOrDefault, _Cost.GetValueOrDefault, _Sent.GetValueOrDefault)
'Provando a stampare una variabile nullable priva'di valore senza usare la funzione GetValueOrDefault,'semplicemente non stamperete niente:' Console.WriteLine(Number)'Non stampa niente e va a capo. Console.ReadKey()
End Sub End Module
Tr ue Xor Null Null
False Xor Null Null
A44. La Reflection - Parte I
Con il ter mine gener ale di r eflection si intendono tutte le classi del Fr amewor k che per mettono di acceder e o
manipolar e assembly e moduli.
Assembly
L'assembly è l'unità logica più piccola su cui si basa il Fr amewor k .NET. Un assembly altr o non è che un pr ogr amma o
una libr er ia di classi (compilati in .NET). Il Fr amewor k stesso è composto da una tr entina di assembly pr incipali che
costituiscono le libr er ie di classi più impor tanti per la pr ogr ammazione .NET (ad esempio System.dll,
System.Dr aw ing.dll, System.Cor e.dll, ecceter a...).
Il ter mine Reflection ha un significato molto pr egnante: la sua tr aduzione in italiano è alquanto lampante e significa
"r iflessione". Dato che viene usata per ispezionar e, analizzar e e contr ollar e il contenuto di assembly, r isulta evidente
che mediante r eflection noi scr iviamo del codice che analizza altr o codice, anche se compilato: è una specie di
our obor os, il ser pente che si mor de la coda; una r iflessione della pr ogr ammazione su se stessa, appunto.
Lasciando da par te questo inter cor so filosofico, c'è da dir e che la r eflection è di gr an lunga una delle tecniche più
utilizzate dall'IDE e dal Fr amewor 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 dinamico 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 ContestiPr 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 domini
applicativ i o AppDomain o contesti di esecuzione. 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 Windows 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.
Caricare un assemblyUn 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 nome completo 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 amewor 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 amewor 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 amewor 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 contesto di ispezione. Quest'ultimo è unico per ogni pr ocesso e condiviso da tutti gli AppDomain
pr esenti nel pr ocesso.
Nome dell'assembly e analisi superfic ialeUna 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;
Global A ssembly Cache (GAC)
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 amewor k .NET, ma è possibile aggiunger ne altr i con deter minati
comandi. La GAC di Windows è di solito posizionata in C:\WINDOWS\assembly e contiene tutte le libr er ie base
del Fr amewor 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 nome completo 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 amewor 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.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.
Module Module1
Sub Main()'Carica un assembly per soli scopi di analisi.'mscorlib è l'assembly più importante di'tutto il Framework, da cui deriva pressochè ogni'cosa. Data la sua importanza, non ha dipendenze,'perciò non ci saranno problemi nel risolverle.'Se volete caricare un altro assembly, dovrete usare'uno dei metodi in grado di risolvere le dipendenze.Dim Asm As Assembly = Assembly.ReflectionOnlyLoad("mscorlib")Dim Name As AssemblyName = Asm.GetName
Console.WriteLine(Asm.FullName)Console.WriteLine("Nome: " & Name.Name)Console.WriteLine("Versione: " & Name.Version.ToString)Console.WriteLine("Cultura: " & Name.CultureInfo.Name)
'Il formato X indica di scrivere un numero usando la'codifica esadecimale. X2 impone di occupare sempre almeno'due posti: se c'è una sola cifra, viene inserito'uno zero.Console.Write("Public Key: ")For Each B As Byte In Name.GetPublicKey()
Console.Write("{0:X2}", B)NextConsole.WriteLine()
Console.Write("Public Key token: ")For Each B As Byte In Name.GetPublicKeyToken
Console.Write("{0:X2}", B)NextConsole.WriteLine()
Console.WriteLine("Processore: " & _
Name.ProcessorArchitecture.ToString)
Console.ReadKey()
End Sub 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.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.17.18.19.
Sub EnumerateAssemblies()Dim Asm As AssemblyDim Name As AssemblyName
'AppDomain è una variabile globale, oggetto singleton, da cui'si possono trarre informazioni sull'AppDomain corrente o'crearne degli altri.For Each Asm In AppDomain.CurrentDomain.GetAssemblies
Name = Asm.GetNameConsole.WriteLine("Nome: " & Name.Name)Console.WriteLine("Versione: " & Name.Version.ToString)Console.Write("Public Key Token: ")For Each B As Byte In Name.GetPublicKeyToken
Console.Write(Hex(B))NextConsole.WriteLine()Console.WriteLine()
NextEnd Sub
A45. La Reflection - Parte II
La c lasse System.TypeLa 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:
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.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.
Module Module1
Sub Main()'Ottiene un OT per il tipo double tramite'l'operatore GetTypeDim DoubleType As Type = GetType(Double)Console.WriteLine(DoubleType.FullName) 'Ottiene un OT per il tipo string tramite'la funzione statica Type.GetType. Essa richiede'come parametro il nome (possibilmente completo)'del tipo. Nel caso il nome non corrisponda a'nessun tipo, verrà restituito NothingDim StringType As Type = Type.GetType("System.String")Console.WriteLine(StringType.FullName)
'Ottiene un OT per il tipo ArrayList tramite'la funzione d'istanza GetType. Da notare che,'mentre le precedenti usavano come punto'di partenza direttamente un tipo (o il suo nome),'questa richiede un oggetto di quel tipo.Dim A As New ArrayListDim ArrayListType As Type = A.GetType()Console.WriteLine(ArrayListType.FullName)
Console.ReadKey()
End Sub End Module
01.02.03.04.
Module Module1
Sub Main()
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 amewor 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;
05.06.07.08.09.10.11.12.13.14.15.16.17.18.19.
Dim Type1 As Type = GetType(String)Dim Type2 As Type = Type.GetType("System.String")Dim Type3 As Type = "Ciao".GetType()
Console.WriteLine(Type1 Is Type2)'> TrueConsole.WriteLine(Type2 Is Type3)'> True
'Gli OT contenuti in Type1, Type2 e Type3'SONO lo stesso oggetto Console.ReadKey()
End Sub End Module
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:
Il nostro piccolo segretoPr 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.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.
Module Module1
Sub EnumerateTypes(ByVal Asm As Assembly)Dim Category As String
'GetTypes restituisce un array di Type che'indicano tutti i tipi definiti all'interno'dell'assembly AsmFor Each T As Type In Asm.GetTypes
If T.IsClass ThenCategory = "Class"
ElseIf T.IsInterface ThenCategory = "Interface"
ElseIf T.IsEnum ThenCategory = "Enumerator"
ElseIf T.IsValueType ThenCategory = "Structure"
ElseIf T.IsPrimitive ThenCategory = "Base Type"
End IfConsole.WriteLine("{0} ({1})", T.Name, Category)
NextEnd Sub
Sub Main()
'Ottiene un riferimento all'assembly in esecuzione,'quindi al programma. Non otterrete molti tipi'usando questo codice, a meno che il resto del'modulo non sia pieno di codice vario come nel'mio caso XDDim Asm As Assembly = Assembly.GetExecutingAssembly()
EnumerateTypes(Asm)
Console.ReadKey()
End Sub End Module
01.02.03.04.05.06.07.08.09.
Module Module1
'Analizza l'albero di ereditarietà di un tipoSub AnalyzeInheritance(ByVal T As Type)
'La proprietà BaseType restituisce la classe'base da cui T è derivataIf T.BaseType IsNot Nothing Then
Console.WriteLine("> " & T.BaseType.FullName)
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 membriFino 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;
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.
AnalyzeInheritance(T.BaseType)End If
End Sub
Enum StatusEnabledDisabledStandby
End Enum
Structure ExampleDim 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 End Module
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:
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
decompilazione. 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:
Member Info
Declar ingType : la classe che dichiar a questo membr o;
Member Type : categor ia del membr o;
Name : il nome del membr o;
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.
Module Module1Sub Main()
Dim T As Type = GetType(String)
'Elenca tutti i membri di StringFor Each M As MemberInfo In T.GetMembers
'La proprietà MemberType restituisce un enumeratore che'specifica di che tipo sia il membro, se una proprietà,'un metodo, un costruttore, eccetera...Console.WriteLine(M.MemberType.ToString & " " & M.Name)
Next
Console.ReadKey()End Sub
End Module
ReflectedType : il tipo usato per ottener e un r ifer imento a questo membr o tr amite r eflection;
MethodInfo
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
GetRawCostantValue() : 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;
PropertyInfo
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à.
EventInfo (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.002.003.004.005.006.007.008.009.010.
Module Module1'Analizza il metodo rappresentato dall'oggetto MISub AnalyzeMethod(ByVal MI As MethodInfo)
'Il nomeDim Name As String'Il nome completo, con scpecificatori di accesso,'modificatori, signature e tipo restituito. Per'ulteriori informazioni sul tipo StringBuilder,'vedere il capitolo "Magie con le stringhe"
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 accessoDim Scope As String'Gli eventuali modificatoriDim Modifier As String'La categoria: Sub o FunctionDim Category As String'La signature del metodo, che andremo a costruireDim 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 ReturnParameterIf MI.ReturnParameter IsNot Nothing AndAlso _
MI.ReturnType.FullName <> "System.Void" ThenCategory = "Function"
ElseCategory = "Sub"
End If
If MI.IsConstructor ThenName = "New"
ElseName = MI.Name
End If
If MI.IsAssembly ThenScope = "Friend"
ElseIf MI.IsFamily ThenScope = "Protected"
ElseIf MI.IsFamilyOrAssembly ThenScope = "Protected Friend"
ElseIf MI.IsPrivate ThenScope = "Private"
ElseScope = "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 ThenModifier = "MustOverride"
ElseIf MI.IsVirtual ThenModifier = "Overridable"
ElseIf MI.GetBaseDefinition IsNot Nothing AndAlso _MI IsNot MI.GetBaseDefinition ThenModifier = "Overrides"
End If
If MI.IsStatic ThenIf Modifier <> "" Then
Modifier = "Shared " & ModifierElse
Modifier = "Shared"End If
End If
'Inizia la signature con una parentesi tonda aperta.'Append aggiunge una stringa a SignatureSignature.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 virgolaIf 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 parametroIf P.ParameterType.IsByRef Then
Signature.Append("ByRef ")Else
Signature.Append("ByVal ")End If
'Se P è opzionale, ci vuole la keyword OptionalIf 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: rimediamoSignature.Append(" As " & P.ParameterType.Name.Replace("[]",""))
'Si ricordi che i parametri optional hanno un valore'di defaultIf P.IsOptional Then
Signature.Append(" = " & P.DefaultValue.ToString)End If
NextSignature.Append(")")
If MI.ReturnParameter IsNot Nothing AndAlso _
MI.ReturnType.FullName <> "System.Void" ThenSignature.Append(" As " & MI.ReturnType.Name)
End If
'Ed ecco il nome completoCompleteName.AppendFormat("{0} {1} {2} {3}{4}", Scope, Modifier, _Category, Name, Signature.ToString)Console.WriteLine(CompleteName.ToString)Console.WriteLine()
'Ora ci occupiamo del corpoDim MB As MethodBody = MI.GetMethodBody
If MB Is Nothing Then
Exit SubEnd If
'Massima memoria occupata sullo stackConsole.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 indiceConsole.WriteLine(" Var({0}) As {1}", L.LocalIndex, _
L.LocalType.Name)NextConsole.WriteLine()
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
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.
'Gestione delle eccezioniConsole.WriteLine("Gestori di eccezioni:")For Each Ex As ExceptionHandlingClause In MB.ExceptionHandlingClauses
'Tipo di clausola: distingue tra filtro (When),'clausola (Catch) o un blocco FinallyConsole.WriteLine(" Tipo : {0}", Ex.Flags.ToString)'Se si tratta di un blocco Catch, ne specifica la'naturaIf Ex.Flags = ExceptionHandlingClauseOptions.Clause Then
Console.WriteLine(" Catch Ex As " & Ex.CatchType.Name)End If'Offset, ossia posizione in bytes nel Try, del gestoreConsole.WriteLine(" Offset : {0}", Ex.HandlerOffset)'Lunghezza, in bytes, del codice eseguibile del gestoreConsole.WriteLine(" Lunghezza : {0}", Ex.HandlerLength)Console.WriteLine()
NextEnd Sub
Sub Test(ByVal Num As Int32, ByVal S As String)
Dim T As DateDim V As String
Try
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.GetMethodsDim 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)NextConsole.WriteLine()Index = Console.ReadLine
If Index >= 0 And Index &rt; Methods.Length Then
AnalyzeMethod(Methods(Index))End If
Console.ReadKey()
End Sub
End Module
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
Reflection dei genericsI 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:
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 non collegati). Ad esempio:
1.2.3.
Class Example(Of T, K)'...
End Class
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.
Module Module1
'Dichiaro questa classe e la prossima variabile come'pubblici perchè se fossero Friend bisognerebbe'usare un overload troppo lungo di GetField e'GetNestedTypes specificando ci cercare i membri non'pubblici. Di default, le funzioni di ricerca operano'solo su membri pubblici Public Class Example(Of T)
End Class
Public E As Example(Of Int32)
Sub Main()
'Ottiene il tipo di questo moduloDim ModuleType As Type = GetType(Module1)
'Enumera tutti i tipi presenti nel modulo fino a'trovare la classe Example. Ho usato un for perchè'non si può usare GetType (in qualsiasi'sua versione) su una classe generics senza specificare'un tipo generics collegato, cosa che noi non'vogliamo affatto. Per ottenere il riferimento a'Example(Of T) bisogna per forza usare una funzione'che restituisca tutti i tipi esistenti e poi'cercarlo tra questi.For Each T As Type In ModuleType.GetNestedTypes()
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 izione 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:
31.32.33.34.35.36.37.38.39.40.41.
42.43.44.45.46.
If T.Name.StartsWith("Example") ThenConsole.WriteLine("{0} - IsGenericTypeDefinition: {1}", _
T.Name, T.IsGenericTypeDefinition)End If
Next
'Ottiene un riferimento al campo E dichiarayo sopraDim EField As FieldInfo = ModuleType.GetField("E")'E ne ottiene il tipoDim EType As Type = EField.FieldType
Console.WriteLine("{0} - IsGenericTypeDefinition: {1}", EType.Name,
EType.IsGenericTypeDefinition)
Console.ReadKey()End Sub
End Module
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.
Module Module1
Sub EnumerateGenerics(ByVal Asm As Assembly)For Each T As Type In Asm.GetTypes
'Controlla se si tratta di un tipo contenente'tipi generics apertiIf T.IsGenericTypeDefinition Then
'Ottiene il nome semplice di quel tipo (la'versione completa è troppo lunga XD)Dim Name As String = T.Name
'Controlla che il nome contenga l'accento tonico.'Infatti, possono esistere casi in cui la'propietà IsGeneircTypeDefinition è vera,'ma non ci troviamo di fronte a un tipo la cui'signature contenga effettivamente tipi generics.'Ne darò un esempio dopo...If Not Name.Contains("`") Then
Continue ForEnd If
'Ottiene una stringa in cui elimina tutti i'caratteri a partire dall'indice del'accentoName = T.Name.Remove(T.Name.IndexOf("`"))'E poi gli aggiunge un "(Of ", per far vedere che'si sta iniziando una dichiarazione genericName &= "(Of "'Quindi aggiunge tutti gli argomenti genericFor Each GenT As Type In T.GetGenericArguments
'Se il parametro non è il primo, lo separa dal'precedente con una virgola.If GenT.GenericParameterPosition > 0 Then
Name &= ", "End If'Quindi vi aggiunge il nomeName &= GenT.Name
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:
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:
38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57.58.59.60.61.
Next'E chiude la parentesiName &= ")"Console.WriteLine(Name)
End IfNext
End Sub '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 correnteDim Asm As Assembly = Assembly.GetExecutingAssembly()
EnumerateGenerics(Asm)
Console.ReadKey()
End Sub
End Module
1.2.3.4.
Class Example(Of T)Delegate Sub DoSomething(ByVal Data As T)'...
End Class
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.17.
Module Module1
'Enumera solo i campi generic di un tipoSub EnumerateGenericFieldMembers(ByVal T As Type)
For Each F As FieldInfo In T.GetFields()If F.FieldType.IsGenericType Then
Dim Name As String = F.FieldType.NameDim I As Int16 = 0
If Not Name.Contains("`") Then
Continue ForEnd If
Name = Name.Remove(Name.IndexOf("`"))Name &= "(Of "For Each GenP As Type In F.FieldType.GetGenericArguments
L'uso della ReflectionFino 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 owser ), 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à:
18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.
'Dato che non si stanno analizzando dei'parametri generic, non si può utilizzare'la proprietà GenericParameterPositionIf I > 0 Then
Name &= ", "End IfName &= GenP.NameI += 1
NextName &= ")"Console.WriteLine("Dim {0} As {1}", F.Name, Name)
End IfNext
End Sub
Public L As New List(Of Integer)Public I As Int32?
Sub Main()
EnumerateGenericFieldMembers(GetType(Module1))
Console.ReadKey()End Sub
End Module
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.
Module Module1
'Stampa tutte le informazioni ricavabili dalle'proprietà di un dato oggetto O. Indent è solo'una variabile d'appoggio per la formattazione, in modo'da indentare bene le righe nel caso i valori delle'proprietà siano altri oggetti.Public Sub PrintInfo(ByVal O As Object, ByVal Indent As String)
'Ottiene il tipo di ODim T As Type = O.GetType()
Console.WriteLine("{0}Object of type {1}", Indent, T.Name)'Enumera tutte le proprietàFor Each Prop As PropertyInfo In T.GetProperties()
'Ottiene il tipo restituito dalla proprietàDim PropType As Type = Prop.PropertyType()
'Se si tratta di una proprietà parametrica,'la salta: in questo esempio non volevo dilungarmi,'ma potete completare il codice se desiderate.If Prop.GetIndexParameters().Count > 0 Then
Continue ForEnd If
'Se è un di tipo base o una stringa (giacché le'stringhe non sono tipo base ma reference), ne stampa'direttamente il valore a schermoIf (PropType.IsPrimitive) Or (PropType Is GetType(String)) Then
Console.WriteLine("{0} {1} = {2}", _
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
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.
Indent, Prop.Name, Prop.GetValue(O, Nothing))
'Altrimenti, se si tratta di un oggetto, lo analizza a'sua voltaElseIf PropType.IsClass Then
Console.WriteLine("{0} {1} = ", Indent, Prop.Name)PrintInfo(Prop.GetValue(O, Nothing), Indent & " ")
End IfNextConsole.WriteLine()
End Sub
Sub Main()'Crea alcuni oggetti variDim P As New Person("Mario", "Rossi", New Date(1982, 3, 17))Dim T As New Teacher("Luigi", "Bianchi", New Date(1879, 8, 21), "Storia")Dim R As New Relation(Of Person, Teacher)(P, T)Dim Q As New List(Of Int32)Dim K As New 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 SubEnd Module
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.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.50.51.
Module Module1
'Non riscrivo PrintInfo, ma considero che stia'ancora in questo modulo
Sub Main()
Dim P As New Person("Mario", "Rossi", New Date(1982, 3, 17))Dim T As New Teacher("Luigi", "Bianchi", New Date(1879, 8, 21), "Storia")Dim R As New Relation(Of Person, Teacher)(P, T)Dim Q As New List(Of Int32)Dim K As New Text.StringBuilder()Dim Objects() As Object = {P, T, R, Q, K}Dim Cmd As Int32
Console.WriteLine("Oggetti nella collezione: ")For I As Int32 = 0 To Objects.Length - 1
Console.WriteLine("{0} - Istanza di {1}", _I, Objects(I).GetType().Name)
NextConsole.WriteLine("Inserire il numero corrispondente all'oggetto da modificare: ")Cmd = Console.ReadLine
If Cmd < 0 Or Cmd > Objects.Length - 1 Then
Console.WriteLine("Nessun oggetto corrispondente!")Exit Sub
End If
Dim Selected As Object = Objects(Cmd)Dim SelectedType As Type = Selected.GetType()Dim Properties As New List(Of PropertyInfo)
For Each Prop As PropertyInfo In SelectedType.GetProperties()
If (Prop.PropertyType.IsPrimitive Or Prop.PropertyType Is GetType(String)) And _Prop.CanWrite ThenProperties.Add(Prop)
End IfNext
Console.Clear()Console.WriteLine("Proprietà dell'oggetto:")For I As Int32 = 0 To Properties.Count - 1
Console.WriteLine("{0} - {1}", _I, Properties(I).Name)
NextConsole.WriteLine("Inserire il numero corrispondente alla proprietà da modificare:")Cmd = Console.ReadLine
If Cmd < 0 Or Cmd > Objects.Length - 1 Then
Console.WriteLine("Nessuna proprietà corrispondente!")Exit Sub
Chi ha letto anche la ver sione pr ecedente della guida, avr à notato che manca il codice per l'assembly br owser , 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).
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.
End If
Dim SelectedProp As PropertyInfo = Properties(Cmd)Dim NewValue As Object
Console.Clear()Console.WriteLine("Nuovo valore: ")NewValue = Console.ReadLine
'Imposta il nuovo valore della proprietà. Noterete che'si ottiene un errore di cast con tutti i tipi che'non siano String. Questo accade poiché viene'eseguito un matching sul tipo degli argomenti: se essi'sono diversi, indipendentemente dal fatto che possano'essere convertiti l'uno nell'altro (al contrario di'quanto dice il testo dell'errore), viene sollevata'quell'eccezione. Per aggirare il problema, si'dovrebbe eseguire un cast esplicito controllando prima'il tipo della proprietà:' If SelectedProp.PropertyType Is GetType(Int32) Then' NewValue = CType(NewValue, Int32)' ElseIf SelectedProp. ...'È il prezzo da pagare quando si lavora con'uno strumento così generale come la Reflection.'[Generalmente si conosce in anticipo il tipo]SelectedProp.SetValue(Selected, NewValue, Nothing)
Console.WriteLine("Proprietà modificata!")
PrintInfo(Selected, "")
Console.ReadKey()
End SubEnd Module
A47. La Reflection - Parte IV
Compilazione di codice a runtimeBene, 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 war 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.002.003.004.005.006.
Module Module1
'Questa classe rappresenta una funzione matematica:'ne ho racchiuso il nome tra parentesi quadre poiché Function'è una keyword del linguaggio, ma in questo caso la si
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 StringPrivate EvaluateFunction As MethodInfo
'Contiene l'espressione matematica che costruisce il valore'della funzione in base alla variabile xPublic Property Expression() As String
GetReturn _Expression
End GetSet(ByVal value As String)
_Expression = valueMe.CreateEvaluator()
End SetEnd 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 compilazioneDim 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 compilazioneDim 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 CompResultsDim CompResults As CompilerResults = _
Provider.CompileAssemblyFromSource(Parameters, Code)
'Se ci sono errori, lancia un'eccezioneIf 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]()
DoTry
Console.Clear()Console.WriteLine("Inserisci una funzione: ")Console.Write("f(x) = ")F.Expression = Console.ReadLineExit Do
Catch ex As ExceptionConsole.WriteLine("Espressione non valida!")Console.ReadKey()
End TryLoop
Dim Input As StringDim X As Double
Do
TryConsole.Clear()Console.WriteLine("Immettere 'stop' per terminare.")Console.WriteLine("Il programma calcola il valore di f in X: ")Console.Write("x = ")Input = Console.ReadLineIf Input <> "stop" Then
X = CType(Input, Double)Console.WriteLine("f(x) = {0}", F.Apply(X))Console.ReadKey()
Else
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:
CompilerParameter 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 indows 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 war ning come se fosser o er r or i. Questo impedisce di por tar e a ter mine la
compilazione quando ci sono degli war 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 war 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.
CompilerResults
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
151.152.153.154.155.156.157.158.
Exit DoEnd If
Catch Ex As ExceptionConsole.WriteLine(Ex.Message)Console.ReadKey()
End TryLoop
End SubEnd Module
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 war 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 programmiIl pr ossimo sor gente è un esempio che, secondo me, sar ebbe stato MOLTO più fr uttuoso se usato in un'applicazione
w indows 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 softwar 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.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.
Module Module1
'Classe che rappresenta il nuovo tipo di dato, ed'espone una funzione per scriverne il codiceClass TypeCreator
Private _Fields As Dictionary(Of String, String)Private _Name As String
'Fields è un dizionario che contiene come'chiavi i nomi delle proprietà da definire'nel nuovo tipo e come valori il loro tipiPublic ReadOnly Property Fields() As Dictionary(Of String, String)
GetReturn _Fields
End GetEnd Property
'Nome del nuovo tipoPublic Property Name() As String
GetReturn _Name
End GetSet(ByVal value As String)
_Name = valueEnd Set
End Property
Public Sub New()_Fields = New Dictionary(Of String, String)
End Sub
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)
NextCode.AppendLine("End Class")
Return Code.ToString()
End Function
End Class
'Classe statica contenente la funzione per scrivere'e generare il nuovo programmaClass ProgramCreator
'Accetta come input il nuovo tipo di dato'da gestire. Restituisce in output il percorso'dell'eseguibile creatoPublic 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)
NextCode.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 valoreFor Each Field As String In T.Fields.Keys
Code.AppendFormat("Console.WriteLine("" {0} = "" & Storage(I).{0}.ToString()){1}", Field, Environment.NewLine)
NextCode.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 VBCodeProviderDim CompResults As CompilerResults = _
Provider.CompileAssemblyFromSource(Parameters, Code.ToString())
If CompResults.Errors.Count = 0 ThenReturn CompResults.PathToAssembly
ElseFor Each E As CompilerError In CompResults.Errors
StopNextReturn Nothing
End IfEnd Function
End Class
Sub Main()
Dim NewType As New TypeCreator()Dim I As Int16Dim 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 = 1Do
Console.Write("Nome campo {0}: ", I)Field = Console.ReadLine
If String.IsNullOrEmpty(Field) Then
Exit DoEnd 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)
174.175.176.177.178.179.180.181.182.183.184.185.186.187.188.189.190.
Console.ReadKey()Else
NewType.Fields.Add(Field, FieldType)I += 1
End IfLoop
Dim Path As String = ProgramCreator.CreateManagingProgram(NewType)
If Not String.IsNullOrEmpty(Path) Then
Console.WriteLine("Programma di gestione per il tipo {0} creato!", NewType.Name)Console.WriteLine("Avviarlo ora? y/n")If Console.ReadKey().KeyChar = "y" Then
Process.Start(Path)End If
End IfEnd Sub
End Module
A48. Gli Attributi
Cosa sono e a cosa servonoGli 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 metadati. 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 fanno nulla, ma
dicono 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 assegna un attr ibuto a un membr o (non "si dichiar a", né "si inizializza", ma "si assegna" a
qualcosa) è questa:
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 amewor 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 non
CLS-Compliant, ossia che non r ispetta le specifiche del Fr amewor 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:
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
1. <Attributo(Parametri)> [Entità]
01.02.03.04.05.06.07.08.09.10.11.12.
13.14.15.16.17.18.19.20.
<CLSCompliant(False)> _Function CreateArray(Of T)(ByVal IndexFrom As Int32, ByVal IndexTo As Int32) As Array
'Per creare un array a estremi variabili è necessario'usare una funzione della classe Array, ed è altrettanto'necessario dichiarare l'array come di tipo Array, anche se'questo è solitamente sconsigliato. Per creare il'suddetto oggetto, bisogna passare alla funzione tre'parametri:' - il tipo degli oggetti che l'array contiene;' - un array contenente le lunghezze di ogni rango dell'array;' - un array contenente gli indici iniziali di ogni rango.Return Array.CreateInstance(GetType(T), New Int32() {IndexTo - IndexFrom}, New Int32()
{IndexTo})End Function Sub Main()
Dim CustomArray As Array = CreateArray(Of Int32)(3, 9)
CustomArray(3) = 1'...
End Sub
softwar 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:
Tuttavia otter r ete un War ning che r ipor ta la seguente dicitur a: 'Public Shared Function Res olve(hos tName As String)
As Sys tem.Net.IPHos tEntry' is obs olete: Res olve is obs oleted for this type, pleas e us e GetHos tEntry ins tead.
http://go.micros oft.com/fwlink/?linkid=14202 . Questo è un esempio di un metodo, esistente dalla ver sione 1.1 del
Fr amewor 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.
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.
Dichiarare nuovi attributiFor 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à:
1. Dim IP As Net.IPHostEntry = System.Net.Dns.Resolve("www.totem.altervista.org")
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 collectionConsole.WriteLine("Memory: {0}", GC.GetTotalMemory(False))
End Sub 'Crea un nuovo oggettoFunction 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
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.070.071.072.
'In questo codice, cronometreremo dei metodi, per'vedere quale è il più veloce!Module Module1
'Questo è un nuovo attributo completamente vuoto.'L'informazione che trasporta consiste nel fatto stesso'che esso sia applicato ad un membro.'Nel metodo di cronometraggio, rintracceremo e useremo'solo i metodi a cui sia stato assegnato questo attributo.Public Class TimeAttribute
Inherits Attribute
End Class
'I prossimi quattro metodi sono procedure di test. Ognuna'esegue una certa operazione 100mila o 10 milioni di volte. <Time()> _Sub AppendString()
Dim S As String = ""For I As Int32 = 1 To 100000
S &= "a"NextS = Nothing
End Sub
<Time()> _Sub AppendBuilder()
Dim S As New Text.StringBuilder()For I As Int32 = 1 To 100000
S.Append("a")NextS = Nothing
End Sub
<Time()> _Sub SumInt32()
Dim S As Int32For I As Int32 = 1 To 10000000
S += 1Next
End Sub
<Time()> _Sub SumDouble()
Dim S As DoubleFor I As Int32 = 1 To 10000000
S += 1.0Next
End Sub
'Questa procedura analizza il tipo T e ne estrae tutti'i metodi statici e senza parametri marcati con l'attributo'Time, quindi li esegue e li cronometra, poi riporta'i risultati a schermo per ognuno.'Vogliamo che i metodi siano statici e senza parametri'per evitare di raccogliere tutte le informazioni per la'funzione Invoke.Sub ReportTiming(ByVal T As Type)
Dim Methods() As MethodInfo = T.GetMethods()Dim TimeType As Type = GetType(TimeAttribute)Dim TimeMethods As New List(Of MethodInfo)
'La funzione GetCustomAttributes accetta due parametri'nel secondo overload: il primo è il tipo di'attributo da cercare, mentre il secondo specifica se'cercare tale attributo in tutto l'albero di'ereditarietà del membro. Restituisce come'risultato un array di oggetti contenenti gli attributi'del tipo voluto. Vedremo fra poco come utilizzare'questo array: per ora limitiamoci a vedere se non è
Ecco i r isultati del benchmar king (ter mine tecnico) sul mio por tatile:
Method: AppendString Time: 4765msMethod: AppendBuilder Time: 2msMethod: SumInt32 Time: 27msMethod: SumDouble Time: 34ms
Come potete osser var e, concatenar e le str inghe con & è enor memente meno efficiente r ispetto all'Append della classe
Str ingBuilder . Ecco per chè, quando si hanno molti dati testuali da elabor ar e, consiglio sempr e di usar e il secondo
metodo. Per quando r iguar da i numer i, le pr estazioni sono comunque buone, se non che i Double occupano 32 bit in più
e ci vuole più tempo anche per elabor ar li. In questo esempio avete visto che gli attr ibuti possono esser e usati solo
attr aver so la Reflection. Pr ima di pr oceder e, bisogna dir e che esiste uno speciale attr ibuto applicabile solo agli
attr ibuti che definisce quali entità possano esser e mar cate con dato attr ibuto. Esso si chiama Attr ibuteUsage. Ad
esempio, nel codice pr ecedente, Time è stato scr itto con l'intento di mar car e tutti i metodi che sar ebber o stati
sottoposti a benchmar king, ossia è "nato" per esser e applicato solo a metodi, e non a classi, enumer ator i, var iabili o
altr o. Tuttavia, per come l'abbiamo dichiar ato, un pr ogr ammator e può applicar lo a qualsiasi cosa. Per r estr inger e il
suo campo d'azione si dovr ebbe modificar e il sor gente come segue:
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.
'vuoto, ossia se il metodo è stato marcato con TimeFor Each M As MethodInfo In Methods
If M.GetCustomAttributes(TimeType, False).Length > 0 And _M.GetParameters().Count = 0 And _M.IsStatic ThenTimeMethods.Add(M)
End IfNext
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 XDReportTiming(This)
Console.ReadKey()
End SubEnd Module
1.2.
<AttributeUsage(AttributeTargets.Method)> _
Attr ibuteTar gets è un enumer ator e codificato a bit.
Ma veniamo or a agli attr ibuti con par ametr i:
3.4.5.
Public Class TimeAttributeInherits Attribute
End Class
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.
Module Module1
'UserInputAttribute specifica se una certa proprietà'debba essere valorizzata dall'utente e se sia'obbligatoria o meno. Il costruttore impone un solo'argomento, IsUserScope, che deve essere per forza'specificato, altrimenti non sarebbe neanche valsa la'pena di usare l'attributo, dato che questa è la'sua unica funzione.'Come specificato dalle convenzioni, la proprietà'impostata nel costruttore è ReadOnly, mentre'le altre (l'altra) è normale.<AttributeUsage(AttributeTargets.Property)> _Class UserInputAttribute
Inherits Attribute
Private _IsUserScope As BooleanPrivate _IsCompulsory As Boolean = False
Public ReadOnly Property IsUserScope() As Boolean
GetReturn _IsUserScope
End GetEnd Property
Public Property IsCompulsory() As Boolean
GetReturn _IsCompulsory
End GetSet(ByVal value As Boolean)
_IsCompulsory = valueEnd Set
End Property
Sub New(ByVal IsUserScope As Boolean)_IsUserScope = IsUserScope
End Sub
End Class
'CuboClass Cube
Private _SideLength As SinglePrivate _Density As SinglePrivate _Cost As Single
'Se i parametri del costruttore vanno specificati'tra parentesi quando si assegna l'attributo, allora'come si fa a impostare le altre proprietà'facoltative? Si usa un particolare operatore di'assegnamento ":=" e si impostano esplicitamente'i valori delle proprietà ad uno ad uno,'separati da virgole, ma sempre nelle parentesi.<UserInput(True, IsCompulsory:=True)> _Public Property SideLength() As Single
GetReturn _SideLength
End GetSet(ByVal value As Single)
_SideLength = valueEnd Set
End Property
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
GetReturn _Density
End GetSet(ByVal value As Single)
_Density = valueEnd Set
End Property
'Cost non verrà chiesto all'utente<UserInput(False)> _Public Property Cost() As Single
GetReturn _Cost
End GetSet(ByVal value As Single)
_Cost = valueEnd 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 ForEnd If
Dim Attributes As Object() = PI.GetCustomAttributes(GetType(UserInputAttribute),
True)
If Attributes.Count = 0 ThenContinue 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 cicloIf Not Attr.IsUserScope Then
Continue ForEnd 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
DoTry
Console.Write("* {0} = ", PI.Name)Value = Convert.ChangeType(Console.ReadLine, PI.PropertyType)
Catch Ex As ExceptionValue = NothingConsole.WriteLine(Ex.Message)
End TryLoop Until Value IsNot Nothing
Else'Altrimenti la richiede una sola voltaTry
Console.Write("{0} = ", PI.Name)Value = Convert.ChangeType(Console.ReadLine, PI.PropertyType)
Catch Ex As Exception
Vi lascio immaginar e cosa faccia il metodo Conver t.ChangeType...
136.137.138.139.140.141.142.143.144.145.146.147.148.149.150.151.152.153.154.155.156.157.158.159.
Value = NothingEnd Try
End IfIf Value IsNot Nothing Then
PI.SetValue(O, Value, Nothing)End If
Next
Return OEnd 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 faPrintInfo(O, "")
Console.ReadKey()
End SubEnd Module
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 war ning, er r or i, o se non segnalar le neppur e.
Option Explic itQuando 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.02.03.04.05.06.07.08.09.10.11.12.13.14.15.
Option Explicit Off Module Module1
Sub Main()'La variabile Stringa non viene dichiarata, ma è'lecito usarla e non viene comunicato alcun erroreStringa = "Ciao"
'Stessa cosa per la variabile I, che non è stata'dichiarata da nessuna parteFor I = 1 To 20
Console.WriteLine(Stringa)Next
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:
Anche se è possibile disattivar e Ex plicit, è fortemente s cons 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:
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 war 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 CompareQuesta 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 ens 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-ins ens itive). Eccone un esempio:
Scr ivendo "Binar y" al posto di "Tex t", otter r emo un messaggio differ ente a r untime!
Option StrictQuesta opzione si occupa di r egolar e le conver sioni implicite tr a tipi di dato differ enti. Quando è attiva, tutti i cast
16.17.
Console.ReadKey()End Sub
End Module
1. Option [Nome] On/Off
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.
Option Explicit Off Module Module1
Sub Main()Stringa = "Ciao"
For I = 1 To 20
If I > 10 ThenStrnga = I
End IfConsole.WriteLine(Stringa)
Next
Console.ReadKey()End Sub
End Module
01.02.03.04.05.06.07.08.09.10.11.
Option Compare TextModule Module1Sub Main()If "CIAO" = "ciao" ThenConsole.WriteLine("Option Compare Text")
ElseConsole.WriteLine("Option Compare Binary")
End IfConsole.ReadKey()
End SubEnd Module
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:
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 InferQuesta 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:
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.
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.
Option Strict OnModule Module1
Sub Main()Dim I As Int32Dim P As Student
'Conversione implicita da Double a Int32: viene'segnalata come erroreI = 4.0'Conversione implicita da Person a Student: viene'segnalata come erroreP = New Person("Mario", "Rossi", New Date(1968, 9, 12))
Console.ReadKey()
End SubEnd Module
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.
Option Infer Off Module Module1
Sub Main()'Infer è disattivata: I viene considerata di'tipo ObjectDim I = 2
'Dato che I è Object, può contenere'qualsiasi cosa, e quindi questo codice non genera'alcun erroreI = "ciao"
End Sub
End Module
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:
Ins ieme di is truzioni elementari univocamente interpretabili che, es eguite in un ordine s tabilito, permettono la
s oluzione di un problema in un numero 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 elementar 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.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.50.51.52.
Module Module1
Sub Main()Dim S1, S2 As StringDim C1, C2 As Dictionary(Of Char, Int32)Dim Result As Boolean = True
Console.Write("Stringa 1: ")S1 = Console.ReadLineConsole.Write("Stringa 2: ")S2 = Console.ReadLine
If S1.Length = S2.Length Then
C1 = New Dictionary(Of Char, Int32)For I As Int16 = 0 To S1.Length - 1
If C1.ContainsKey(S1.Chars(I)) ThenC1(S1.Chars(I)) += 1
ElseC1.Add(S1.Chars(I), 1)
End IfNext
C2 = New Dictionary(Of Char, Int32)For I As Int16 = 0 To S2.Length - 1
If C2.ContainsKey(S2.Chars(I)) ThenC2(S2.Chars(I)) += 1
ElseC2.Add(S2.Chars(I), 1)
End IfNext
If C1.Keys.Count = C2.Keys.Count Then
For Each C As Char In C1.KeysIf Not C2.ContainsKey(C) Then
Result = FalseElseIf C1(C) <> C2(C) Then
Result = FalseEnd IfIf Not Result Then
Exit ForEnd If
NextElse
Result = FalseEnd If
ElseResult = False
End If
If Result ThenConsole.WriteLine("Sono anagrammi!")
53.54.55.56.57.58.
ElseConsole.WriteLine("Non sono anagrammi!")
End If
Console.ReadKey()End Sub
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:
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:
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 nomeQuando si cr eano nuovi contr olli all'inter no della w indows 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 à btnNewFile, una lista
che debba contener e degli indir izzi di posta sar à lstEmail: quest'ultima notazione è detta "notazione ungher ese". A tal
1. X = Math.Sqrt((1 - (Y ^ 2 / B ^ 2)) * A ^ 2)
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)
pr oposito, vi voglio sugger ir e quest'ar ticolo e vi invito caldamente a legger lo.
VariabiliE 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 memoriaRispar 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 OccamLa 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 UInt64If X = 1 ThenReturn 1
ElseDim T As UInt64 = 1For I As Byte = 1 To XT *= I
NextReturn T
End IfEnd Function 'Diventa: Public Function Fattoriale(ByVal X as byte) As UInt64If X = 1 ThenReturn 1
ElseReturn X * Fattoriale(X - 1)
End IfEnd Function
01.02.03.04.
Sub Copia(ByVal Da As String, ByVal A As String)Dim R As <font class="keyword">New</font> IO.SreamReader(Da)Dim W As <font class="keyword">New</font> IO.StreamWriter(A)
IncapsulamentoL'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 denominazioneNei 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 nomenclatura 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.
Camel 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).
Notazione Ung herese : 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
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
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 Windows For ms, far ò una r apida
panor amica dell'IDE, più appr ofondita di quella pr oposta all'inizio.
Primo impattoFate click su File > New Pr oject, scegliete "Windows 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 ipaleIl 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 ExplorerLa 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 indows, My,
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.Windows.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 sonoOr 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 eventi. Volendo dar e una definizione teor ico-
concettuale di evento, potr emmo dir e che è un qualsiasi atto che modifica lo stato attuale di un oggetto. Ho di
pr oposito detto "oggetto", poiché le For ms non sono le uniche entità a posseder e eventi. Passando ad un ambito più
for male e r igor oso, infatti, un evento non è altr o che una speciale var iabile di tipo delegate (multicast). Essendo di tipo
delegate, tale var iabile può contener e r ifer imenti a uno o più metodi, i quali vengono comunemente chiamati g estor i
d'evento (o event'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 invocazione degli eventiLe 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:
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 amewor 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
1. Event [Nome] As [Tipo]
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 KeyDown (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.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.
Module Module1
'Questa classe rappresenta una collezione generica di'elementi che può essere ordinata con l'algoritmo'Bubble Sort già analizzatoPublic Class BubbleCollection(Of T As IComparable)
'Eredita tutti i membri pubblici e protected della classe'a tipizzazione forte List(Of T), il che consente di'disporre di tutti i metodi delle liste scrivendo'solo una linea di codiceInherits List(Of T)
'Questo campo indica il numero di millisecondi impiegati'ad ordinare tutta la collezionePrivate _TimeElapsed As Single = 0
Public ReadOnly Property TimeElapsed() As Single
GetReturn _TimeElapsed
End GetEnd Property
'Ecco gli eventi:'Il primo viene lanciato prima che inizi la procedura di'ordinamento, e per tale motivo è di tipo'CancelEventHandler. Questo delegate espone come'secondo parametro della signature una variabile "e"'al cui intero è disponibile una proprietà'Cancel che indica se cancellare oppure no l'operazione.'Se si volesse cancellare l'operazione sarebbe possibile'farlo nell'evento BeforeSorting.'In genere, si usa il tipo CancelEventHandler o un suo'derivato ogni volta che bisogna gestire un evento'che inizia un'operazione annullabile.Event BeforeSorting As System.ComponentModel.CancelEventHandler'Il secondo viene lanciato dopo aver terminato la procedura'di ordinamento e serve solo a notificare un'azione'avvenuta. Il tipo è un semplicissimo EventHandlerEvent AfterSorting As EventHandler
'Scambia l'elemento alla posizione Index con il suo'successivoPrivate Sub SwapInList(ByVal Index As Int32)
Dim Temp As T = Me(Index + 1)Me.RemoveAt(Index + 1)Me.Insert(Index, Temp)
End Sub
'In List(Of T) è già presente un metodo Sort,'perciò bisogna oscurarlo con Shadows (in quanto non'è sovrascrivibile con il polimorfismo)Public Shadows Sub Sort()
Dim Occurrences As Int32Dim J As Int32Dim Time As New Stopwatch'Attenzione! non bisogna confondere EventHandlers con'EventArgs: il primo è un tipo delegate e costituisce'il tipo dell'evento; il secondo è un normale tipo'reference e rappresenta tutti gli argomenti opzionali'inerenti alle operazioni svolteDim e As New System.ComponentModel.CancelEventArgs
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:
in questo caso, non basta. Per poter usar e gli eventi di un oggetto, è necessar io comunicar lo esplicitamente al
compilator e usando la keywor d WithEvents, da antepor r e (o sostituir e) a Dim:
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.
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.
'Viene generato l'evento. RaiseEvent si occupa di'richiamare tutti i gestori d'evento memorizzati'nell'evento BeforeSorting (che, ricordo, è un'delegate multicast). A tutti i gestori d'evento'vengono passati i parametri Me ed e. Al termine'di questa operazione, se un gestore d'evento ha'modificato una qualsiasi proprietà di e (e volendo,'anche di quest'oggetto), possiamo sfruttare tale'conoscenza per agire in modi diversi.RaiseEvent BeforeSorting(Me, e)'In questo caso, se e.Cancel = True si'cancella l'operazioneIf e.Cancel Then
Exit SubEnd If
Time.Start()J = 0Do
Occurrences = 0For I As Int32 = 0 To Me.Count - 1 - J
If I = Me.Count - 1 ThenContinue For
End IfIf Me(I).CompareTo(Me(I + 1)) = 1 Then
SwapInList(I)Occurrences += 1
End IfNextJ += 1
Loop Until Occurrences = 0Time.Stop()_TimeElapsed = Time.ElapsedMilliseconds
'Qui genera semplicemente l'eventoRaiseEvent AfterSorting(Me, EventArgs.Empty)
End Sub
End Class '...
End Module
1. Dim Bubble As New BubbleCollection(Of Int32)
1. WithEvents Bubble As New BubbleCollection(Of Int32)
1.2.3.
Sub [Nome Gestore](ByVal sender As Object, ByVal e As [Tipo]) Handles [Oggetto].[Evento]'...
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:
End Sub
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.
Module Module1
Public Class BubbleCollection(Of T As IComparable)Inherits List(Of T)
Private _TimeElapsed As Single = 0
Public ReadOnly Property TimeElapsed() As Single
GetReturn _TimeElapsed
End GetEnd Property
Event BeforeSorting As System.ComponentModel.CancelEventHandlerEvent AfterSorting As EventHandler
Private Sub SwapInList(ByVal Index As Int32)
Dim Temp As T = Me(Index + 1)Me.RemoveAt(Index + 1)Me.Insert(Index, Temp)
End Sub
Public Shadows Sub Sort()Dim Occurrences As Int32Dim J As Int32Dim Time As New StopwatchDim e As New System.ComponentModel.CancelEventArgs RaiseEvent BeforeSorting(Me, e)If e.Cancel Then
Exit SubEnd If
Time.Start()J = 0Do
Occurrences = 0For I As Int32 = 0 To Me.Count - 1 - J
If I = Me.Count - 1 ThenContinue For
End IfIf Me(I).CompareTo(Me(I + 1)) = 1 Then
SwapInList(I)Occurrences += 1
End IfNextJ += 1
Loop Until Occurrences = 0Time.Stop()_TimeElapsed = Time.ElapsedMilliseconds
'Qui genera semplicemente l'eventoRaiseEvent AfterSorting(Me, EventArgs.Empty)
End Sub
End Class
'Bubble è WithEvents poiché ne utilizzeremo'gli eventiWithEvents Bubble As New BubbleCollection(Of Int32)Sub Main()
Dim I As Int32
Console.WriteLine("Inserire degli interi (0 per terminare):")I = Console.ReadLineDo While I <> 0
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 è:
Il codice sopr a potr ebbe esser e stato modificato come segue:
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.
Bubble.Add(I)I = Console.ReadLine
Loop
'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 programmaBubble.Sort()
End Sub
Private Sub Bubble_BeforeSorting(ByVal sender As Object, ByVal e AsSystem.ComponentModel.CancelEventArgs) Handles Bubble.BeforeSortingIf Bubble.Count = 0 Then
e.Cancel = TrueConsole.WriteLine("Lista vuota!")Console.ReadKey()
End IfEnd Sub
Private Sub Bubble_AfterSorting(ByVal sender As Object, ByVal e As EventArgs) Handles
Bubble.AfterSortingConsole.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
End Module
1.2.3.4.
'Add Handler = Aggiungi Gestore; molto intuitiva come keywordAddHandler [Oggetto].[Evento], AddressOf [Gestore]'E per rimuovere il gestore dall'invocation list:RemoveHandler [Oggetto].[Evento], AddressOf [Gestore]
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.17.18.19.20.
Module Module1
'... WithEvents Bubble As New BubbleCollection(Of Int32)Sub Main()
Dim I As Int32
AddHandler Bubble.BeforeSorting, AddressOf Bubble_BeforeSortingAddHandler Bubble.AfterSorting, AddressOf Bubble_AfterSorting Console.WriteLine("Inserire degli interi (0 per terminare):")I = Console.ReadLineDo While I <> 0
Bubble.Add(I)I = Console.ReadLine
Loop
'Il corpo di Main termina con l'esecuzione di Sort, ma
Ovviamente se usate questo metodo, non potr ete usar e allo stesso tempo anche Handles, o aggiunger este due volte lo
stesso gestor e!
21.22.23.24.25.26.27.28.
29.30.31.32.33.34.35.36.37.38.39.40.41.42.
'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 programmaBubble.Sort()
End Sub
Private Sub Bubble_BeforeSorting(ByVal sender As Object, ByVal e AsSystem.ComponentModel.CancelEventArgs)If Bubble.Count = 0 Then
e.Cancel = TrueConsole.WriteLine("Lista vuota!")Console.ReadKey()
End IfEnd Sub
Private Sub Bubble_AfterSorting(ByVal sender As Object, ByVal e As EventArgs)
Console.WriteLine("Lista ordinata:")Bubble.ForEach(AddressOf Console.WriteLine)Console.WriteLine("Tempo impiegato: {0} ms", Bubble.TimeElapsed)Console.ReadKey()
End SubEnd Module
B3. I Controlli
La base delle applicazioni Windows FormSe 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.Windows.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 Windows 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 ControlLa 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:
AllowDr 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 FormFor 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:
AllowTr 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;
ShowDialog() : 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;
WindowState : 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 controllo ButtonPer 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 Windows 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 à:
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 :
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").
1.
2.3.
Private Sub btnHello_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) HandlesbtnHello.Click
End Sub
1.2.3.
4.
5.6.7.
Public Class Form1
Private Sub btnHello_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)Handles btnHello.ClickMessageBox.Show("Hello, World!", "Esempio", MessageBoxButtons.OK,
MessageBoxIcon.Information)End Sub
End Class
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.
LabelIl contr ollo Label ser ve per visualizzar e un qualsiasi messaggio o testo sulla super ficie della w indows 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ì:
TextBoxCostituisce 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 Lower (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;
Passwor 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 passwor 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 calcoloUltima 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:
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.
01.02.03.
04.05.06.07.
08.09.10.
Public Class Form1
Private Sub btnArea_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)Handles btnArea.ClickDim Radius As Single = txtRadius.TextDim Area As SingleArea = Radius ^ 2 * Math.PIMessageBox.Show("L'area del cerchio è " & Area & ".", Me.Text, MessageBoxButtons.OK,
MessageBoxIcon.Information)End Sub
End Class
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.
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.02.03.
04.05.06.07.08.
09.10.
11.12.13.14.
Public Class Form1
Private Sub btnArea_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)Handles btnArea.ClickTry
Dim Radius As Single = txtRadius.TextDim Area As SingleArea = Radius ^ 2 * Math.PIMessageBox.Show("L'area del cerchio è " & Area & ".", Me.Text,
MessageBoxButtons.OK, MessageBoxIcon.Information)Catch ICE As InvalidCastException
MessageBox.Show("Inserire un valore numerico valido!", Me.Text,MessageBoxButtons.OK, MessageBoxIcon.Error)
End TryEnd Sub
End Class
01.02.03.
04.05.06.07.08.09.10.11.12.13.
14.15.
16.17.
18.19.20.21.
Public Class Form1
Private Sub btnArea_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)Handles btnArea.ClickTry
Dim Radius As Single = txtRadius.Text
If Radius <= 0 ThenThrow New ArgumentException()
End If
Dim Area As SingleArea = Radius ^ 2 * Math.PIMessageBox.Show("L'area del cerchio è " & Area & ".", Me.Text,
MessageBoxButtons.OK, MessageBoxIcon.Information)Catch ICE As InvalidCastException
MessageBox.Show("Inserire un valore numerico valido!", Me.Text,MessageBoxButtons.OK, MessageBoxIcon.Error)
Catch AE As ArgumentExceptionMessageBox.Show("Il raggio non può essere negativo o nullo!", Me.Text,
MessageBoxButtons.OK, MessageBoxIcon.Exclamation)End Try
End Sub End Class
B5. Input e Output su file
Gli StreamLe 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
Networ 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 .
FileStreamQuesta 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 aggiungere 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 amewor 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.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.
Module Module1
Sub Main()Dim File As IO.FileStreamDim FileName As String
Console.WriteLine("Inserire il percorso di un file:")FileName = Console.ReadLine
'IO.File.Exists(path) restituisce True se il percorso'path indica un file esistente e False in caso contrarioIf Not IO.File.Exists(FileName) Then
Console.WriteLine("Questo file non esiste!")Console.ReadKey()Exit Sub
End If
Console.Clear()
'Apre il file specificato, posizionandosi all'inizioFile = New IO.FileStream(FileName, IO.FileMode.Open)
Dim Buffer() As ByteDim Number, ReadBytes As Int32
'Chiede all'utente quanti bytes vuole leggere, e'memorizza tale numero in NumberConsole.WriteLine("Quanti bytes leggere?")Number = CType(Console.ReadLine, Int32)'Se Number è un numero positivo e non siamo ancora'arrivati alla fine del file, allora legge quei bytes.'La proprietà Position restituisce la posizione'corrente all'interno del file (a iniziare da 0), mentre'File.Length restituisce la lunghezza del file, in bytes.Do While (Number > 0) And (File.Position < File.Length - 1)
'Ridimensiona il bufferReDim Buffer(Number - 1)'Legge Number bytes e li mette in Buffer, a partire'dall'inizio dell'array. Read è una funzione, e'restituisce come risultato il numero di bytes'effettivamente letti dallo stream.ReadBytes = File.Read(Buffer, 0, Number)
Console.WriteLine("Bytes letti:")For I As Int32 = 0 To ReadBytes - 1
Console.Write("{0:000} ", Buffer(I))
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
Networ 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:
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/scrittura di file testualiI 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
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.
NextConsole.WriteLine()
'Se abbiamo letto tanti bytes quanti ne erano stati'chiesti, allora non siamo ancora arrivati alla'fine del file. Richiede all'utente un numeroIf ReadBytes = Number Then
Console.WriteLine("Quanti bytes leggere?")Number = CType(Console.ReadLine, Int32)
End IfLoop
'Controlla se si è raggiunta la fine del file.'Infatti, il ciclo potrebbe terminare anche se l'utente'immettesse 0.If File.Position >= File.Length - 1 Then
Console.WriteLine("Raggiunta fine del file!")End If
'Chiude il fileFile.Close()
Console.ReadKey()
End Sub End Module
1.2.3.4.5.6.
'Si sposta alla posizione 100File.Seek(100, IO.SeekOrigin.Begin)'Si sposta di 250 bytes indietro rispetto alla posizione correnteFile.Seek(-250, IO.SeekOrigin.Current)'Si sposta a 100 bytes dalla fine del fileFile.Seek(-100, IO.SeekOrigin.End)
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.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.50.51.52.53.54.55.56.57.58.59.60.61.62.63.
Module Module1Sub Main()
Dim File As StringDim Mode As Char
Console.WriteLine("Premere R per leggere un file, W per scriverne uno.")'Console.ReadKey restituisce un oggetto ConsoleKeyInfo,'al cui interno ci sono tre proprietà: Key,'enumeratore che definisce il codice del pulsante premuto;'KeyChar, il carattere corrispondente a quel pulsante;'Modifier, enumeratore che definisce i modificatori attivi,'ossia Ctrl, Shift e Alt.'Quello che serve ora è solo KeyCharMode = Console.ReadKey.KeyChar'Dato che potrebbe essere attivo il Bloc Num, ci si'assicura che Mode contenga un carattere maiuscolo'con la funzione statica ToUpper del tipo base CharMode = Char.ToUpper(Mode)'Pulisce lo schermoConsole.Clear()
Select Case Mode
Case "R"Console.WriteLine("Inserire il percorso del file da leggere:")File = Console.ReadLine
'Cosntrolla che il file esistaIf Not IO.File.Exists(File) Then
'Se non esiste, visualizza un messggio ed esceConsole.WriteLine("Il file specificato non esiste!")Console.ReadKey()Exit Sub
End If
Dim Reader As New IO.StreamReader(File)
'Legge ogni singola riga del file, fintanto che non'si è raggiunta la fineDo While Not Reader.EndOfStream
'Come Console.Readline, la funzione d'istanza'ReadLine restituisce una linea di testo'dal fileConsole.WriteLine(Reader.ReadLine)
Loop
'Quindi chiude il fileReader.Close()
Case "W"Console.WriteLine("Inserire il percorso del file da creare:")File = Console.ReadLine
Dim Writer As New IO.StreamWriter(File)Dim Line As String
Console.WriteLine("Immettere il testo del file, " & _
"premere due volte invio per terminare")'Fa immettere righe di testo fino a quando'si terminaDo
Line = Console.ReadLine'Come Console.WriteLine, la funzione d'istanza'WriteLine scrive una linea di testo sul file
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/scrittura di file binariCome 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:
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
64.65.66.67.68.69.70.71.72.73.74.
Writer.WriteLine(Line)Loop While Line <> ""
'Chiude il fileWriter.Close()
Case ElseConsole.WriteLine("Comando non valido!")
End Select
Console.ReadKey()End Sub
End Module
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.17.18.19.20.21.22.
Module Module1
Sub Main()'Apre il file "prova.dat", creandolo o sovrascrivendoloDim File As New IO.FileStream("prova.dat", IO.FileMode.Create)'Writer è lo strumento che ci permette di scrivere'sullo stream File con codifica binariaDim Writer As New IO.BinaryWriter(File)Dim Number As Int32
Console.WriteLine("Inserisci 10 numeri da scrivere sul file:")For I As Int32 = 1 To 10
Console.Write("{0}: ", I)Number = CType(Console.ReadLine, Int32)Writer.Write(Number)
NextWriter.Close()
Console.ReadKey()
End Sub End Module
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 0014 00 00 00 40 lf 00 00 39 4a 00 00 a7 fe ff ff5a 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, wav (vedi sezione FFS), zip, mpg, ecceter a...
Esempio: steganografia su immaginiLa 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.02.03.
04.05.
06.07.08.09.10.
11.
Public Class Form1
Private Sub btnHide_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)Handles btnHide.ClickIf Not IO.File.Exists(txtPath.Text) Then
MessageBox.Show("File inesistente!", Me.Text, MessageBoxButtons.OK,MessageBoxIcon.Error)
Exit SubEnd If
If IO.Path.GetExtension(txtPath.Text) <> ".jpg" Then
MessageBox.Show("Il file deve essere in formato JPEG!", Me.Text,MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
Il testo accodato può esser e r ilevato facilmente con un Hex Editor , per questo lo si dovr ebbe cr iptar e con una
passwor d: per ulter ior i infor mazioni sulla cr iptazione in .NET, veder e capitolo r ekativo.
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.
Exit SubEnd 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 UTF8Dim TextBytes() As Byte = _
System.Text.Encoding.UTF8.GetBytes(txtText.Text)
'Va alla fine del fileFile.Seek(0, IO.SeekOrigin.End)'Scrive i bytesFile.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.ClickIf Not IO.File.Exists(txtPath.Text) Then
MessageBox.Show("File inesistente!", Me.Text, MessageBoxButtons.OK,MessageBoxIcon.Error)
Exit SubEnd 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 SubEnd If
Dim File As New IO.FileStream(txtPath.Text, IO.FileMode.Open)Dim TextBytes() As ByteDim B1, B2 As Byte
'Legge un byteB1 = File.ReadByte()Do
'Legge un altro byteB2 = File.ReadByte()'Se i bytes formano la sequenza FF D9, si ferma.'In Visual Basic, in numeri esadecimali si scrivono'facendoli precedere da "&H"If B1 = &HFF And B2 = &HD9 Then
Exit DoEnd If'Passa il valore di B2 in B1B1 = B2
Loop While (File.Position < File.Length - 1)
ReDim TextBytes(File.Length - File.Position - 1)'Legge ciò che rimane dopo FF D9File.Read(TextBytes, 0, TextBytes.Length)File.Close()
txtText.Text = System.Text.Encoding.UTF8.GetString(TextBytes)
End SubEnd Class
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 indows for m, il lor o aspetto sar à simile a questo:
ListBox
ComboBox
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:
ListBox MultiColumn
Scr ollAlwaysVisible : 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 opDownHeight : deter mina l'altezza, in pix el, del menù a discesa
Dr opDownStyle : 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 opDown (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 opDownList (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 opDownStyle =
Simple:
ComboBox Simple DropDown
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 opDownItems : 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 opDownStyle non è impostata su
Dr opDownList, poichè tale stile impedisce di modificar e il contenuto della combobox tr amite tastier a,
come già detto
Per entr ambe le liste:
Dr awMode : deter mina la modalità con cui ogni elemento viene disegnato. Può assumer e tr e valor i:
Nor mal, Owner Dr aw Fix ed e Owner 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 Font 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 con Integ ralHeig 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 indows 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.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.17.18.19.20.21.22.23.
Public Class Form1Private Sub cmdAdd_Click(ByVal sender As Object, _
ByVal e As EventArgs) Handles cmdAdd.ClickDim S As String
'Inputbox(' ByVal Prompt As Object,' ByVal Title As String,' ByVal DefaultResponse As String)'Visualizza una finestra con una label esplicativa'il cui testo è racchiuso in Prompt, con un titolo'Title e una textbox con un testo di default'DeafultResponse: una volta che l'utente ha inserito'la stringa nella textbox e cliccato OK, la funzione'restituisce la stringa immessaS = InputBox("Inserisci una stringa:", "Inserimento stringa", _"[Stringa]")
'Aggiunge la stringa alla listalstItems.Items.Add(S)
End Sub
Non solo stringheNell'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):
Una volta pr emuto btnDoSomething, nella lista ver r anno aggiunti due oggetti, tuttavia la GUI della listbox
visualizzer à questi due elementi:
WindowsApplication4.Form1+ItemWindowsApplication4.Form1+Item
Questo nel mio caso, poiché il pr ogetto (e quindi il namespace pr incipale) si chiama WindowsApplication4. 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
24.25.26.27.28.29.30.31.
Private Sub cmdRemove_Click(ByVal sender As Object, _ByVal e As EventArgs) Handles cmdRemove.Click'Se è selezionato un elemento...If lstItems.SelectedIndex >= 0 Then
'Lo eliminalstItems.Items.RemoveAt(lstItems.SelectedIndex)
End IfEnd Sub
End Class
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.
Class Form1
Class ItemPrivate Shared IDCounter As Int32 = 0
Private _ID As Int32Private _Description As String
Public ReadOnly Property ID() As Int32
GetReturn _ID
End GetEnd Property
Public Property Description() As String
GetReturn _Description
End GetSet(ByVal value As String)
_Description = valueEnd Set
End Property
Sub New()_ID = IDCounterIDCounter += 1
End Sub
End Class
Private Sub btnDoSomething_Click(ByVal sender As Object, ByVal e As EventArgs)Handles btnDoSomething.ClicklstItems.Items.Add(New Item() With {.Description = "Asus Eee PC 900"})lstItems.Items.Add(New Item() With {.Description = "Hp Pavillion Dv6000"})
End Sub
End Class
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
WindowsApplication4.For m+Item. Per modificar e il compor tamento del contr ollo, dobbiamo aggiunger e alla
classe un metodo ToStr ing, ad esempio così:
Avviando di nuovo l'applicazione, gli elementi visualizzati sulla lista sar anno:
Asus Eee PC 900Hp 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:
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.
1.2.3.4.5.6.7.
Class Item'...
Public Overrides Function ToString() As String
Return Me.DescriptionEnd Function
End Class
1.2.3.
lstItems.DisplayMember = "Description"lstItems.Items.Add(New Item() With {.Description = "Asus Eee PC 900"})lstItems.Items.Add(New Item() With {.Description = "Hp Pavillion Dv6000"})
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.
CheckBoxLa 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.
RadioButtonA 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.
GroupBoxPar 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
NumericUpDownQuesto 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 icUpDown. 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 owKey : 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
UpDownAlign : 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 icUpDown 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.02.03.04.05.06.07.08.09.10.11.12.13.
Class Form1'Il numero da indovinarePrivate Number As Byte'Determina se l'utente ha già dato la sua rispostaPrivate Tried As Boolean'Crea un nuovo oggetto Random in grado di generare numeri'casualiDim Rnd As New Random()
Private Sub cmdGenerate_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles cmdGenerate.Click'Genera un numero aleatorio tra 0 e 99 e lo deposita in
DomainUpDownQuesto 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.
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.
'NumberNumber = Rnd.Next(0, 100)'Imposta Tried su FalseTried = False'Nasconde la soluzionelblNumber.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 proceduraIf 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 vinceIf nudValue.Value = Number Then
MessageBox.Show("Complimenti, hai vinto!", "Numeri a caso", _MessageBoxButtons.OK, MessageBoxIcon.Information)
ElseMessageBox.Show("Risposta sbagliata!", "Numeri a caso", _MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
End If
'Ormai l'utente ha fatto la sua sceltaTried = True'Fa vedere la soluzionelblNumber.Text = Number
End SubEnd Class
B9. PictureBox e ProgressBar
PictureBoxLa 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 wmf. 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
RawFor 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
ProgressBarLa 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: Bianco e neroL'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.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.
Class Form1'Funzione che converte un colore in scala di grigioPrivate Function ToGreyScale(ByVal C As Color) As Color
'Per convertire un colore in scala di grigio è sufficiente'prendere le sue componenti di rosso, verde e blu (red,'green e blue), farne la media aritmetica e quindi'assegnare tale valore alle nuove coordinate RGB del'colore risultante
'Ottiene le componenti (coordinate RGB)Dim Red As Int32 = C.RDim Green As Int32 = C.GDim Blue As Int32 = C.B'Fa la mediaDim Grey As Int32 = (Red + Green + Blue) / 3
'Quindi crea un nuovo colore, mettendo tutte le'componenti uguali alla media ottenutaReturn Color.FromArgb(Grey, Grey, Grey)
End Function
Private Sub cmdLoad_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles cmdLoad.Click'Per ulteriori informazioni sui controlli OpenFileDialog e
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.
'SaveFileDialog vedere capitolo relativoDim Open As New OpenFileDialogOpen.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 FromFileimgPreview.Image = Image.FromFile(Open.FileName)
End IfEnd 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 salvaIf imgPreview.Image IsNot Nothing Then
Dim Save As New SaveFileDialogSave.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'immagineimgPreview.Image.Save(Save.FileName)
End IfEnd 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 astrattaDim Image As Bitmap = imgPreview.Image'Variabile ausiliaria per i calcoliDim 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 voltaFor X As Int32 = 0 To Image.Width - 1
'Quindi ogni pixel nella rigaFor Y As Int32 = 0 To Image.Height - 1
'Converte il coloreTempColor = 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 analizzateprgConvert.Value = X * 100 / Image.Width'Evita di bloccare il programma. Per ulteriori'informazioni su Application e il namespace My,'vedere capitolo relativoApplication.DoEvents()
Next
'Reimposta l'immagine finaleimgPreview.Image = Image
End SubEnd 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 è:
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;*.wav;*.wma;*.ogg;*.mid|Video|*.mpg;*.mp4;*.wmv;*.avi". Per i file di testo "File di
testo|*.tx t" e per tutti i file "Tutti i file|*.*"
InitialDir ector y: la car tella iniziale pr edefinita
MultiSelect: se ver o, si potr anno selezionar e più file (cr eando un r iquadr o col puntator e o selezionandoli
manualmente uno ad uno tenendo pr emuto Ctr l)
Title: il titolo della finestr a di dialogo
ValidatesName: contr olla che i nomi dei file non contengano car atter i vietati
Over Wr itePr ompt: (solo per SaveFileDialog) contr olla se il file selezionato ne sovr ascr ive un altr o e chiede se
pr oceder e o no
Esempio: Editor di testiDopo 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 indows 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:
1. Descrizione file|*.estensione1;*.estensione2|Descrizione file|...
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"
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.
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, procediamoIf 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 dialogoDim R As New IO.StreamReader(FOpen.FileName)
'Legge tutto il testo del file e lo deposita nella textboxtxtFile.Text = R.ReadToEnd
'Chiude il fileR.Close()
End IfEnd Sub Private Sub cmdSve_Click(ByVal sender As Object, ByVal e As EventArgs) _
Handles cmdSave.Click'Viene visualizzata la finestra di dialogoIf 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à OverwritePromptDim W As New IO.StreamWriter(FSave.FileName)
'Scrive tutto il contenuto della textbox nel fileW.Write(txtFile.Text)
'Chiude il fileW.Close()
End IfEnd Sub Private Sub cmdClose_Click(ByVal sender As Object, ByVal e As EventArgs) _
Handles cmdClose.ClickIf txtFile.Text <> "" And _
FSave.ShowDialog = Windows.Forms.DialogResult.OK ThenDim W As New IO.StreamWriter(FSave.FileName)
W.Write(txtFile.Text)
W.Close()
End IfEnd Sub
B11. Scrivere un INI Reader - Parte I
I file INIDato 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: campi e valor 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 = ProofGameVersion = 1.1.0.2Company = FG CorporationYear = 2006
[Run Info]Diffucult = easy ;difficoltàLives = 10 ;numero di viteHealth = 90 ;saluteLevel = 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 indows usa per r appr esentar e la str uttur a ger ar chica delle car telle.
MenuStripÈ il classico menù di w indows. 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 opDownItems del menù. Ecco alcune
pr opr ietà inter essanti:
MenuStr 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?
ToolStr ipMenuItem
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 opDownItems : 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
ShowShor 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ì:
StatusStripLa 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 opdownitem, 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".
TreeViewEcco 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
FullRowSelect: 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
ShowLindes: indica se visualizzar e le linee che congiungono i nodi
ShowPlusMinus: indica se visualizzar e i '+' per espander e i nodi contenuti in un elemento e i '-' per eseguir e
l'oper azione opposta
ShowRootLindes: 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 nodo 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, radici. 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.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.50.51.52.53.54.55.56.57.58.59.60.61.
Class Form1Private Sub ReadFile(ByVal File As String)
'Lo stream da cui leggere il fileDim Reader As New IO.StreamReader(File)'Una stringa che rappresenta ogni singola riga del fileDim Line As String'L'indice associato al numero di campi letti. Dato che ogni'campo costituirà una radice del grafico, bisogna sapere da'dove far derivare i relativi valori.'Questa variabile è opzionale, in quanto è possibile usare'la proprietà trwIni.Nodes.Count-1, poichè si aggiungono'valori sempre soltanto all'ultimo campo apertoDim FieldCount As Int16 = -1
'Imposta il testo della label di statolblStatus.Text = "Apertura del file in corso..."
'Finchè non si raggiunge la fine del file si continua'a leggereWhile Not Reader.EndOfStream
'Leggiamo una linea di file (S)Line = Reader.ReadLine'Se la linea è diversa da una riga vuotaIf Line <> Nothing Then
'Se la linea inizia per "[" (significa che è'un campo)If Line.StartsWith("[") Then
'Si aumenta FieldCount, che indica quanti campi 'si sono già letti (in base 0)FieldCount += 1'Rimuove il primo carattere, ossia "["Line = Line.Remove(0, 1)'Rimuove dalla linea l'ultimo carattere,'ossia "]"Line = Line.Remove(Line.Length - 1, 1)'Aggiunge una radice alla TreeViewtrwIni.Nodes.Add(Line)
Else'Altrimenti, se la linea non inzia per ";",'ossia non è un commentoIf Not Line.StartsWith(";") Then
'Aggiunge la linea come sotto-nodo'dell'ultimo campo inserito. La linea'conterrà il valore in forma' [nome]=[contenuto]'Attenzione! Possono esserci commenti in'riga, quindi si deve prima controllare'di eliminarli'Se l'indice del carattere ";" nella riga'è positivo...If Line.IndexOf(";") > 0 Then
'Rimuove tutto quello che viene dopo'il commentoLine = Line.Remove(Line.IndexOf(";"))
End IftrwIni.Nodes(FieldCount).Nodes.Add(Line)
End IfEnd If
End IfEnd While
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:
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.
'Chiude il fileReader.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 codiceDim FOpen As New OpenFileDialogFOpen.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 correnteMe.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 procedereIf 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 proceduraExit Sub
End If
'Elimina tutti i noditrwIni.Nodes.Clear()
End SubEnd Class
B13. DateTimePicker - Lavorare con le date
Il tipo di dato standar d che il .NET Fr amewor 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 lifeEcco 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
MM : mese, sempr e con due cifr e
MMM : nome del mese, abbr eviato a tr e car atter i secondo la cultur a cor r ente
MMMM : 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
mm : 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.02.03.04.05.06.07.08.09.10.11.12.13.14.
Class Form1Private Sub tmrRefresh_Tick(ByVal sender As Object, _
ByVal e As EventArgs) Handles tmrRefresh.Tick'Ottiene la differenza tra le due dateDim Age As TimeSpan = (Date.Now - dtpBirthDay.Value)'La trasforma in secondiDim Seconds As Double = Age.TotalSeconds'Variabile temporanea che serve alla costruzioneDim AgeStr As New System.Text.StringBuilder
With AgeStr
.AppendLine("Hai vissuto")
Per il mio caso, il r isultato è questo:
15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.
'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 labellblAge.Text = .ToString
End WithEnd Sub
End Class
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 Networ 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:
Imag eList su ListView
Imag eList su TreeView
Imag eList su TabControl
Reperire le iconeIndubbiamente 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 eewar e o al limite sotto licenza lgpl (il
che le r ende disponibili per l'uso da par te di softwar 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 Windows 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 web. 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 web: 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 indows
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:
Larg e Icon
Small Icon
Details
ListViewCome 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
FullRowSelect : 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
Owner 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
ShowGr 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.
ListViewItemOgni 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 ListViewSubItem: di questi è possibile modificar e il font e il color e,
oltr e che il testo.
ListViewGroupUn 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à ShowGr oups della ListView sia
impostata a Tr ue, altr imenti... beh, niente festa.
Ecco un esempio di codice:
ListView con View = DetailsLa 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.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 listviewListView1.Groups.Add(G)'Setta il gruppo a cui L apparterràL.Group = G'Aggiunge l'elemento alla listaListView1.Items.Add(L)
01.02.03.04.05.06.07.08.
Class Form1Private Sub cmdAdd_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles cmdAdd.Click'Nuovo elementoDim Item As ListViewItem'Array dei valori che andranno a rappresentare i campi di'ogni singola colonna
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:
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.
Dim Values() As String = _{txtProduct.Text, nudPrice.Value, nudQuantity.Value}
'Inizializza Item sulla base dei valori datiItem = New ListViewItem(Values)'E lo aggiunge alla listalstProducts.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 selezionatiFor Each SelItem As ListViewItem In lstProducts.SelectedItems
'E li rimuove dalla listalstProducts.Items.Remove(SelItem)
NextEnd Sub
Private Sub cmdCalculate_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles cmdCalculate.Click'Totale spesaDim Total As Single = 0'Prezzo unitarioDim Price As Single'Quantit?Dim Quantity As Int32
For Each Item As ListViewItem In lstProducts.Items
'Ottiene i valori da ogni elementoPrice = 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 SubEnd Class
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.17.18.19.20.21.22.23.
Class Form1'Queste classi saranno i comparer usati per ordinare'le righe della ListView, al pari di come si è imparato nelle'lezioni sulle interfaccePrivate Class ProductComparer
Implements IComparer(Of ListViewItem)
Public Function Compare(ByVal x As ListViewItem, _ByVal y As ListViewItem) As Integer _Implements IComparer(Of ListViewItem).Compare'Gli oggetti da comparare sono ListViewItem, mentre la proprietà'che bisogna confrontare è il nome del prodotto, ossia'il primo sotto-elementoDim Name1 As String = x.SubItems(0).TextDim Name2 As String = y.SubItems(0).TextReturn Name1.CompareTo(Name2)
End FunctionEnd Class
Private Class NumberComparer
Implements IComparer(Of ListViewItem)
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.
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 ThenIndex = 1
ElseIndex = 2
End IfEnd 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 costruttoreDim Val1 As Single = x.SubItems(Index).TextDim Val2 As Single = y.SubItems(Index).TextReturn Val1.CompareTo(Val2)
End FunctionEnd 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 elementiPrivate 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 teoricaPrivate Sub SortListViewItems(ByVal List As ListView, _
ByVal Comparer As IComparer(Of ListViewItem))Dim Occurrences As Int32 = 0
Do
Occurrences = 0For I As Int32 = 0 To List.Items.Count - 1
If I = List.Items.Count - 1 ThenContinue For
End IfIf Comparer.Compare(List.Items(I), List.Items(I + 1)) = 1 Then
SwapInList(List, I)Occurrences += 1
End IfNext
Loop Until Occurrences = 0End Sub Private Sub lstProducts_ColumnClick(ByVal sender As System.Object, _
ByVal e As ColumnClickEventArgs) Handles lstProducts.ColumnClickSelect Case e.Column
Case 0'NomeMe.SortListViewItems(lstProducts, New ProductComparer())
Case 1'PrezzoMe.SortListViewItems(lstProducts, New NumberComparer(True))
Case 2'QuantitàMe.SortListViewItems(lstProducts, New NumberComparer(False))
End SelectEnd Sub
End Class
B16. ToolStrip e TabControl
ToolStripTutti 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 opDownItems (menù a discesa, identico a MenuStr ip: nell'immagine Documenti è un dr opdownitem);
SplitButton (una fusione tr a button e dr opdownitems, 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).
TabControlTabContr 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 RowCount 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
NotifyIconLa par te infer ior e destr a della bar r a delle applicazioni di Windows è 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 war 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)
ShowBaloonTip(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:
Per r ipor tar e tutto allo stato pr ecedente è sufficiente inver tir e i valor i booleani.
Fumetto
SplitContainerAnche lo SplitContainer è un contenitor e, e può r ivelar si davver o molto utile. La sua peculiar ità consiste nel poter
r idimensionar e con il mouse, spostando quello che viene chiamato splitter , le due par ti del contr ollo. Ogni par te è una
super ficie contenitor e a sè stante e viene r appr esentata da un oggetto Panel. Ecco le pr opr ietà più significative:
Bor der Style : pr opr ietà enumer ata che descr ive lo stile dei bor di: assenti (None), a linea singola (Single) o 3D
(Fix ed3D)
Fix edPanel : specifica quale dei due pannelli debba r estar e di dimensioni fisse dur ante l'atto di
r idimensionamento
IsSplitter Fix ed : deter mina se lo splitter è fisso o può muover si
1.2.3.4.5.6.
'Nasconde il form dalla barra delle applicazioniMe.ShowInTaskBar = False'Rende il form invisibileMe.Visible = False'Se l'icona non è già visibile, la rende visibileMe.nftIcon.Visible = True
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 wor d, che mantiene, tuttavia, un
layout w indows 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.
Syntax HighlightingQuesta 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
keywor 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.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.
Public Class SyntaxRTBInherits System.Windows.Forms.RichTextBox
'La funzione SendMessage serve per inviare dati messaggi'a una finestra o un dispositivo allo scopo di ottenere'dati valori od eseguire dati compitiPrivate Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
(ByVal hWnd As IntPtr, ByVal wMsg As Integer, _ByVal wParam As Integer, ByVal lParam As Integer) As Integer
'Blocca il Refresh della finestraPrivate Declare Function LockWindowUpdate Lib "user32" _
(ByVal hWnd As Integer) As Integer
'Campo privato che specifica se il meccanismo di syntax'highlighting è case sensitive oppure noPrivate _SyntaxHighlight_CaseSensitive As Boolean = False'La tabella delle parolePrivate Words As New DataTable
Public Property CaseSensitive() As Boolean
GetReturn _SyntaxHighlight_CaseSensitive
End GetSet(ByVal Value As Boolean)
_SyntaxHighlight_CaseSensitive = Value
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 SetEnd Property
'Contiene costanti usate nell'inviare messaggi all'API'di windowsPrivate Enum EditMessages
LineIndex = 187LineFromChar = 201GetFirstVisibleLine = 206CharFromPos = 215PosFromChar = 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ù generatoProtected Overrides Sub OnTextChanged(ByVal e As EventArgs)
ColorVisibleLines()End Sub
'Colora tutta la RichTextBoxPublic Sub ColorRtb()
Dim FirstVisibleChar As IntegerDim i As Integer = 0
While i < Me.Lines.Length
FirstVisibleChar = GetCharFromLineIndex(i)ColorLineNumber(i, FirstVisibleChar)i += 1
End WhileEnd Sub
'Colora solo le linee visibiliPublic 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'è testoExit Sub
ElseWhile FirstLine < LastLine
FirstVisibleChar = GetCharFromLineIndex(FirstLine)ColorLineNumber(FirstLine, FirstVisibleChar)FirstLine += 1
End WhileEnd If
End Sub
'Colora una linea all'indice LineIndex, a partire dal carattere'lStartPublic Sub ColorLineNumber(ByVal LineIndex As Integer, _
ByVal lStart As Integer)Dim i As Integer = 0Dim SelectionAt As Integer = Me.SelectionStartDim MyRow As DataRowDim Line() As String, MyI As Integer, MyStr As String
'Blocca il refreshLockWindowUpdate(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 parolaMe.SelectionStart = MyIMe.SelectionLength = MyStr.Length
'Se la parola è contenuta in una delle righeIf Words.Rows.Contains(MyStr) Then
'Seleziona la rigaMyRow = Words.Rows.Find(MyStr)'Quindi colora la parola prelevando il colore da'tale rigaIf (Not CaseSensitive) Or _
(CaseSensitive And MyRow("Word") = MyStr) ThenMe.SelectionColor = Color.FromName(MyRow("Color"))
End IfElse
'Altrimenti lascia il testo in neroMe.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 selezioneMe.SelectionStart = SelectionAtMe.SelectionLength = 0'E il coloreMe.SelectionColor = Color.Black
'Riprende il refreshLockWindowUpdate(0)
End Sub
'Ottiene il primo carattere della linea LineIndexPublic Function GetCharFromLineIndex(ByVal LineIndex As Integer) _
As IntegerReturn SendMessage(Me.Handle, EditMessages.LineIndex, LineIndex, 0)
End Function
'Ottiene la prima linea visibilePublic Function FirstVisibleLine() As Integer
Return SendMessage(Me.Handle, EditMessages.GetFirstVisibleLine, 0, 0)End Function
'Ottiene l'ultima linea visibilePublic 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.LengthEnd If
Return LastLine
End Function
Public Sub New()Dim MyRow As DataRowDim arrKeyWords() As String, strKW As String
Me.AcceptsTab = True
'Carica la colonna Word e ColorWords.Columns.Add("Word")Words.PrimaryKey = New DataColumn() {Words.Columns(0)}Words.Columns.Add("Color")
'Aggiunge le keywords del linguaggio SQL all'arrayarrKeyWords = New String() {"select", "insert", "delete", _
"truncate", "from", "where", "into", "inner", "update", _
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
keywor 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 keywor 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:
172.173.174.175.176.177.178.179.180.181.182.183.184.185.186.187.188.189.
"outer", "on", "is", "declare", "set", "use", "values", "as", _"order", "by", "drop", "view", "go", "trigger", "cube", _"binary", "varbinary", "image", "char", "varchar", "text", _"datetime", "smalldatetime", "decimal", "numeric", "float", _"real", "bigint", "int", "smallint", "tinyint", "money", _"smallmoney", "bit", "cursor", "timestamp", "uniqueidentifier", _"sql_variant", "table", "nchar", "nvarchar", "ntext", "left", _"right", "like", "and", "all", "in", "null", "join", "not", "or"}
'Quindi le aggiunge una alla volta alla tabella con'colore rossoFor Each strKW In arrKeyWords
MyRow = Words.NewRow()MyRow("Word") = strKWMyRow("Color") = Color.LightCoral.NameWords.Rows.Add(MyRow)
NextEnd Sub
End Class
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.
Public Class SHRichTextBoxInherits System.Windows.Forms.RichTextBox
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
(ByVal hWnd As IntPtr, ByVal wMsg As Integer, _ByVal wParam As Integer, ByVal lParam As Integer) As Integer
Private Declare Function LockWindowUpdate Lib "user32" _(ByVal hWnd As Integer) As Integer
Private Enum EditMessages
LineIndex = 187LineFromChar = 201GetFirstVisibleLine = 206CharFromPos = 215PosFromChar = 1062
End Enum
Protected Overrides Sub OnTextChanged(ByVal e As EventArgs)'Non colora tutte le linee visibili, bensì solo la riga'dove si trova il cursorse: in questo modo l'applicazione'risulta più veloce. L'unico caso in cui questo'approccio non funzione è quando si copia un testo'all'interno della richtextbox. In quel caso ci sarà'un pulsante appositoDim LineIndex As Int32 = Me.GetLineFromCharIndex(Me.SelectionStart)Me.ColorLineNumber(LineIndex)
End Sub
'Colora tutta la RichTextBoxPublic Sub ColorRtb()
For I As Int32 = 0 To Me.Lines.Length - 1ColorLineNumber(I)
Next
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 visibiliPublic Sub ColorVisibleLines()
Dim FirstLine As Integer = FirstVisibleLine()Dim LastLine As Integer = LastVisibleLine() If (FirstLine = 0) And (LastLine = 0) Then
'Non c'è testoExit Sub
ElseWhile FirstLine < LastLine
ColorLineNumber(FirstLine)FirstLine += 1
End WhileEnd 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'HTMLPublic Sub ColorLineNumber(ByVal LineIndex As Int32)
TryIf Me.Lines(LineIndex).Length = 0 Then
Exit SubEnd If
Catch Ex As ExceptionExit Sub
End Try
'Indice del primo carattere della lineaDim FirstCharIndex As Int32 = _
Me.GetFirstCharIndexFromLine(LineIndex)'Tiene traccia del cursoreDim SelectionAt As Integer = Me.SelectionStart
'Blocca il refreshLockWindowUpdate(Me.Handle.ToInt32)
'Tiene traccia se ci siano tag apertiDim TagOpened As Boolean = False'Indica se il tag ha degli attributiDim Attribute As Boolean = False'Indica se un attributo è stato assegnatoDim Assigned As Boolean = False'Indica, per gli attributi come [readonly], se le parentesi'sono state aperteDim AttributeOpened As Boolean = False'Variabili locali che rappresentano Me.SelectionStart e'Me.SelectionLength: usando la variable enregistration si'guadagna qualche millisecondoDim Start, Length As Int32Dim Max As Int32 = _
(FirstCharIndex + Me.Lines(LineIndex).Length) - 1
Me.Select(FirstCharIndex, Max + 1)
For Index As Int32 = FirstCharIndex To MaxIf Char.IsLetterOrDigit(Me.Text(Index)) Then
Continue ForEnd If'Viene aperto un tag, inizia a selezionare'Es.: <aIf Me.Text(Index) = "<" Then
Start = IndexTagOpened = TrueAttribute = FalseAssigned = 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.
'Es.: <a href='www.example.com'>'altrimenti tutta la stringa da "<" a ">"'Es.: <div>If Not Attribute Then
Length = Index - StartMe.Select(Start, Length)Me.SelectionColor = Color.Blue
End IfMe.Select(Index, 1)Me.SelectionColor = Color.BlueMe.DeselectAll()TagOpened = FalseAttribute = FalseAssigned = 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 = FalseElse
Length = Index - StartMe.Select(Start, Length)Me.SelectionColor = Color.Blue
End IfMe.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 - StartMe.Select(Start, Length)Me.SelectionColor = Color.RedMe.DeselectAll()Attribute = TrueAssigned = True
ElseIf Me.Text(Index) = "[" Then'Apre un attributoStart = IndexAttributeOpened = True
ElseIf Me.Text(Index) = "]" And AttributeOpened Then'Chiude un attributo'Es.: <input type='text' [readonly]>Length = Index - StartMe.Select(Start, Length)Me.SelectionColor = Color.RedMe.DeselectAll()AttributeOpened = False
End IfNext
'Ripristina la selezioneMe.SelectionStart = SelectionAtMe.SelectionLength = 0'E il coloreMe.SelectionColor = Color.Black
'Riprende il refreshLockWindowUpdate(0)
End Sub
'Ottiene la prima linea visibilePublic Function FirstVisibleLine() As Integer
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
keywor 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:
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 parola 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.
180.181.182.183.184.185.186.187.188.189.190.191.192.193.
Return SendMessage(Me.Handle, EditMessages.GetFirstVisibleLine, 0, 0)End Function
'Ottiene l'ultima linea visibilePublic 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.LengthEnd If
Return LastLine
End FunctionEnd Class
01.02.03.04.05.06.07.08.09.10.11.12.13.14.
For Each Word As String In WordsI = FirstCharIndexDo
I = Me.Find(Word, I, I + Me.Lines(LineIndex).Length, _RichTextBoxFinds.None)
If I >= 0 ThenMe.SelectionStart = IMe.SelectionLength = Word.Length'Qui utilizo un dictionaryMe.SelectionColor = Words(Word)I += Word.Length
End IfLoop While I >= 0
Next
Esempio 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 Code
Project:
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 create dal programmatorePer 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:
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
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 stringaPublic Class PersonConverter
'(Per convenzione, i convertitori di questo tipo, devono'terminare con la parola "Converter"'...
End Class Public Class Person
Private _Name As StringPrivate _Birthday As DatePrivate _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
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 owsable : 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 owsable 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 PropertyGridImports 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 biografiaPublic Class Author
'Il nome completoPrivate _Name As String'Data di nascita e mortePrivate _Birth, _Death As Date'Indica se l'autore è ancora vivoPrivate _IsStillAlive As Boolean'Una piccola biografiaPrivate _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
GetReturn _Name
End GetSet(ByVal Value As String)
_Name = ValueEnd 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 StringGet
Return _BiographyEnd GetSet(ByVal Value As String)
_Biography = ValueEnd Set
End Property
<DisplayName("Data di nascita"), _Description("La data di nascita dell'autore."), _Browsable(True), _Category("Generalita'")> _
Public Property Birth() As DateGet
Return _BirthEnd GetSet(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 SetEnd Property
<DisplayName("Data di morte"), _
Description("Data di morte dell'autore."), _Browsable(True), _Category("Generalita'")> _
Public Property Death() As DateGet
Return _DeathEnd GetSet(ByVal Value As Date)
'Bisogna assicurarsi che la data di morte sia'posteriore a quella di nascitaIf Value.CompareTo(Me.Birth) < 1 Then
'Genera un'eccezioneThrow 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 IfEnd Set
End Property
<DisplayName("Vive"), _Description("Determina se l'autore è ancora in vita."), _Browsable(True), _Category("Generalita'")> _
Public Property IsStillAlive() As BooleanGet
Return _IsStillAlive
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.169.
End GetSet(ByVal Value As Boolean)
_IsStillAlive = ValueEnd Set
End Property
'Un nome e una data di nascita sono obbligatoriSub New(ByVal Name As String, ByVal Birth As Date)
Me.Name = NameMe.Birth = BirthMe.IsStillAlive = True
End Sub
'Tuttavia, il controllo PropertyGrid richiede un costruttore'senza parametriSub New()
Me.Birth = Date.NowMe.IsStillAlive = True
End SubEnd Class Public Class IsbnConverter
'Facendo derivare questa classe da ExpandableObjectConverter'si comunica al compilatore che questa classe è usata per'convertire in stringa un valore rappresentabile in una'PropertyGrid. Così facendo, sarà possibile modificare'il codice agendo sulla stringa complessiva e non'obbligatoriamente sulle varie partiInherits ExpandableObjectConverter
'Determina se sia possibile convertire nel tipo datoPublic Overrides Function CanConvertTo(ByVal Context As ITypeDescriptorContext, _
ByVal DestinationType As Type) As Boolean'Si può convertire in Isbn, dato che questa classe è'scritta apposta per questoIf (DestinationType Is GetType(Isbn)) Then
Return TrueEnd IfReturn MyBase.CanConvertFrom(Context, DestinationType)
End Function
'Determina se sia possibile convertire dal tipo datoPublic Overrides Function CanConvertFrom(ByVal Context As ITypeDescriptorContext, _
ByVal SourceType As Type) As Boolean'Si può convertire da String, dato che questa classe è'scritta apposta per questoIf (SourceType Is GetType(String)) Then
Return TrueEnd IfReturn MyBase.CanConvertFrom(Context, SourceType)
End Function
'Converte da stringa a IsbnPublic Overrides Function ConvertFrom(ByVal Context As ITypeDescriptorContext, _
ByVal Culture As Globalization.CultureInfo, _ByVal Value As Object) As ObjectIf TypeOf Value Is String Then
Dim Str As String = DirectCast(Value, String)'Cerca di creare un nuovo oggetto isbnTry
Dim Obj As Isbn = Isbn.CreateNew(Str)Return Obj
Catch ex As ExceptionMessageBox.Show(ex.Message, "Books Manager", _MessageBoxButtons.OK, MessageBoxIcon.Exclamation)Return New Isbn
End TryEnd IfReturn MyBase.ConvertFrom(Context, Culture, Value)
End Function
'Converte da Isbn a stringa
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 ObjectIf DestinationType Is GetType(String) And _
TypeOf Value Is Isbn ThenDim Temp As Isbn = DirectCast(Value, Isbn)Return Temp.ToString
End IfReturn MyBase.ConvertTo(Context, Culture, Value, DestinationType)
End FunctionEnd 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 CasePublic Class Isbn
'Un codice è formato da:'Un prefisso (3 cifre) - solitamente 978 indica un libro in generalePrivate _Prefix As Int16 = 978'Un identificativo linguistico (da 1 a 5 cifre): indica'il paese di provenienza dell'autore - in Italia è 88Private _LanguageID As Int16 = 88'Un prefisso editoriale (da 2 a 6 cifre): indica l'editorePrivate _PublisherID As Int64 = 89637'Un identificatore del titoloPrivate _TitleID As Int32 = 15'Un codice di controllo che può variare da 0 a 10. 10 viene'indicato con X, perciò lo imposto come CharPrivate _ControlChar As Char = "9"
<DisplayName("Prefisso"), _
Description("Prefisso del codice, costituito da tre cifre."), _Browsable(True), _Category("Isbn")> _
Public Property Prefix() As Int16Get
Return _PrefixEnd GetSet(ByVal Value As Int16)
If Value = 978 Or Value = 979 Then_Prefix = Value
ElseThrow New ArgumentException("Prefisso non valido!")
End IfEnd Set
End Property
<DisplayName("ID Lingua"), _Description("Identifica l'area da cui previene l'autore."), _Browsable(True), _Category("Isbn")> _
Public Property LanguageID() As Int16Get
Return _LanguageIDEnd GetSet(ByVal Value As Int16)
_LanguageID = ValueEnd Set
End Property
<DisplayName("ID Editore"), _Description("Identifica il marchio dell'editore."), _Browsable(True), _Category("Isbn")> _
Public Property PublisherID() As Int32Get
Return _PublisherIDEnd GetSet(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.313.
End SetEnd Property
<DisplayName("ID Titolo"), _
Description("Identifica il titolo del libro."), _Browsable(True), _Category("Isbn")> _
Public Property TitleID() As Int32Get
Return _TitleIDEnd GetSet(ByVal Value As Int32)
_TitleID = ValueEnd Set
End Property
<DisplayName("Carattere di controllo"), _Description("Verifica la correttezza degli altri valori."), _Browsable(True), _Category("Isbn")> _
Public Property ControlChar() As CharGet
Return _ControlCharEnd GetSet(ByVal Value As Char)
_ControlChar = ValueEnd Set
End Property
Public Sub New()
End Sub
'Restituisce in forma di stringa il codicePublic 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 erroriShared Function CreateNew(ByVal StringCode As String) As Isbn
'Con le espressioni regolari, ottiene le varie partiDim 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, procedeIf StringCode.Length = 17 And M.Success Then
Dim Result As New IsbnWith 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").ValueEnd WithReturn Result
ElseThrow New ArgumentException("Il codice inserito è errato!")
End IfEnd Function
End Class
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.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.
'Una classe che rappresenta un libroPublic Class Book
Private _Title As String'Si suppone che un libro abbia meno di 32767 pagine XDPrivate _Pages As Int16 = 100'Si possono anche avere più autori: in questo caso si ha'una lista a tipizzazione forte.Private _Authors As New List(Of Author)'L'eventuale serie a cui il libro appartienePrivate _Series As String'Casa editricePrivate _Publisher As String'Data di pubblicazionePrivate _PublicationDate As Date'ArgomentoPrivate _Subject As String'Costo in euroPrivate _Cost As Single = 1.0'RistampaPrivate _Reprint As Byte = 1'Codice ISBN13Private _Isbn As New Isbn
<DisplayName("Titolo"), _
Description("Il titolo del libro."), _Browsable(True), _Category("Editoria")> _
Public Property Title() As StringGet
Return _TitleEnd GetSet(ByVal Value As String)
_Title = ValueEnd Set
End Property
<DisplayName("Collana"), _Description("La collana o la serie a cui il libro appartiene."), _Browsable(True), _Category("Editoria")> _
Public Property Series() As StringGet
Return _SeriesEnd GetSet(ByVal Value As String)
_Series = ValueEnd Set
End Property
<DisplayName("Editore"), _Description("La casa editrice."), _Browsable(True), _Category("Editoria")> _
Public Property Publisher() As StringGet
Return _PublisherEnd GetSet(ByVal Value As String)
_Publisher = ValueEnd Set
End Property
<DisplayName("Pagine"), _Description("Il numero di pagine da cui il libro è composto."), _DefaultValue("100"), _Browsable(True), _Category("Dettagli")> _
Public Property Pages() As Int16Get
Return _PagesEnd Get
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 = ValueElse
Throw New ArgumentException("Numero di pagine insufficiente!")End If
End SetEnd Property
<DisplayName("Autore/i"), _
Description("L'autore o gli autori."), _Browsable(True), _Category("Editoria")> _
Public ReadOnly Property Authors() As List(Of Author)Get
Return _AuthorsEnd Get
End Property
<DisplayName("Pubblicazione"), _Description("La data di pubblicazione della prima edizione."), _Browsable(True), _Category("Dettagli")> _
Public Property PublicationDate() As DateGet
Return _PublicationDateEnd GetSet(ByVal Value As Date)
_PublicationDate = ValueEnd 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 IsbnGet
Return _IsbnEnd GetSet(ByVal Value As Isbn)
_Isbn = ValueEnd Set
End Property
<DisplayName("Ristampa"), _Description("Il numero della ristampa."), _DefaultValue(1), _Browsable(True), _Category("Dettagli")> _
Public Property Reprint() As ByteGet
Return _ReprintEnd GetSet(ByVal Value As Byte)
If Value > 0 Then_Reprint = Value
ElseThrow New ArgumentException("Ristampa: valore errato!")
End IfEnd Set
End Property
<DisplayName("Costo"), _Description("Il costo del libro, in euro."), _Browsable(True), _Category("Editoria")> _
Public Property Cost() As SingleGet
Return _CostEnd Get
E il codice del for m:
458.459.460.461.462.463.464.465.
Set(ByVal Value As Single)If Value > 0 Then
_Cost = ValueElse
Throw New ArgumentException("Inserire prezzo positivo!")End If
End SetEnd Property
End Class
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.
Class Form1Private Sub strAddBook_Click(ByVal sender As Object, _
ByVal e As EventArgs) Handles strAddBook.ClickDim Title As String = _
InputBox("Inserire il titolo del libro:", "Books Manager")
'Controlla che la stringa non sia vuota o nullaIf Not String.IsNullOrEmpty(Title) Then
Dim Item As New ListViewItem(Title)Dim Book As New Book()Book.Title = TitleItem.ImageIndex = 0Item.Tag = BooklstBooks.Items.Add(Item)
End IfEnd Sub
Private Sub lstBooks_SelectedIndexChanged(ByVal sender As Object, _
ByVal e As EventArgs) Handles lstBooks.SelectedIndexChanged'Esce dalla procedura se non ci sono elementi selezionatiIf lstBooks.SelectedIndices.Count = 0 Then
Exit SubEnd If
'Altrimenti procedepgBook.SelectedObject = lstBooks.SelectedItems(0).Tag
End Sub
C1. Introduzione ai database relazionali
Il modello r elazionale non è stato il pr imo in assoluto ad esser e usato per la gestione dei database, ma è stato
intr odotto più tar di, negli anni '70, gr azie alle idee di E. F. Codd. Ad oggi, è il modello più diffuso e utilizzato per la
sua semplicità.
Tale modello si basa su un unico concetto, la relazione, una tabella costituita da r ig he (o record o tuple) e colonne
(o attr ibuti). Per definir e una r elazione, basta specificar ne il nome e gli attr ibuti. Ad esempio:
Person (FirstName, LastName, BirthDay)
indica una r elazione di nome Per son, che pr esenta tr e colonne, denominate r ispettivamente Fir stName, LastName e
Bir thDay. Una volta data la definizione, per ò, è necessar io anche for nir e dei dati che ne r ispettino le r egole: dobbiamo
aggiunger e delle r ighe a questa tabella per r appr esentar e i dati che ci inter essano, ad esempio:
Relazione Per son
Fir stName LastName Bir thDay
Mar io Rossi 1/1/1965
Luigi Bianchi 13/7/1971
...
L'insieme di tutte le r ighe della r elazione si dice estensione della relazione, mentr e ogni singola tupla viene anche
chiamata istanza di relazione. Facendo un par allelismo con la pr ogr ammazione ad oggetti, quindi, avr emo queste
"somiglianze" (che si r iveler anno di vitale impor tanza nella tipizzazione for te, come vedr emo in seguito):
Database Programmazione ad oggettiRelazione -> ClasseTupla -> OggettoEstensione della relazione -> Lista di oggettiAttributo -> Proprietà dell'oggettoIstanza di relazione -> Istanza di classe (= Oggetto)
Avendo or a chiar ito questi par allelismi, vi sar à più facile entr ar e nella mentalità del modello r elazionale, dato che, se
siete ar r ivati fino a qui, si assume che sappiate già benissimo tutti gli aspetti e i concetti della pr ogr ammazione ad
oggetti.
Uno sguar do attento, tuttavia, far à notar e che, tr a i car atter i fondamentali che si possono r intr acciar e in questi
par allelismi, manca il concetto di "tipo" di un attr ibuto. Infatti, per come abbiamo pr ima definito la r elazione, sar ebbe
del tutto lecito immetter e un numer o inter o nel campo Fir stName o una str inga in Bir thDay. Per for tuna, il modello
pr evede anche che ogni colonna possegga un dominio, 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 chiave pr imar ia (pr imar y key), che ser ve ad identificar e univocamente una tupla
all'inter no della r elazione. Facendo un par agone con la pr ogr ammazione, se una tupla è assimilabile ad un oggetto ed
esistono due tuple con attr ibuti identici, esse non r appr esentano comunque la stessa entità, pr opr io come due oggetti
con pr opr ietà uguali non sono lo stesso oggetto. E se per gli oggetti esiste un codice "segr eto" per distinguer li (guid), a
cui solo il pr ogr amma ha accesso, così esiste un par ticolar e valor e che ser ve per indicar e senza ombr a di dubbio se
due r ecor d sono differ enti: questo valor e è la chiave pr imar ia. Essa è solitamente un numer o inter o positivo ed è
anche la pr ima colonna definita dalla r elazione. Modificando la definizione di Per son data pr ecedente, ed intr oducendo
anche il dominio degli attr ibuti, si otter r ebbe:
'Questa sintassi è del tutto inventata!'Serve solo per esemplificare i concetti:Person (ID As Integer, FirstName As String, LastName As String, BirthDay As Date)
Relazione Per son
ID Fir stName LastName Bir thDay
1 Mar io Rossi 1/1/1965
2 Luigi Bianchi 13/7/1971
...
Per distinguer e le singole r ighe esiste, poi, un'altr a tipologia di chiave, detta chiave candidata, costituita dal più
piccolo insieme di attr ibuti per cui non esistono due tuple in cui quegli attr ibuti hanno lo stesso valor e. In gener ale,
tutte le chiavi pr imar ie sono chiavi canditate, a causa della stessa definizione data poco fa; mentr e esistono chiavi
candidate che non sono chiavi pr imar ie. Ad esempio, in questo caso, l'insieme degli attr ibuti Fir stName, LastName e
Bir thDay costituisce una chiave candidata, poichè è pr aticamente impossibile tr ovar e due per sone con lo stesso nome
nate nello stesso gior no alla stessa or a (almeno, è impossibile nella nostr a r elazione for mata da due elementi XD e
questo ci basta): quindi, questi tr e attr ibuti soddisfano le condizioni della definizione e identificano univocamente un
r ecor d. In gener e, si sceglie una fr a tutte le chiavi candidate possibili che viene assunta come chiave pr imar ia: a r igor
di logica, essa dovr à esser e la più semplice di tutte. In questo caso, il singolo numer o ID è molto più maneggevole che
non l'insieme di due str inghe e una data.
Or a, ammettiamo di aver e una r elazione così definita:
Program (ProgramID As Integer, Path As String, Description As String)
che indica un qualsiasi pr ogr amma installato su un computer ; e quest'altr a r elazione:
User (UserID As Integer, Name As String, MainFolder As String)
che indica un qualsiasi utente di quel computer . Ammettiamo anche che la macchina sulla quale sono installati i
pr ogr ammi pr esenti nell'estensione della r elazione sia condivisa da più utenti: vogliamo stabilir e, tr amite r elazioni,
quale utente possa acceder e a quale pr ogr amma. In questa cir costanza, abbiamo diver se soluzioni possibili, ma una sola
è la miglior e:
Abbiamo detto che la r elazione Pr ogr am ha una chiave pr imar ia, e la r elazione User pur e. Dato che si tr atta di
due tabelle diver se, potr ebbe venir e in mente di stabilir e questa policy di accesso: un utente può acceder e a un
pr ogr amma solo se la sua chiave pr imar ia (User ID) coincide con la chiave pr imar ia del pr ogr amma (Pr ogr amID).
In questo caso, tuttavia, le cir costanze sono molto r estr ittive, in quanto un utente può usar e uno e un solo
pr ogr amma (e vicever sa). La r elazione (in senso letter ale, ossia il collegamento) tr a le due tabelle si dice uno a
uno.
Aggiungiamo alla r elazione Pr ogr am un altr o attr ibuto User ID, che dovr ebbe indicar ci l'utente che può usar e un
dato pr ogr amma. Tuttavia, come abbiamo visto pr ima, i valor i delle colonne devono esser e atomici e per ciò non
possiamo inser ir e in quella singola casella tutta un'altr a r iga (anche per chè non sapr emmo che tipo specificar e
come dominio). Qui ci viene in aiuto la chiave pr imar ia: sappiamo che nella r elazione User , ogni tupla è
univocamente identificata da una e una sola chiave pr imar ia chiamata User ID, quindi indicando una chiave,
indichiamo anche la r iga ad essa associata. Per cui, possiamo ben cr ear e un nuovo attr ibuto di tipo inter o (in
quanto la chiave è un numer o inter o in questo caso), nel quale specifichiamo l'User ID dell'utente che può usar e il
nostr o pr ogr amma. Ad esempio:
Program (ProgramID As Integer, Path As String, Description As String, UserID As String)
Relazione Pr ogr am
Pr ogr amID Path Descr iption User ID
1 C:\WINDOWS\notepad.ex e Editor di testo 2
2 C:\Pr ogr ammi\Fir eFox \fir efox .ex e Fir eFox web br owser 1
3 C:\Pr ogr ammi\Wor ld of War cr aft\WoW.ex e Wor ld of War cr aft 2
...
Relazione User
User ID Name MainFolder
1 Mar io Rossi C:\User s\MRossi
2 Luigi Bianchi 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 molti (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 molti 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 non es is ta alcuna chiave primaria. Infatti lo scopo di questa r elazione è un
altr o: ad un cer to pr ogr amma associa un utente, ma questo lo si può far e più volte. Ad esempio:
Relazione Pr ogr am
Pr ogr amID Path Descr iption
1 C:\WINDOWS\notepad.ex e Editor di testo
2 C:\Pr ogr ammi\Fir eFox \fir efox .ex e Fir eFox web br owser
3 C:\Pr ogr ammi\Wor ld of War cr aft\WoW.ex e Wor ld of War cr aft
...
Relazione User
User ID Name MainFolder
1 Mar io Rossi C:\User s\MRossi
1 Luigi Bianchi C:\User s\Gigi
...
Relazione User sPr ogr ams
User ID Pr ogr amID
1 1
1 2
2 2
2 3
...
Nell'ultima r elazione tr oviamo un 1 (due volte) associamo pr ima ad un 1 e poi ad un 2: significa che lo stesso
utente 1 (Mar io Rossi) può acceder e sia al pr ogr amma 1 (notepad) sia al pr ogr amma 2 (fir efox ). Allo stesso
modo, l'utente 2 può acceder e sia al pr ogr amma 2 (fir efox ) sia al pr ogr amma 3 (Wor ld of War cr aft). Con
l'aggiunta di un'altr a tabella siamo r iusciti a legar e più utenti a più pr ogr ammi. Relazioni tr a tabelle di questo
tipo si dicono molti a molti.
In ognuno di questi esempi, l'inter o con cui ci si r ifer isce ad un'altr a tupla viene detto chiave esterna.
C2. Descrizione dei componenti principali
Dettagli tecnic iPr 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 A1 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 amewor 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.
ConnessioneLa 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
connection 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:
Esiste anche un'altr a var iante della connection str ing, che si pr esenta come segue:
"Data Source=localhost; UserId=root; PWD=root;"
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 connessioneConn.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
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...
Esecuzione di una queryIl 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: World Wide Web Consortium ,
Morpheus Web, HTML.it (SQL) HTML.it (MySQL).
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:
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
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
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:
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 recordCome 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;
HasRows : 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 Windows For m con una listview e un pulsante:
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.17.
18.19.20.21.22.23.
Dim Conn As New MySqlConnection("Server=localhost; Database=appdata; Uid=root; Pwd=root;")Dim Cmd As New MySqlCommand() Try
Conn.Open()Cmd.Connection = Conn'Crea una nuova tabella di nome Customers nel database'appdata. I suoi attributi (colonne) sono:' - ID : identificativo numerico del record; non può essere' vuoto, viene autoincrementato quando si aggiunge una' nuova riga ed è la chiave primaria della' relazione Customers' - FirstName : nome del cliente (max 150 caratteri)' - LastName : cognome del cliente (max 150 caratteri)' - Address : indirizzo (max 255 caratteri)' - PhoneNumber : numero telefonico (max 30 caratteri)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()Catch Ex As Exception Finally
Conn.Close()End Try
01.02.03.04.05.
Imports MySql.Data.MySqlClient Class Form1
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.
56.57.58.59.60.61.62.
Handles btnLoad.ClickDim 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 CustomersCommand.CommandText = "SELECT * FROM Customers;"Command.Connection = ConnConn.Open()
'Esegue la query e restituisce il readerDim 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 MySqlDataReaderFor I As Int32 = 0 To Reader.FieldCount - 1
lstRecords.Columns.Add(Reader.GetName(I))Next
Dim L As ListViewItemDim S(Reader.FieldCount - 1) As String
'Fintanto che c'è qualche record da leggere,'lo aggiunge alla listviewDo 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 postoFor I As Int32 = 0 To S.Length - 1
If Not Reader.IsDBNull(I) Then'GetString(I) ottiene il valore della cella'I sottoforma di stringaS(I) = Reader.GetString(I)
ElseS(I) = ""
End IfNextL = New ListViewItem(S)lstRecords.Items.Add(L)
Loop
'Chiude il ReaderReader.Close()
Catch ex As ExceptionMessageBox.Show(ex.Message, Me.Text, MessageBoxButtons.OK,
MessageBoxIcon.Exclamation)Finally
'Chiude la connessioneConn.Clone()
End TryEnd Sub
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.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.
Imports MySql.Data.MySqlClient Class Form1
Private Conn As MySqlConnection
'Esegue una query sul database, quindi carica i'risultati nella listviewPrivate Sub LoadData(ByVal Query As String)
Dim Command As New MySqlCommand
Command.CommandText = QueryCommand.Connection = Conn
Dim Reader As MySqlDataReader = Command.ExecuteReader()
If lstRecords.Columns.Count = 0 Then
For I As Int32 = 0 To Reader.FieldCount - 1lstRecords.Columns.Add(Reader.GetName(I))
NextEnd If
Dim L As ListViewItemDim S(Reader.FieldCount - 1) As String
lstRecords.Items.Clear()Do While Reader.Read()
For I As Int32 = 0 To S.Length - 1If Not Reader.IsDBNull(I) Then
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.
S(I) = Reader.GetString(I)Else
S(I) = ""End If
NextL = New ListViewItem(S)lstRecords.Items.Add(L)
Loop
Reader.Close()Command.Dispose()Command = Nothing
End Sub
Private Sub LoadData()Me.LoadData("SELECT * FROM Customers")
End Sub
'Scorciatoia per eseguire una query velocementePrivate 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) HandlesMyBase.LoadConn = 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.ClickIf ExecuteQuery(String.Format("INSERT INTO Customers VALUES(null, '{0}', '{1}', '{2}',
'{3}');", txtFirstName.Text, txtLastName.Text, txtAddress.Text,txtPhoneNumber.Text)) ThenMessageBox.Show("Cliente aggiunto!", Me.Text, MessageBoxButtons.OK,
MessageBoxIcon.Information)LoadData()
End IfEnd Sub
Private Sub btnEdit_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
Handles btnEdit.ClickIf lstRecords.SelectedIndices.Count = 0 Then
MessageBox.Show("Nessun record selezionato!", Me.Text, MessageBoxButtons.OK,MessageBoxIcon.Exclamation)
Exit SubEnd 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 modificatiQuery.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.
147.148.149.150.151.152.153.154.155.156.157.
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 = NothingLoadData()
End Sub
Private Sub btnFilter_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)Handles btnFilter.ClickDim 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 IfNext
If Conditions.Count >= 1 Then
Query.AppendFormat(" {0}", Conditions(0))If Conditions.Count > 1 Then
For I As Int32 = 1 To Conditions.Count - 1Query.AppendFormat(" AND {0}", Conditions(I))
NextEnd If
End IfQuery.Append(";")
LoadData(Query.ToString())Query = Nothing
End Sub
Private Sub Form1_FormClosing(ByVal sender As System.Object, ByVal e AsSystem.Windows.Forms.FormClosingEventArgs) Handles MyBase.FormClosingIf 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.ClickLoadData()
End Sub
Private Sub lstRecords_SelectedIndexChanged(ByVal sender As System.Object, ByVal e AsSystem.EventArgs) Handles lstRecords.SelectedIndexChangedIf lstRecords.SelectedItems.Count = 0 Then
Exit SubEnd If
Dim Selected As ListViewItem = lstRecords.SelectedItems(0)txtFirstName.Text = Selected.SubItems(1).TexttxtLastName.Text = Selected.SubItems(2).TexttxtAddress.Text = Selected.SubItems(3).TexttxtPhoneNumber.Text = Selected.SubItems(4).Text
End SubEnd 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 amewor 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.
DataSetLa 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);
DataTableSe 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, Rows, Pr imar yKey e del metodo AddRow (ho menzionato solo quelli di uso più fr equente).
Caricamento e bindingOr 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 idView1, possiamo scr iver e questo codice:
DataGr idView1 ver r à r iempito con tutti i dati pr esenti nella pr ima (e unica) tabella del dataset.
DataSet tipizzatiNel 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
01.02.03.04.05.
06.
07.08.09.10.11.12.13.14.15.16.17.18.19.
Imports MySql.Data.MySqlClientClass Form1
Private Data As New DataSet
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) HandlesMyBase.LoadDim Conn As New MySqlConnection("Server=localhost; Database=appdata; Uid=root;
Pwd=root;")Dim Adapter As New MySqlDataAdapter
Conn.Open()
Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Customers;", Conn)Adapter.Fill(Data)
Conn.Clone()
DataGridView1.DataSource = Data.Tables(0)
End Sub
End Class
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).
Musica, 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.002.003.
004.
005.006.007.008.009.010.011.012.013.014.015.016.017.018.019.
Public Class Form1
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) HandlesMyBase.LoadDim Conn As New MySqlConnection("Server=localhost; Database=appdata; Uid=root;
Pwd=root;")Dim Adapter As New MySqlDataAdapter
Conn.Open()
'Carica le tabelle nel dataset. Le tabelle sono ora'accessibili mediante omonime proprietà dal'dataset tipizzato Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Songs;", Conn)Adapter.Fill(AppDataSet.Songs)
Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Authors;", Conn)Adapter.Fill(AppDataSet.Authors)
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 AsSystem.EventArgs) Handles lstSongs.SelectedIndexChangedIf lstSongs.SelectedIndex < 0 Then
Exit SubEnd 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.NameIf Not Author.IsNicknameNull() Then
lblAuthorName.Text &= vbCrLf & Chr(34) & Author.Nickname & Chr(34)End IfIf Not Author.IsImageNull() Then
imgAuthor.Image = Image.FromFile(Author.Image)Else
imgAuthor.Image = NothingEnd If
If Not Author.IsDescriptionNull() Then
txtAuthorDescription.Text = Author.DescriptionElse
txtAuthorDescription.Text = ""End IftabAuthor.Tag = Author.ID
End IfEnd If
tabAlbum.Tag = NothingIf Not S.IsAlbumNull() Then
Dim Albums() As AppDataSet.AlbumsRow = S.GetAlbumsRows()
If Albums.Length > 0 ThenDim Album As AppDataSet.AlbumsRow = Albums(0)
lblAlbumName.Text = Album.NameIf Not Album.IsYearNull() Then
lblAlbumYear.Text = Album.YearElse
lblAlbumYear.Text = ""End IfIf Not Album.IsImageNull() Then
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.
imgAlbum.Image = Image.FromFile(Album.Image)Else
imgAlbum.Image = NothingEnd IfIf Not Album.IsDescriptionNull() Then
txtAlbumDescription.Text = Album.DescriptionElse
txtAlbumDescription.Text = ""End IftabAlbum.Tag = Album.ID
End IfEnd If
End Sub
Private Sub btnSearch_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)Handles btnSearch.ClickIf 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)
ElseSongsBindingSource.Filter = ""
End IfEnd Sub
End Class
C5. Dalle relazioni agli oggetti - Parte II
Aggiungere, eliminare, modificareL'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.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.50.51.52.53.54.
Public Class AuthorViewerPrivate _Author As AppDataSet.AuthorsRow
'Evento generato quando un autore viene aggiunto. Questo'evento si verifica se l'utente salva dei cambiamenti'quando la proprietà Author è Nothing.'Non potendo modificare una riga esistente, quindi, ne'viene creata una nuova. Poich´, tuttavia, questo'autore deve essere associato alla canzone, bisogna che'qualcuno ponga l'ID della nuova riga nel campo'Author della canzone opportuna e, dato che questo'controllo non può n´ logicamente né'praticamente arrivare a fare ciò, bisogna che'qualcun altro se ne occupi.Public Event AuthorAdded As EventHandler
Public Property Author() As AppDataSet.AuthorsRow
GetReturn _Author
End GetSet(ByVal value As AppDataSet.AuthorsRow)
If value IsNot Nothing Then_Author = valueWith value
lblName.Text = .NameIf Not .IsImageNull() Then
imgAuthor.ImageLocation = .ImageElse
imgAuthor.ImageLocation = NothingEnd IfIf Not .IsDescriptionNull() Then
txtDescription.Text = .DescriptionElse
txtDescription.Text = ""End If
End WithElse
lblName.Text = "Nessun nome"imgAuthor.ImageLocation = NothingtxtDescription.Text = ""
End IfimgSave.Visible = False
End SetEnd Property
Private Sub imgAuthor_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
Handles imgAuthor.Click'FOpen è un OpenFileDialog dichiarato nel designer.'Questo codice serve per caricare un'immagine da disco'fissoIf FOpen.ShowDialog = DialogResult.OK Then
imgAuthor.ImageLocation = FOpen.FileNameimgSave.Visible = True
End If
E questa è l'inter faccia:
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.TextChangedimgSave.Visible = True
End Sub
Private Sub lblName_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)Handles lblName.ClickDim NewName As String = InputBox("Inserire nome:")
If Not String.IsNullOrEmpty(NewName) Then
lblName.Text = NewNameimgSave.Visible = True
End IfEnd Sub
Private Sub imgSave_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
Handles imgSave.ClickIf _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 AuthorAddedRaiseEvent AuthorAdded(Me, EventArgs.Empty)
Else_Author.Name = lblName.Text_Author.Description = txtDescription.Text_Author.Image = imgAuthor.ImageLocation
End IfimgSave.Visible = False
End SubEnd Class
È 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.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.
Imports MySql.Data.MySqlClient Public Class Form1
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) HandlesMyBase.LoadDim Conn As New MySqlConnection("Server=localhost; Database=appdata; Uid=root;
Pwd=root;")Dim Adapter As New MySqlDataAdapter
Conn.Open()
Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Songs;", Conn)Adapter.Fill(AppDataSet.Songs)
Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Authors;", Conn)Adapter.Fill(AppDataSet.Authors)
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 AsSystem.EventArgs) Handles lstSongs.SelectedIndexChangedIf lstSongs.SelectedIndex < 0 Then
Exit SubEnd If
Dim S As AppDataSet.SongsRow = AppDataSet.Songs.FindByID(lstSongs.SelectedValue)
lblName.Text = S.Title
tabAuthor.Tag = NothingIf Not S.IsAuthorNull() Then
Dim Authors() As AppDataSet.AuthorsRow = S.GetAuthorsRows()
'Imposta la proprietà Author del controllo avAuthor,'che non è altro che un'istanza di AuthorViewer,'il controllo utente creato poco faIf Authors.Length > 0 Then
Dim Author As AppDataSet.AuthorsRow = Authors(0)avAuthor.Author = Author
ElseavAuthor.Author = Nothing
End IfEnd If
tabAlbum.Tag = NothingIf Not S.IsAlbumNull() Then
Dim Albums() As AppDataSet.AlbumsRow = S.GetAlbumsRows()
If Albums.Length > 0 ThenDim Album As AppDataSet.AlbumsRow = Albums(0)
lblAlbumName.Text = Album.NameIf Not Album.IsYearNull() Then
lblAlbumYear.Text = Album.YearElse
lblAlbumYear.Text = ""
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.
End IfIf Not Album.IsImageNull() Then
imgAlbum.Image = Image.FromFile(Album.Image)Else
imgAlbum.Image = NothingEnd IfIf Not Album.IsDescriptionNull() Then
txtAlbumDescription.Text = Album.DescriptionElse
txtAlbumDescription.Text = ""End IftabAlbum.Tag = Album.ID
End IfEnd If
End Sub
Private Sub btnSearch_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)Handles btnSearch.ClickIf 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 AsSystem.EventArgs) Handles avAuthor.AuthorAddedIf lstSongs.SelectedIndex < 0 Then
Exit SubEnd If
Dim S As AppDataSet.SongsRow = AppDataSet.Songs.FindByID(lstSongs.SelectedValue)'Imposta il campo Author della canzone correnteS.Author = avAuthor.Author.ID
End Sub
Private Sub Form1_FormClosing(ByVal sender As System.Object, ByVal e AsSystem.Windows.Forms.FormClosingEventArgs) Handles MyBase.FormClosingDim Conn As New MySqlConnection("Server=localhost; Database=appdata; Uid=root;
Pwd=root;")Dim Adapter As New MySqlDataAdapter 'Un oggetto di tipo CommandBuilder genera automaticamente'tutti le istruzioni update, insert e delete che servono'a un adapter per funzionare. Tali istruzioni vengono'generate relativamente alla tabella dalla quale si stanno'caricando i datiDim Builder As MySqlCommandBuilder
Conn.Open()
Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Songs;", Conn)Builder = New MySqlCommandBuilder(Adapter)Adapter.Update(AppDataSet.Songs)
Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Authors;", Conn)Builder = New MySqlCommandBuilder(Adapter)Adapter.Update(AppDataSet.Authors)
Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Albums;", Conn)Builder = New MySqlCommandBuilder(Adapter)Adapter.Update(AppDataSet.Albums)
Conn.Clone()
End SubEnd Class
C6. Il controllo BindingNavigator
FunzionamentoQuesto 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 dove attinger e i dati
e come 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.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.
Public Class Form1
Public Class AuthorPrivate _Name As StringPrivate _Works As List(Of String)
Public Property Name() As String
GetReturn _Name
End GetSet(ByVal value As String)
_Name = valueEnd Set
End Property
Public ReadOnly Property Works() As List(Of String)Get
Return _WorksEnd Get
End Property
Public Sub New()_Works = New List(Of String)
End Sub
Public Sub New(ByVal Name As String, ByVal ParamArray WorksNames() As String)Me.New()Me.Name = NameMe.Works.AddRange(WorksNames)
End SubEnd 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:
34.35.
Public Authors As New List(Of Author) End Class
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.50.
Public Class Form1
Public Class AuthorPrivate _Name As StringPrivate _Works As List(Of String)
Public Property Name() As String
GetReturn _Name
End GetSet(ByVal value As String)
_Name = valueEnd Set
End Property
Public ReadOnly Property Works() As List(Of String)Get
Return _WorksEnd Get
End Property
Public Sub New()_Works = New List(Of String)
End Sub
Public Sub New(ByVal Name As String, ByVal ParamArray WorksNames() As String)Me.New()Me.Name = NameMe.Works.AddRange(WorksNames)
End SubEnd Class
Public Authors As New List(Of Author)
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles
MyBase.Load'Aggiungie alcuni elementi iniziali alla listaAuthors.Add(New Author("Dante Alighieri", "Comedìa", "Vita Nova", "De vulgari
eloquentia", "De Monarchia"))Authors.Add(New Author("Luigi Pirandello", "Il fu Mattia Pascal", "Uno, nessuno,
centomila", "Il gioco delle parti"))'Imposta la sorgente di dati del bindingsourcebsAuthors.DataSource = Authors
'Aggiunge un binding alla textbox. Ciò significa'che la proprietà Text di txtName sarà da'ora in poi sempre legata alla proprietà Name'dell'elemento corrente della sorgente di dati di'bsAuthors. Dato che quest'ultima è una lista di'Author, ogni suo elemento espone la proprietà'Name.txtName.DataBindings.Add("Text", bsAuthors, "Name")
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).
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 AsSystem.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 contieneDim Author As Author = bsAuthors.CurrentlstWorks.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'utenteIf Not String.IsNullOrEmpty(txtAdd.Text) Then
lstWorks.Items.Add(txtAdd.Text)DirectCast(bsAuthors.Current, Author).Works.Add(txtAdd.Text)txtAdd.Text = ""
End IfEnd Sub
Private Sub btnDelete_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
Handles btnDelete.Click'Elimina una delle opere visualizzateIf lstWorks.SelectedIndex >= 0 Then
DirectCast(bsAuthors.Current, Author).Works.RemoveAt(lstWorks.SelectedIndex)lstWorks.Items.RemoveAt(lstWorks.SelectedIndex)
End IfEnd Sub
End Class
C7. DataGridView - Parte I
IntroduzioneDataGr idView è uno dei contr olli più potenti e gr andi del Fr amewor 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);
AllowUser To AddRows, DeleteRows, Or der Columns, ResizeColumns, ResizeRows: 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 natingRowsDefaultCellStyle : 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.
AutoSizeRowsMode : 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 / RowCount : 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 idViewCell)
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/Rows(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 solo 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 entRowDir 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/Rows : 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), FullRowSelect (tutta la r iga), FullColumnSelect (tutta la colonna), RowHeader Select (solo
l'intestazione della r iga) o ColumnHeader Select (solo l'intestazione della colonna);
ShowCellToolTip : indica se visualizzar e il tooltip della cella;
ShowEditingIcon : 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 lassico esempio di gestionaleLa 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 solo 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.002.
Imports MySql.Data.MySqlClient
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) HandlesMyBase.LoadDim 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 ExceptionMessageBox.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'iconadgvCustomers.Columns(0).Visible = FalsedgvCustomers.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 defaultImg.Image = imgAccountTypes.Images(0)'E l'intestazione della colonnaImg.HeaderText = "AccountLevel"'Quindi la aggiunge a quelle esistentidgvCustomers.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 comunqueFor 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.
Row.Cells(7).Value = imgAccountTypes.Images(CInt(Row.Cells(6).Value))Next
'Imposta come sorgente di dati di dgvOrders il binding'source bsOrders, specificato in precedenzadgvOrders.DataSource = bsOrders'E nasconde le prime due colonne, ossia CustomerID e IDdgvOrders.Columns(0).Visible = FalsedgvOrders.Columns(1).Visible = False'Impone di visualizzare solo le tuple di ID pari a -1,'ossia nessunabsOrders.Filter = "ID=-1"
End Sub
Private Sub dgvCustomers_KeyDown(ByVal sender As System.Object, ByVal e AsSystem.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 AccountTypeDim CurValue As Int32 = CInt(dgvCustomers.CurrentRow.Cells(6).Value)
'Se è stato premuto +, aumenta il valore di 1'Se è stato premuto -, lo decrementa di 1If e.KeyCode = Keys.Oemplus Then
CurValue += 1ElseIf e.KeyCode = Keys.OemMinus Then
CurValue -= 1End If
'Fa in modo di non andare oltre i limit impostiIf CurValue < 0 Then
CurValue = 0ElseIf CurValue > 2 Then
CurValue = 2End If
'Quindi imposta il nuovo valore di AccountTypedgvCustomers.CurrentRow.Cells(6).Value = CurValue'E la corrispondente nuova immaginedgvCustomers.CurrentCell.Value = imgAccountTypes.Images(CurValue)
End IfEnd 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 cosaPrivate Sub dgvCustomers_DataError(ByVal sender As System.Object, ByVal e As
System.Windows.Forms.DataGridViewDataErrorEventArgs) Handles dgvCustomers.DataErrorDim Result As DialogResult
'Riporta l'errore all'utente, e lascia scegliere se'modificare i dati incompatibili oppure annullare'le modifiche e cancellare la rigaResult = 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.
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 correntedgvCustomers.BeginEdit(True)
Else'Annulla l'eccezione e l'evento, quindi cancella'la riga correntee.ThrowException = Falsee.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 IfEnd Sub
'Questa procedura aggiorna la ListView con alcuni dettagli'sullo stati dei pagamentiPrivate Sub RefreshPreview(ByVal CustomerID As Int32)
lstPreview.Items.Clear()
'Cerca il clienteDim Customer As AppDataSet.CustomersRow = _
MainDatabase.Customers.FindByID(CustomerID)
'Se non esiste, esceIf Customer Is Nothing Then
Exit SubEnd If Dim TotalPaid, TotalUnpaid As SingleDim Delay As TimeSpan
'Notate che qui agiamo direttamente sul dataset,'perchè contiene campi tipizzati, e ci consente di'utilizzare meno operatori di castFor Each Order As AppDataSet.OrdersRow In MainDatabase.Orders
'Conta solo gli ordini associati a un clienteIf Order.CustomerID <> CustomerID Then
Continue ForEnd 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 pagamentoIf Order.Settled Then
TotalPaid += Order.ItemPrice * Order.ItemCountElse
TotalUnpaid += Order.ItemPrice * Order.ItemCountIf Date.Now - Order.CreationDate > Delay Then
Delay = Date.Now - Order.CreationDateEnd If
End IfNext
Dim Item1 As New ListViewItemItem1.Text = "Ammontare pagato"Item1.SubItems.Add(String.Format("{0:N2}€", TotalPaid))
Dim Item2 As New ListViewItemItem2.Text = "Oneri futuri"Item2.SubItems.Add(String.Format("{0:N2}€", TotalUnpaid))
Dim Item3 As New ListViewItemItem3.Text = "Ritardo pagamento"Item3.SubItems.Add(CInt(Delay.TotalDays) & " giorni")
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
softwar 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.
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.
Dim DaysLimit As Int32'Un diverso tipo di account permette un maggior ritardo'nei pagamenti...Select Case Customer.AccountType
Case 0DaysLimit = 60
Case 1DaysLimit = 90
Case 2DaysLimit = 120
Case ElseDaysLimit = 60
End Select
'Se il cliente ha superato il limite con almeno uno'dei suoi ordini, la riga viene colorata in rossoIf Delay.TotalDays > DaysLimit Then
Item3.ForeColor = Color.RedEnd If
lstPreview.Items.Add(Item1)lstPreview.Items.Add(Item2)lstPreview.Items.Add(Item3)
End Sub
'Evento generato quando l'utente si posizione su una rigaPrivate 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 clientebsOrders.Filter = "CustomerID=" & CurID'E aggiorna la listviewRefreshPreview(CurID)
End IfEnd 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.CellEndEditTry
RefreshPreview(CInt(dgvCustomers.CurrentRow.Cells(0).Value))Catch Ex As Exception
End Try
End SubEnd Class
C8. DataGridView - Parte II
Manipolazione dei datiNell'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 non 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.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.
Public Class CreateOrderDialog
Private CustomerID As Int32Private _NewOrder As AppDataSet.OrdersRow 'Restituisce una nuova riga con gli attributi impostati'nel dialogPublic ReadOnly Property NewOrder() As AppDataSet.OrdersRow
GetReturn _NewOrder
End GetEnd Property
'Per creare un nuovo ordine ci serve l'ID del cliente ad'esso associato, perciò dobbiamo costringere il chiamante'(ossia noi stessi XD) a passarci questo dato in qualche'modo. In questo caso, sovrascriviamo il metodo ShowDialog'mediante shadowing:Public Shadows Function ShowDialog(ByVal CustomerID As Int32) As DialogResult
Me.CustomerID = CustomerIDReturn MyBase.ShowDialog()
End Function
Private Sub OK_Button_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)Handles OK_Button.ClickIf String.IsNullOrEmpty(txtItemName.Text) Then
MessageBox.Show("Specificare una descrizione valida del prodotto!", Me.Text,MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
Exit SubEnd If
If nudItemPrice.Value = 0.0F Then
MessageBox.Show("Prezzo non valido!", Me.Text, MessageBoxButtons.OK,MessageBoxIcon.Exclamation)
Exit SubEnd If
Tenendo conto del nuovo dialog appena scr itto, il codice del for m diventer ebbe:
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:
36.37.38.39.40.41.42.43.44.45.46.47.48.49.
50.51.52.53.54.
_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.CheckedEnd With Me.DialogResult = System.Windows.Forms.DialogResult.OKMe.Close()
End Sub
Private Sub Cancel_Button_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)Handles Cancel_Button.ClickMe.DialogResult = System.Windows.Forms.DialogResult.CancelMe.Close()
End Sub End Class
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 MySql.Data.MySqlClientPublic Class Form1
'... Private Sub strAddCustomer_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles strAddCustomer.ClickMainDatabase.Customers.AddCustomersRow("", "", "", "", Date.Now, 0)
End Sub
Private Sub strAddOrder_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)Handles strAddOrder.ClickIf dgvCustomers.CurrentRow Is Nothing Then
Exit SubEnd If
Dim CID As Int32 = CInt(dgvCustomers.CurrentRow.Cells(0).Value)Dim OrderDialog As New CreateOrderDialog()
If OrderDialog.ShowDialog(CID) = Windows.Forms.DialogResult.OK Then
MainDatabase.Orders.AddOrdersRow(OrderDialog.NewOrder)RefreshPreview(CID)
End IfEnd Sub
End Class
01.02.03.04.
05.
06.07.08.09.10.11.12.13.14.15.16.
Public Class Form1'... Private Sub Form1_FormClosing(ByVal sender As System.Object, ByVal e As
System.Windows.Forms.FormClosingEventArgs) Handles MyBase.FormClosingDim Connection As New MySqlConnection("Server=localhost; Database=appdata; Uid=root;
Pwd=root;")Dim Adapter As New MySqlDataAdapter()Dim Builder As MySqlCommandBuilder
Try
Connection.Open()Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Customers;", Connection)Builder = New MySqlCommandBuilder(Adapter)Adapter.Update(MainDatabase.Customers)
Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Orders;", Connection)
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).
StampaIl 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:
17.18.19.20.
21.22.23.24.25.26.27.28.
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.NoThene.Cancel = True
End IfFinally
Connection.Close()End Try
End Sub
End Class
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.
Public Class Form1
'... 'Indici dei campi da stamparePrivate PrintingFields() As Int32 = {1, 2, 3, 4, 5, 7}
Private Sub PrintDoc_PrintPage(ByVal sender As System.Object, ByVal e As
System.Drawing.Printing.PrintPageEventArgs) Handles PrintDoc.PrintPage'Indice della prima riga della prima paginaStatic RowIndex As Int32 = 0'Indica se si tratta della prima paginaStatic FirstPage As Boolean = True
'Offset orizzontaleDim CellOffset As Int32 = e.MarginBounds.X'Offset verticaleDim Y As Int32 = e.MarginBounds.Y'Larghezza totale colonneDim TotalWidth As Int32 = 0
'Se è la prima pagina, stampa le intestazioniIf FirstPage Then
For Each Column As DataGridViewColumn In dgvCustomers.Columns'Stampa solo gli header dei campi contemplatiIf Array.IndexOf(PrintingFields, Column.Index) < 0 Then
Continue ForEnd If
e.Graphics.DrawString(Column.HeaderText,
dgvCustomers.ColumnHeadersDefaultCellStyle.Font, NewSolidBrush(dgvCustomers.ColumnHeadersDefaultCellStyle.ForeColor),CellOffset, Y)
CellOffset += Column.WidthTotalWidth += Column.Width
NextY += dgvCustomers.ColumnHeadersHeight
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 righeFor I As Int32 = RowIndex To dgvCustomers.RowCount - 1
Dim Row As DataGridViewRow = dgvCustomers.Rows(I)
CellOffset = e.MarginBounds.X'Ottiene lo stile delle celleDim Style As DataGridViewCellStyle'Distingue fra quelle pari e dispariIf Row.Index Mod 2 = 0 Then
Style = dgvCustomers.DefaultCellStyleElse
Style = dgvCustomers.AlternatingRowsDefaultCellStyleEnd If
'Se nessun font particolare è specificato, prende'quello del controlloIf Style.Font Is Nothing Then
Style.Font = dgvCustomers.FontEnd If'Se nessun colore particolare è specificato (di'default valore argb=(0,0,0,0)) prende quello del'controlloIf Style.ForeColor.A = 0 Then
Style.ForeColor = dgvCustomers.ForeColorEnd If
'Disegna una striscia del colore di sfondo della rigae.Graphics.FillRectangle(New SolidBrush(Style.BackColor), CellOffset, Y,
TotalWidth, dgvCustomers.RowTemplate.Height)'E la contorna con un tratto neroe.Graphics.DrawRectangle(Pens.Black, CellOffset, Y, TotalWidth,
dgvCustomers.RowTemplate.Height)
For Each Cell As DataGridViewCell In Row.Cells'Stampa solo gli attributi contemplatiIf Array.IndexOf(PrintingFields, Cell.ColumnIndex) < 0 Then
Continue ForEnd If
'Se la cella contiene un'immagime, la stampaIf Cell.ValueType Is GetType(Image) Then
Dim Img As Image = Cell.Valuee.Graphics.DrawImage(Img, CellOffset +
dgvCustomers.Columns(Cell.ColumnIndex).Width \ 2 - Cell.Value.Width \2, Y + dgvCustomers.CurrentRow.Height \ 2 - Img.Height \ 2)
ElseDim Height As Int32Dim StrVal As String
'Formatta la data in forma compattaIf 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 centrarloHeight = Cell.MeasureTextHeight(e.Graphics, StrVal, Style.Font, 500,
TextFormatFlags.Default)
'Stampa il testoe.Graphics.DrawString(StrVal, Style.Font, New SolidBrush(Style.ForeColor),
CellOffset, Y + dgvCustomers.RowTemplate.Height \ 2 - Height \ 2)End If
'Si sposta alla prossima ascissaCellOffset += dgvCustomers.Columns(Cell.ColumnIndex).Width
'Per tutte le celle tranne l'ultima, disegna una linea'verticale di separazione dalla cella successivaIf Array.IndexOf(PrintingFields, Cell.ColumnIndex) < PrintingFields.Length - 1
Risultato:
Validazione dell'inputE' possibile convalidar e l'input dell'utente o indicar e che alcuni dati sono er r onei utilizzando l'evento CellValidating o
RowValidating:
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.
Thene.Graphics.DrawLine(Pens.Black, CellOffset - 4, Y, CellOffset - 4, Y +
dgvCustomers.RowTemplate.Height)End If
Next
'Aumenta l'ordinataY += dgvCustomers.RowTemplate.Height
Next
If e.HasMorePages ThenFirstPage = False
ElseFirstPage = TrueRowIndex = 0
End IfEnd Sub
'strPrint è un sottoelemento del context menuPrivate Sub strPrint_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
Handles strPrint.ClickDim PDialog As New PrintDialog
PDialog.Document = PrintDocIf PDialog.ShowDialog = Windows.Forms.DialogResult.OK Then
PrintDoc.PrinterSettings = PDialog.PrinterSettings'Ridimensiona le colonne per far stare i testi in modo'corretto. N.B.: ho impostato la larghezza minima della'colonna Address a 190 pixeldgvCustomers.AutoResizeColumns()PrintDoc.Print()
End IfEnd Sub
End Class
01.
02.03.04.05.06.07.08.09.10.11.
Private Sub dgvCustomers_CellValidating(ByVal sender As System.Object, ByVal e AsSystem.Windows.Forms.DataGridViewCellValidatingEventArgs) HandlesdgvCustomers.CellValidatingIf e.ColumnIndex > 0 And e.ColumnIndex < 5 Then
With dgvCustomers.Item(e.ColumnIndex, e.RowIndex)If String.IsNullOrEmpty(e.FormattedValue.ToString()) Then
.ErrorText = "Il campo non dovrebbe essere lasciato vuoto"Else
.ErrorText = NothingEnd If
End WithEnd If
End Sub
D1. Il controllo WebBrowser
WebBr owser è uno dei contr olli standar d for niti dal Fr amewor 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 web br owser , 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 owser per mette di car icar e al pr opr io inter no una pagina web 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 WebBrowserDopo aver cr eato un nuovo pr ogetto Windows For ms, tr ascinate sulla super ficie del designer un nuovo contr ollo
WebBr owser . 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 war 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 web 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 war 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 owser 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 web su disco fisso)
IsWebBr owser 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)
ShowPageSetupDialog : visualizza le impostazioni pagina con una finestr a di dialogo Inter net Ex plor er
ShowPr intDialog : visualizza la finestr a di stampa di Inter net Ex plor er
ShowPr intPr eviewDialog : visualizza l'antepr ima di stampa in una finestr a Inter net Ex plor er
ShowPr oper tiesDialog : visualizza la finestr a delle pr opr ietà pagina come Inter net Ex plor er
ShowSaveAsDialog : 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 war 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.02.03.
04.05.06.07.08.09.10.11.
Public Class Form1
Private Sub txtUrl_KeyDown(ByVal sender As System.Object, ByVal e AsSystem.Windows.Forms.KeyEventArgs) Handles txtUrl.KeyDown'Quando si preme invio durante la digitazione, naviga'alla pagina indicataIf e.KeyCode = Keys.Enter Then
wbBrowser.Navigate(txtUrl.Text)'Poiché si inizia a navigare, è lecito fermare'il caricamento, quindi attiva btnCancelbtnCancel.Enabled = True
Come alter nativa a DocumentCompleted, si può utilizzar e Navigated:
Possiamo or a aggiunger e una bar r a di stato in basso per comunicar e lo stato della navigazione:
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 IfEnd Sub
Private Sub btnCancel_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
Handles btnCancel.Click'Ferma l'attività del WebBrowserwbBrowser.Stop()btnCancel.Enabled = FalsebtnRefresh.Enabled = True
End Sub
Private Sub wbBrowser_Navigating(ByVal sender As System.Object, ByVal e AsSystem.Windows.Forms.WebBrowserNavigatingEventArgs) Handles wbBrowser.Navigating'L'evento Navigating si genera prima della navigazionebtnCancel.Enabled = True
End Sub
Private Sub wbBrowser_DocumentCompleted(ByVal sender As System.Object, ByVal e AsSystem.Windows.Forms.WebBrowserDocumentCompletedEventArgs) HandleswbBrowser.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 = FalsebtnBack.Enabled = wbBrowser.CanGoBackbtnForward.Enabled = wbBrowser.CanGoForwardbtnRefresh.Enabled = True
End Sub
Private Sub btnRefresh_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)Handles btnRefresh.ClickwbBrowser.Refresh()
End Sub Private Sub btnBack_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
Handles btnBack.ClickwbBrowser.GoBack()
End Sub
Private Sub btnForward_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)Handles btnForward.ClickwbBrowser.GoForward()
End Sub
End Class
1.
2.3.4.5.6.
Private Sub wbBrowser_Navigated(ByVal sender As System.Object, ByVal e AsSystem.Windows.Forms.WebBrowserNavigatedEventArgs) Handles wbBrowser.NavigatedbtnCancel.Enabled = FalsebtnBack.Enabled = wbBrowser.CanGoBackbtnForward.Enabled = wbBrowser.CanGoForwardbtnRefresh.Enabled = True
End Sub
01.02.03.
04.05.06.07.08.09.
Public Class Form1
Private Sub txtUrl_KeyDown(ByVal sender As System.Object, ByVal e AsSystem.Windows.Forms.KeyEventArgs) Handles txtUrl.KeyDownIf e.KeyCode = Keys.Enter Then
wbBrowser.Navigate(txtUrl.Text)btnCancel.Enabled = True
End IfEnd Sub
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.
Private Sub btnCancel_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
Handles btnCancel.ClickwbBrowser.Stop()btnCancel.Enabled = FalsebtnRefresh.Enabled = True
End Sub
Private Sub wbBrowser_Navigating(ByVal sender As System.Object, ByVal e AsSystem.Windows.Forms.WebBrowserNavigatingEventArgs) Handles wbBrowser.NavigatingbtnCancel.Enabled = True'La proprietà StatusText contiene in forma leggibile'un resoconto dell'operazione che il controllo sta svolgendolblStatus.Text = wbBrowser.StatusText
End Sub
Private Sub wbBrowser_Navigated(ByVal sender As System.Object, ByVal e AsSystem.Windows.Forms.WebBrowserNavigatedEventArgs) Handles wbBrowser.NavigatedbtnCancel.Enabled = FalsebtnBack.Enabled = wbBrowser.CanGoBackbtnForward.Enabled = wbBrowser.CanGoForwardbtnRefresh.Enabled = TruelblStatus.Text = "Pagina caricata"
End Sub
Private Sub btnRefresh_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)Handles btnRefresh.ClickwbBrowser.Refresh()
End Sub
Private Sub btnBack_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)Handles btnBack.ClickwbBrowser.GoBack()
End Sub
Private Sub btnForward_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)Handles btnForward.ClickwbBrowser.GoForward()
End Sub
Private Sub wbBrowser_ProgressChanged(ByVal sender As System.Object, ByVal e AsSystem.Windows.Forms.WebBrowserProgressChangedEventArgs) HandleswbBrowser.ProgressChangedprgProgress.Value = e.CurrentProgress / e.MaximumProgress * 100lblStatus.Text = wbBrowser.StatusText
End Sub End Class
Dato che questo non vuole esser e un tutor ial su come cr ear e un br owser , 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 web in molti modi diver si, di cui WebBr owser è 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 owser 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 lassiHtmlDocument è 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 owser . 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 automaticoEcco 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:
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
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.
Class Form1
'Qui c'è il codice del capitolo precedente 'Ecco il dizionario che contiene tuttoDim LoginInfo As New Dictionary(Of String, Dictionary(Of String, String))
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles
MyBase.Load'Per semplicità, in questo esempio carichiamo dei'dati di prova al caricamento del form Dim TInfo As New Dictionary(Of String, String)With TInfo
'ID del form di login.Add("form-id", "totemlogin")'ID della textbox per l'username.Add("username-field", "lname")'ID della textbox per la password.Add("password-field", "lpassw")'Username e password.Add("username", "prova").Add("password", "prova")
End With
'Associa alla pagina il suo loginLoginInfo.Add("http://totem.altervista.org/guida/versione3/login.php", TInfo)
End Sub
Private Sub btnAction_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)Handles btnAction.Click'Quando viene premuto il pulsante, ricava il dizionario'dei dati dall'url della paginaDim Info As Dictionary(Of String, String) = LoginInfo(wbBrowser.Url.ToString())'Quindi compila i campi e invia la richiesta di loginWith wbBrowser.Document
.GetElementById(Info("username-field")).SetAttribute("value", Info("username"))
.GetElementById(Info("password-field")).SetAttribute("value", Info("password"))'InvokeMember invoca un metodo usabile da un'certo elemento. I metodi sono gli stessi che si'usano in javascript.GetElementById(Info("form-id")).InvokeMember("submit")
End WithEnd Sub
End Class
br owser , 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 amewor k 3.5 per confer mar e
l'accesso mediante una par ola detta a voce.
TrasformazioniNel 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.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.50.51.52.53.
Class Form1
'... Private Sub btnAction_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
Handles btnAction.ClickWith wbBrowser.Document
'Scorre tutte le immagini nella pagina e ad ognuna'aggiunge un nuovo gestore d'evento per l'evento OnClick.'Il metodo AttachEventHandler può essere usato'da qualsiasi HtmlElement, ed accetta come primo'parametro il nome dell'evento da gestire (vedere la'documentazione ufficiale W3C) e come secondo un'delegate che punta al sottoscrittore.For Each img As HtmlElement In .Images
.AttachEventHandler("onclick", AddressOf ImageToLink)Next
End WithEnd Sub 'Questo è il nuovo gestore d'evento. Nonostante i'parametri, sender è sempre NothingPrivate Sub ImageToLink(ByVal sender As Object, ByVal e As EventArgs)
'Ottiene un riferimento all'immagine con il metodo'GetElementFromPoint, sfruttando il fatto che questo'codice viene eseguito subito dopo un click.'MousePosition indica la posizione del mouse sullo schermo,'Me.Location determina la posizione del form sullo schermo'e wbBrowser.Location la posizione del browser sul form.'La differenza tra questi punti è la posizione'del mouse rispetto al browser. Anche se un po' grezzo,'questo metodo dovrebbe funzionare abbastanzaDim Img As HtmlElement = wbBrowser.Document.GetElementFromPoint(MousePosition -
Me.Location - wbBrowser.Location)'Crea un nuovo link mediante il metodo CreateElement'di HtmlDocumentDim Link As HtmlElement = wbBrowser.Document.CreateElement("a")
'Imposta l'attributo href dell'immagineLink.SetAttribute("href", Img.GetAttribute("src"))'Imposta il testo del linkIf Not String.IsNullOrEmpty(Img.GetAttribute("longdesc")) Then
Link.InnerText = Img.GetAttribute("longdesc")ElseIf Not String.IsNullOrEmpty(Img.GetAttribute("alt")) Then
Link.InnerText = Img.GetAttribute("alt")Else
Link.InnerText = "Immagine"End If
'Aggiunge il link prima dell'immagineImg.InsertAdjacentElement(HtmlElementInsertionOrientation.BeforeBegin, Link)'Dato che non è possibile eliminare elementi,'impone all'immagine larghezza 0Img.SetAttribute("width", "0")
54.55.
End Sub End Class
D3. Scaricare file dalla rete
Oltr e al WebBr owser , 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.
Download sincrono gestitoIl pr imo e più semplice dei suddetti modi consiste nell'utilizzar e una classe messa a disposizione dal Fr amewor 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:
DownloadData(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;
DownloadFile(ur l, path) : scar ica il file indicato dall'indir izzo ur l e lo salva nel per cor so path su disco fisso;
DownloadStr 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 passwor d, come ad esempio
si usa far e quando si utilizza il pr otocollo ftp per il tr asfer imento di file. Ad esempio:
Header s : espone una collezione degli header posti all'inizio della r ichiesta per il file. Quando un metodo di
download 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 proxy 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
query 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:
1.2.
Dim W As New Net.WebClientW.Credentials = New Net.NetworkCredential("username", "password")
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 SS = W.DownloadString("http://totem.altervista.org/guida/versione3/Capitoli.txt") 'Aggiunge una coppia nome-valore alla queryW.QueryString.Add("name", "twaveeditor")'La prossima richiesta sarà quindi equivalente a:' http://totem.altervista.org/download/details.php?name=twaveeditor
La pecca di questi metodi è che sono sincr oni, ossia bloccano il funzionamento dell'applicazione fino a quando il download
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.
Download asincrono gestitoPer 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: DownloadPr ogr essChanged, che notifica
il pr ogr esso del download, e DownloadFileCompleted (o DownloadDataCompleted o Dow nloadStr ingCompleted, a seconda
dei casi). Ecco un semplice esempio:
15.16.
'Ossia scaricherà la pagina di download di TWave Editor.'Il contenuto del file verrà salvato in BDim B() As Byte = W.DownloadData("http://totem.altervista.org/download/details.php")
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.
Class Form1'WithEvents permette di gestire gli eventi di WPrivate WithEvents W As New Net.WebClient()
Private Sub btnDownload_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
Handles btnDownload.Click'Inizia il download asincronoW.DownloadFileAsync(New Uri(txtUrl.Text), txtFile.Text)btnCancel.Enabled = TruebtnDownload.Enabled = False
End Sub
Private Sub W_DownloadProgressChanged(ByVal sender As Object, ByVal e AsNet.DownloadProgressChangedEventArgs) Handles W.DownloadProgressChanged'Il parametro e contiene alcune informazioni'sul progresso del downloadlblStatus.Text = _
String.Format("Bytes ricevuti: {0} B{3}Dimensione file: {1} B{3}Progresso:{2:N0}%", _
e.BytesReceived, e.TotalBytesToReceive, _e.ProgressPercentage, Environment.NewLine)
End Sub
Private Sub W_DownloadFileCompleted(ByVal sender As Object, ByVal e AsSystem.ComponentModel.AsyncCompletedEventArgs) Handles W.DownloadFileCompleted'e.Cancelled vale True se il download è stato annullato.'e.Error è di tipo Exception e contiene l'eccezione' generata nel caso si sia verificato un errore.If e.Cancelled Then
MessageBox.Show("Il download è stato cancellato!", Me.Text, MessageBoxButtons.OK,MessageBoxIcon.Exclamation)
ElseIf e.Error IsNot Nothing ThenMessageBox.Show("Si è verificato un errore: " & e.Error.Message, Me.Text,
MessageBoxButtons.OK, MessageBoxIcon.Exclamation)Else
MessageBox.Show("Download completato con successo!", Me.Text,MessageBoxButtons.OK, MessageBoxIcon.Information)
End IfbtnDownload.Enabled = TruebtnCancel.Enabled = False
End Sub
Private Sub btnCancel_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)Handles btnCancel.Click'Il metodo CancelAsync cancella il download asincronoW.CancelAsync()
Download sincrono/asincrono non gestitoCome ho illustr ato nei par agr afi pr ecedenti, WebClient si occupa di eseguir e molte istr uzioni r iguar do al download:
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:
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.
40.41.42.43.
btnDownload.Enabled = TruebtnCancel.Enabled = False
End Sub
End Class
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.
Public Sub DownloadFile(ByVal Address As String, ByVal Path As String)'Crea una richiesta http per l'indirizzo Address.'Address può anche contenere una query stringDim Request As Net.HttpWebRequest = Net.HttpWebRequest.Create(Address)'Invia la richieste a ottiene la rispostaDim Response As Net.HttpWebResponse = Request.GetResponse()'Ottiene da Response uno stream di rete dal quale si'potrà leggere il file richiesto.Dim Reader As IO.Stream = Response.GetResponseStream()'Crea un nuovo file in localeDim Writer As New IO.FileStream(Path, IO.FileMode.Create)'Un buffer di byte che contiene i blocchi letti'dallo stream. La lettura a blocchi è più'conveniente che trasferire in massa tutto il contenuto,'poiché altrimenti si dovrebbe usare un buffer'gigantesco (almeno le dimensioni del file)Dim Buffer(8127) As ByteDim BytesRead As Int32
'La funzione Read, vi ricordo, restituisce come risultato'il numero di bytes effettivamente letti dallo streamBytesRead = Reader.Read(Buffer, 0, Buffer.Length)Do While BytesRead > 0
Writer.Write(Buffer, 0, BytesRead)BytesRead = Reader.Read(Buffer, 0, Buffer.Length)
Loop
Reader.Close()Writer.Close()Response = NothingRequest = Nothing
End Sub
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 download. Basta r ichiamar e AddRange
pr ima dell'invio della r ichiesta:
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.
01.02.03.04.05.06.07.08.09.10.
'...'Indica al server che vogliamo iniziare la lettura'dall'offset nRequest.AddRange(n) 'oppure 'Indica al server che vogliamo iniziare la lettura dalla'posizione n, ma solo fino alla posizione qRequest.AddRange(n, q)
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 wr apper ad alcune funzioni basilar i del socket:
TcpListener e TcpClient.
ServerIl 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.
ClientLa 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 semplice scambio di messaggiPer 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.002.003.004.005.006.007.008.009.010.011.012.013.014.015.016.017.018.019.020.021.022.
Imports System.Net.SocketsImports System.Text.UTF8Encoding Public Class Form1
Private Listener As TcpListenerPrivate Client As TcpClientPrivate NetStream As NetworkStream
'Questa procedura serve per attivare o disattivare i'controlli a seconda che si sia connessi oppure no. Serve'per impedire che si tenti di inviare un messaggio quando'non si è connessi, ad esempioPrivate Sub EnableControls(ByVal Connected As Boolean)
btnConnect.Enabled = Not ConnectedbtnListen.Enabled = Not ConnectedtxtIP.Enabled = Not Connected
btnSend.Enabled = ConnectedtxtMessage.Enabled = ConnectedbtnDisconnect.Enabled = Connected
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) HandlesbtnListen.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 100msListener = New TcpListener(5000)Listener.Start()tmrConnections.Start()btnListen.Enabled = FalsebtnConnect.Enabled = FalsetxtLog.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 timertmrConnections.Stop()
'Chiede all'utente se confermare la connessioneIf 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 clientClient = Listener.AcceptTcpClient()'Ferma il listenerListener.Stop()'Ottiene il network streamNetStream = Client.GetStream()'E attiva/disattiva i controlli per quando si è connessiEnableControls(True)
ElseListener.Stop()Listener.Start()tmrConnections.Start()
End IfEnd If
End Sub
Private Sub btnConnect_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)Handles btnConnect.ClickDim IP As Net.IPAddress
'Prima esegue un controllo sull'indirizzo IP per'controllare che sia validoIf Not Net.IPAddress.TryParse(txtIP.Text, IP) Then
MessageBox.Show("IP non valido!", Me.Text, MessageBoxButtons.OK,MessageBoxIcon.Exclamation)
Exit SubEnd If
'Quindi inizializza un client e tenta la connessione'al dato IP sulla porta 5000Client = 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
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
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.
'stream e agisce sui controlli come nel codice precedenteIf Client.Connected Then
txtLog.AppendText("Tentativo di connessione riuscito!" & vbCrLf)NetStream = Client.GetStream()EnableControls(True)
ElsetxtLog.AppendText("Tentativo di connessione fallito..." & vbCrLf)
End IfEnd Sub
Private Sub tmrData_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs)
Handles tmrData.Tick'Se ci sono dati disponibiliIf Client.Available > 0 Then
'Li legge dallo streamDim Buffer(Client.Available - 1) As ByteNetStream.Read(Buffer, 0, Buffer.Length)
'Li trasforma in una stringaDim 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 questoIf Msg.StartsWith("\\\close\\\") Then
btnDisconnect_Click(Me, EventArgs.Empty)Exit Sub
End If
'Altrimenti lo accoda alla textbox grandetxtLog.AppendText("Ricevuto: ")txtLog.AppendText(Msg)txtLog.AppendText(Environment.NewLine)
End IfEnd Sub
Private Sub btnSend_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
Handles btnSend.ClickIf 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 IfEnd Sub
Private Sub btnDisconnect_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
Handles btnDisconnect.ClicktmrData.Stop()
Dim Buffer() As Byte = UTF8.GetBytes("\\\close\\\")NetStream.Write(Buffer, 0, Buffer.Length)
Client.Client.Close()Client.Close()Client = Nothing
If Listener IsNot Nothing Then
Listener.Server.Close()Listener = Nothing
End If
EnableControls(False)txtLog.AppendText("Disconnesso" & Environment.NewLine)
End SubEnd Class
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 SenderFino 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: serverHo 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.
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.
Imports System.Net.SocketsImports System.Text.ASCIIEncoding Imports System.ComponentModelPublic Class Form1
'Listener: attende una connessione sulla porta 25'FileListener: attende una connessione sulla porta 1001. Questa' ha il compito di trasferire i bytes del file
Private Listener, FileListener As TcpListener'Client: l'oggetto che ha il compito di dialogare con' il client e confermarne le operazioni'FileReceiver: l'oggetto che ha il compito di ricevere le
' informazioni contenute nel file e scriverle sulla macchina' in forma di file concretoPrivate Client, FileReceiver As TcpClient'NetStream: lo stream su cui si scrivono i dati di comunicazione
'NetFile: lo stream da cui si leggono i dati del filePrivate NetStream, NetFile As NetworkStream'Percorso su cui salvare il filePrivate FileName As String
'Dimensione del filePrivate FileSize As Int64
'I seguenti metodi semplificano le operazioni di invio e'ricezione di stringhe
'Invia un messaggio su uno stream di retePrivate Sub Send(ByVal Msg As String, ByVal Stream As NetworkStream)
'Se si può scrivere
If Stream.CanWrite Then'Converte il messaggio in binarioDim Bytes() As Byte = ASCII.GetBytes(Msg)'E lo scrive sul network stream
Stream.Write(Bytes, 0, Bytes.Length)
End IfEnd Sub
'Ottiene un messaggio dallo stream di retePrivate Function GetMessage(ByVal Stream As NetworkStream) As String
'Se si può leggereIf Stream.CanRead Then
Dim Bytes(Client.ReceiveBufferSize) As Byte
Dim Msg As String'Legge i bytes arrivatiStream.Read(Bytes, 0, Bytes.Length)'Li converte in una stringa leggibileMsg = ASCII.GetString(Bytes)'E restituisce la stringa
Return Msg.Normalize
ElseReturn Nothing
End IfEnd Function
Private Sub cmdListen_Click(ByVal sender As Object, _
ByVal e As EventArgs) Handles cmdListen.ClickIf cmdListen.Text = "Ascolta" Then
'Inizia ad ascoltare sulla porta 25Listener = New TcpListener(25)Listener.Start()'Attiva il timer per controllare le richieste di connesionetmrControlConnection.Start()
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.
'Cambia il testo e la funzione del pulsantecmdListen.Text = "Stop"
Else
'Ferma l'operazione di ascoltoListener.Stop()'Ripristina il testocmdListen.Text = "Ascolta"
End IfEnd 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 operazionitmrControlConnection.Stop()lblStatus.Text = "È stata ricevuta una richiesta"'Richiede all'utente se accettare la connessioneIf MessageBox.Show("È stata ricevuta una richiesta di connessione. Accettare?", _
Me.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Question) = _Windows.Forms.DialogResult.Yes Then
'Acceta la connessioneClient = Listener.AcceptTcpClient'Apre lo stream di rete condivisoNetStream = Client.GetStream'Termina l'ascoltoListener.Stop()'Rende il pulsante cmdListen inutilizzabile, poiché'una connessione è già stata aperta
cmdListen.Enabled = False'Inizia la ricezione di messaggitmrGetData.Start()lblStatus.Text = "Connessione riuscita!"
Else'Altrimenti si rimette in attesa per altre connessionitmrControlConnection.Start()lblStatus.Text = "In attesa di connessioni..."
End If
End IfEnd 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.AcceptTcpClientNetFile = FileReceiver.GetStream'Ferma il listenerFileListener.Stop()lblStatus.Text = "Flusso di informazioni aperto"'Attiva la ricezione di dati attraverso un background workerbgReceiveFile.RunWorkerAsync()
End If
End Sub
Private Sub tmrGetData_Tick(ByVal sender As Object, _ByVal e As EventArgs) Handles tmrGetData.TickIf Client.Connected And Client.Available Then
'Ferma il timer mentre si eseguono le operazionitmrGetData.Stop()'Legge il messaggioDim 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 pipeDim Parts() As String = Msg.Split("|")'La prima parte è "ConfirmTransfer"
'La seconda è il percorso del file sull'altro computerDim File As String = Parts(1)'La terza è la dimensione
Dim Size As Int64 = CType(Parts(2), Int64)'Ottiene solo il nome del file, senza percorsoFile = 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 globaleFileSize = Size'Richiede se accettare il trasferimentoIf 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 clientSend("OK", NetStream)'Intanto si mette in attesa sulla porta 1001 per'l'invio dei bytes del fileFileListener = New TcpListener(1001)FileListener.Start()'E attiva il timer di controllo
tmrControlFile.Start()
Else'Altrimenti, risponde di noSend("NO", NetStream)
End IfEnd If
'Riprende il controllotmrGetData.Start()
End IfEnd 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 precedenteDim Stream As New IO.FileStream(FileName, IO.FileMode.Create)'Crea un indice che indica il progressoDim Index As Int64 = 0
lblStatus.Text = "In ricezione..."Do
If FileReceiver.Available Then
'Riceve i bytes necessariDim 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 fermaIf Msg.Contains("END") Or Index >= FileSize Then
Exit Do
End If'Preleva i bytes dallo stream di rete
File Sender: c lientHo 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 owse : 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ì:
217.218.219.220.221.222.223.224.225.226.227.228.229.230.231.232.233.234.235.236.237.238.239.
NetFile.Read(Bytes, 0, 4096)'E li scrive sul file fisicoStream.Write(Bytes, 0, 4096)'Incrementa l'indice di 4096
Index += 4096'E notifica il progressobgReceiveFile.ReportProgress(Index * 100 / FileSize)
End IfLoop
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.ProgressChangedprgProgress.Value = e.ProgressPercentage
End Sub End Class
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.SocketsImports System.Text.ASCIIEncodingImports 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 filePrivate 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 retePrivate Sub Send(ByVal Msg As String, ByVal Stream As NetworkStream)
'Se si può scrivere
If Stream.CanWrite Then'Converte il messaggio in binarioDim Bytes() As Byte = ASCII.GetBytes(Msg)'E lo scrive sul network stream
Stream.Write(Bytes, 0, Bytes.Length)
End IfEnd Sub
'Ottiene un messaggio dallo stream di retePrivate Function GetMessage(ByVal Stream As NetworkStream) As String
'Se si può leggereIf Stream.CanRead Then
Dim Bytes(Client.ReceiveBufferSize) As Byte
Dim Msg As String'Legge i bytes arrivatiStream.Read(Bytes, 0, Bytes.Length)'Li converte in una stringa leggibileMsg = ASCII.GetString(Bytes)'E restituisce la stringa
Return Msg.Normalize
ElseReturn Nothing
End IfEnd 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 vuotoIf 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 trasferimentoNetStream = Client.GetStreamgrpTransfer.Enabled = TruelblStatus.Text = "Connessione riuscita!"
End If
End Sub
Private Sub cmdBrowse_Click(ByVal sender As Object, _ ByVal e As EventArgs) Handles cmdBrowse.ClickDim Open As New OpenFileDialogOpen.Filter = "Tutti i file|*.*"If Open.ShowDialog = Windows.Forms.DialogResult.OK Then
txtFile.Text = Open.FileName
End IfEnd 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 fileDim 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 arrivatitmrGetData.Start()'Disattiva il pulsante per evitare più azioni'contemporanee indesideratecmdSend.Enabled = FalselblStatus.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.TickIf Client.Connected AndAlso Client.Available Then
'Ferma il timer mentre si eseguono le operazionitmrGetData.Stop()'Legge il messaggioDim 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 fileFileSender = New TcpClientFileSender.Connect(IP, 1001)If FileSender.Connected Then
'Ottiene lo stream associato a questa operaizoneNetFile = FileSender.GetStream'E inizia la trasmissione dei datibgSendFile.RunWorkerAsync(txtFile.Text)
End IfElseIf 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 datitmrGetData.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 precedenteDim 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 StreamReaderDim 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 connessioneDim Bytes(4095) As Byte
'Se il file è più grande di 4KiB, lo divide'in blocchi di dati da 4096 bytesIf Size > 4096 Then
For Block As Int64 = 0 To Size Step 4096
'Se i bytes rimanenti sono più di 4096,
'ne legge un blocco interoIf 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.
Reader.Read(Bytes, 0, Size - Block)End If'Scrive i dati prelevati sullo stream di rete,'inviandoli così al serverNetFile.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 confusioneThreading.Thread.Sleep(30)
Next
Else'Se il file è minore di 4KiB, lo invia tutto'direttamente dal serverReader.Read(Bytes, 0, Size)NetFile.Write(Bytes, 0, Size)
End If
Reader.Close()
'Percentuale massima: lavoro terminatobgSendFile.ReportProgress(100)Threading.Thread.Sleep(100)'Comunica la fine delle operazioniNetFile.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 SubEnd Class
E1. Il Filesystem - Gestire files e cartelle
Il .NET Fr amewor 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.Directory : 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.DirectoryIndo : 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 iveInfo : 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.txtGetFileName(S) = file.txtGetFileNameWithoutExtension(S) = fileGetDirectoryName = 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 BrowserQuesto 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.002.003.004.005.006.007.008.009.010.011.012.013.014.015.016.017.018.019.
Class Form1'Tiene traccia del drive e della cartella correntePrivate CurrentDrive, CurrentDir As String
'Questa funzione permetterà di formattare le date'in poco spazioPrivate Function FormatDate(ByVal D As Date) As String
'Ad esempio'"lunedì 26 novembre 2007, ore 19:97"Return D.ToString("dddd dd MMMM yyyy, ore HH:mm")
End Function
Private Sub lstDrive_SelectedIndexChanged(ByVal sender As Object, _ByVal e As EventArgs) Handles lstDrive.SelectedIndexChangedIf lstDrive.SelectedIndex = -1 Then
Exit SubEnd If
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 è prontaIf 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 selezionatoCurrentDrive = lstDrive.SelectedItem'Quando si cambia driver, si resetta la lista delle'cartelle quindi la cartella iniziale è uguale'al driveCurrentDir = CurrentDrive.Clone'Pulisce la lista delle cartellelstDir.Items.Clear()'Quando si seleziona un drive, carica le cartelle'al suo internoFor Each Dir As String In _
IO.Directory.GetDirectories(lstDrive.SelectedItem)lstDir.Items.Add(Dir)
NextEnd Sub
Private Sub lstDir_SelectedIndexChanged(ByVal sender As Object, _
ByVal e As EventArgs) Handles lstDir.SelectedIndexChanged'Solo se è eramente selezionato un elemento procedeIf lstDir.SelectedIndex = -1 Then
Exit SubEnd If
'Memorizza la cartella selezionataCurrentDir = IO.Path.Combine(CurrentDir, lstDir.SelectedItem)
'Pulisce la lista delle cartelle e dei fileslstDir.Items.Clear()lstFiles.Items.Clear()
'Carica le sottocartelle, solo con il nomeFor Each SubDir As String In IO.Directory.GetDirectories(CurrentDir)
'Si può fare anche con le cartelle, poichè le funzione'considera solamente il formato della stringalstDir.Items.Add(IO.Path.GetFileName(SubDir))
Next'Carica i files interni alla cartella, solo con il nomeFor 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.SelectedIndexChangedIf lstFiles.SelectedIndex = -1 Then
Exit SubEnd If
'Ottiene le informazioni relative al file'Path.Combine combina due directory o un file e'una directory'per ottenere un percorso completoDim 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
092.093.094.095.096.097.098.099.100.101.102.103.104.105.106.107.
Try'Si reca alla directory precedente nell'ordine'gerarchicoCurrentDir = IO.Directory.GetParent(CurrentDir).FullName
lstDir.Items.Clear()For Each SubDir As String In IO.Directory.GetDirectories(CurrentDir)
lstDir.Items.Add(IO.Path.GetFileName(SubDir))Next
Catch Ex As Exception'Se le directory sono le prime in ordine, si'genera un erroreMessageBox.Show("Non è possibile risalire più indietro!", _"File Browser", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
End TryEnd Sub
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 spywar 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 Windows 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.
Microsoft.Win32.RegistryKeyPer 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:
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
1.2.3.4.5.6.
'Da notare che il tipo non espone costruttoriDim RegKey As RegistryKey'Apre la chiave HKEY_CLASSES_ROOTRegKey = Registry.ClassesRoot'Apre la sottochiave .zipRegKey = RegKey.OpenSubKey(".zip")
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.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.
Imports Microsoft.Win32 Module Module1
Sub Main()Dim RegKey As RegistryKeyDim Extension As String
Console.Write("Inserire un'estesione valida: ")Extension = Console.ReadLine()
RegKey = Registry.ClassesRoot.OpenSubKey(Extension)If RegKey Is Nothing Then
Console.WriteLine("Questa estensione non è associata a nessuna applicazione!")Console.ReadKey()Exit Sub
End If
Dim AppKey As String = RegKey.GetValue("")
RegKey = Registry.ClassesRoot.OpenSubKey(AppKey)
If RegKey Is Nothing ThenConsole.WriteLine("Non è possibile risalire all'applicazione. Dati mancanti.")Console.ReadKey()Exit Sub
End If
RegKey = RegKey.OpenSubKey("shell\open\command")
If RegKey Is Nothing ThenConsole.WriteLine("Non è possibile aprire il file direttamente con
un'applicazione.")Console.ReadKey()Exit Sub
End If
Console.WriteLine("Applicazione usata:")Console.WriteLine(RegKey.GetValue(""))
Console.ReadKey()
End Sub 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 ProcessLa 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
MainWindowHandle : handle della finestr a pr incipale. Si tr atta dello stesso handle ottenuto con
EnumDesktopWindow s nel capitolo pr ecedente
MainWindowTitle : titolo della finestr a pr incipale. Si tr atta dello stesso titolo ottenuto con GetWindowTex 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), BelowNor 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 processi in esecuzioneLa 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. Ad esempio Pr ocess.GetPr ocessesByName("ex plor er ") r estituisce un solo pr ocesso,
"ex plor er .ex e", che costituisce il pr ogr amma pr incipale dell'inter faccia di w indows. In ultimo,
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.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.50.51.52.53.54.55.56.57.58.59.60.61.62.
Module Module1'Ottiene tutte le informazioni possibili su un moduloPublic Sub ScanModule(ByVal M As ProcessModule)
Console.WriteLine(" Nome modulo: {0}", M.ModuleName)Console.WriteLine(" Handle: {0:X8}", M.BaseAddress.ToInt32)Console.WriteLine(" Handle del metodo principale: {0:X8}", _
M.EntryPointAddress.ToInt32)Console.WriteLine(" Memoria occupata: {0:N0} bytes", _
M.ModuleMemorySize)Console.WriteLine(" Informazioni versione del programma:")With M.FileVersionInfo
Console.WriteLine(" Nome file: {0}", .FileName)Console.WriteLine(" Versione file: {0}", .FileVersion)Console.WriteLine(" Descrizione file: {0}", _
.FileDescription)Console.WriteLine(" Versione prodotto: {0}", _
.ProductVersion)Dim Rel As String = "Nessuna"If .IsDebug Then
Rel = "Debug"ElseIf .IsPatched Then
Rel = "Patch"ElseIf .IsPreRelease Then
Rel = "Beta"ElseIf .IsPrivateBuild Then
Rel = "Release privata"ElseIf .IsSpecialBuild Then
Rel = "Release speciale"End IfConsole.WriteLine(" Caratteristiche: {0}", Rel)Console.WriteLine(" Copyright: {0}", .LegalCopyright)Console.WriteLine(" Trademark: {0}", .LegalTrademarks)Console.WriteLine(" Commenti: {0}", .Comments)Console.WriteLine(" Nome compagnia: {0}", .CompanyName)
End WithEnd Sub
'Formatta un valore timespanPublic Function FormatTime(ByVal T As TimeSpan) As String
Return String.Format("{0}h {1}m {2}s", T.Hours, T.Minutes, T.Seconds)End Function
'Ottiene tutte le informazioni possibili su un processoPublic Sub ScanProcess(ByVal P As Process)
Console.WriteLine("Nome processo: " & P.ProcessName)Console.WriteLine(" Handle: {0:X8}", P.Handle.ToInt32)Console.WriteLine(" Handles usati: {0}", P.HandleCount)Console.WriteLine(" Id: {0}", P.Id)Console.WriteLine(" Nome macchina: {0}", P.MachineName)Console.WriteLine(" Moduli:")For Each M As ProcessModule In P.Modules
Console.WriteLine()ScanModule(M)
NextConsole.WriteLine()Console.WriteLine(" Handle finestra principale: {0:X8}", _
P.MainWindowHandle.ToInt32)Console.WriteLine(" Titolo finestra principale: {0}", _
P.MainWindowTitle)Console.WriteLine(" Threads: {0}", P.Threads.Count)Console.WriteLine(" Tempo di esecuzione su OS: {0}", _
Avviare nuovi processiPer 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:
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:
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.
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 StringDim Processes() As Process
'Fa inserire il nome del processoConsole.WriteLine("Inserire il nome di un processo:")Name = Console.ReadLine
'Inizializza la collezioneProcesses = Process.GetProcessesByName(Name)'Se l'array è vuoto, non c'è nessun processo'aperto con quel nomeIf Processes.Length = 0 Then
Console.WriteLine("Non esiste alcun processo con questo nome!")Else
'Altrimenti enumera tutti i processi apertiFor Each P As Process In Processes
Console.WriteLine()ScanProcess(P)
NextEnd If
Console.ReadKey()
End SubEnd Module
1. Dim P As New Process
01.02.03.04.05.06.07.08.09.10.
Module Module1Sub Main()
Console.WriteLine("Variabili d'ambiente:")Console.WriteLine()
For Each Name As String In Environment.GetEnvironmentVariables.Keys
Console.WriteLine("{0} = {1}", Name, _Environment.GetEnvironmentVariable(Name))
Next
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 / Passwor d : se il pr ocesso deve esser e avviato in un cer to account, potr ebber o esser e r ichieste
delle cr edenziali. Passwor 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
WindowStyle : 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:
Questo codice equivale ad apr ir e il pr ompt dei comandi di w indows e scr iver e:
11.12.13.
Console.ReadKey()
End SubEnd Module
1.2.3.4.5.6.7.8.
Dim P As New ProcessWith P.StartInfo
.FileName = "C:\programma.exe"
.Arguments = "-t"
.WorkingDirectory = "C:\cartella"
.WindowStyle = ProcessWindowStyle.MaximizedEnd WithP.Start()
1.2.
CD "C:\cartella"
La seconda possibilità consiste nell'usar e la ver sione statica di Star t:
Essa accetta anche altr i par ametr i, che per mettono di impostar e i dati come si far ebbe con Star tInfo.
C:\programma.exe -t
1. Process.Start("nome file")
E4. Multithreading - Parte I
I thr ead sono le ver e unità dinamiche di esecuzione: il computer assegna, infatti, il tempo macchina (noto anche con il
nome di tempo di CPU o timeslice) 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 multitasking preem ptive, 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 ior 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 ThreadTutti 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.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.17.18.19.20.21.
Module Module1'Il metodo da far eseguire al Thread:Sub WriteNumbers()
'Scrive 100 volte il numero 0 sullo schermoFor I As Byte = 1 To 100
Console.Write("0")'Aspetta 0.1 secondi prima di continuare. La classe'Thread espone anche metodi statici come questo, che'vengono eseguiti dal thread chiamante, in questo'caso quello che eseguirà questa proceduraThreading.Thread.Sleep(100)
NextEnd Sub
Sub Main()
'Un nuovo threadDim T As New Threading.Thread(AddressOf WriteNumbers)
'Fa partire il thread'Una volta avviato, il programma passa alle istruzioni
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 asincroni: un esempio è il metodo
WebClient.DownloadFileAsync (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:
22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.
'successive, poichè, come già detto, il thread è in grado'di gestirsi da soloT.Start()
'A prova di ciò, esegue questa routine nel thread'principale, ossia in Sub Main:For I As Byte = 1 To 100
Console.Write("1")Threading.Thread.Sleep(100)
Next 'Curiosità -'Se a Thread.Sleep viene passato il valore 0, il thread'associato cederà il proprio tempo di CPU al'thread successivo, mentre il valore -1 indica di attendere'all'infinito (o almento finchè non verrà abortito)
'Cosa appare alla fine?Console.ReadKey()
End SubEnd Module
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.
Module Module2'Il metodo da far eseguire al Thread:Sub WriteNumbers(ByVal Data As Object)
'Scrive Times volte il numero Number sullo schermoFor I As Byte = 1 To Data.Times
Console.Write(Data.Number)Threading.Thread.Sleep(100)
NextEnd Sub
Sub Main()
'Un nuovo threadDim T As New Threading.Thread(AddressOf WriteNumbers)
'Fa partire il thread'Questo overload si Start accetta un parametro Object, che,'prima dell'avvio verrà passato come argomento'della procedura delegate dichiarata nel costruttore,'in questo caso WriteNumbersT.Start(New ThreadData(8, 120))
For I As Byte = 1 To 100
Console.Write("1")Threading.Thread.Sleep(100)
Next
Console.ReadKey()End Sub
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 point, 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 itare
istr uzioni come queste:
Mentr e codici simili a questo sono del tutto cor r etti:
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
1.2.3.
Thread.Join()'OppureThread.CurrentThread.Join
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 chiamataT.Join()
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, BelowNor mal (infer ior e alla nor ma), AboveNor mal (super ior e alla nor ma), Highest (massimo) e Lowest
(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 datiDi 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 thread-relative, 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.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.
Module Module3Public Class StaticVar
'La variabile è accessibile da ogni membro e da ogni'istanza della classe, ma assume valori differenti a'seconda che sia eseguita in un thread piuttosto che'in un altro'Una dimostrazione? continuate a leggere<ThreadStatic()> _Public Shared Value As Int32
End Class
'Aumenta la variabileSub Test(ByVal Increment As Object)
'StaticVar.Value è statica, perciò ogni thread la dovrebbe'vedere con lo stesso valore, ma in questo caso ciò non'accade a causa dell'attribut ThreadStaticFor I As Byte = 1 To 10
Console.WriteLine("Thread {0} -> Value = {1}", _Thread.CurrentThread.ManagedThreadId, StaticVar.Value)Thread.Sleep(100)StaticVar.Value += CInt(Increment)
NextEnd Sub
Sub Main()
Dim T(2) As Thread
'Inizia 3 thread diversi con un diverso incrementoFor I As Byte = 0 To 2
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 amewor 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.
31.32.33.34.35.36.
T(I) = New Thread(AddressOf Test)T(I).Start(I + 1)
Next
Console.ReadKey()End Sub
End Module
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:
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 sincronizzare 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 è:
L'og g etto di lock 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:
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
1.2.3.4.
If Str = "Ciao" ThenI += 1Str = Nothing
End If
1.2.3.
SyncLock [Oggetto di lock]'Istruzioni sincronizzate
End SyncLock
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 sincronizzatoSyncLock LockObject
If Str = "Ciao" ThenI += 1
End IfEnd SyncLock
non si può saltar vi all'inter no con l'utilizzo di GoTo (vedi capitolo r elativo).
Altri metodi di sincronizzazioneSe 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:
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:
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 .
1.2.3.4.5.6.
<System.Runtime.Remoting.Contexts.Synchronization()> _Public Class AnObject
Inherits ContextBoundObject'Altro dettaglio: l'oggetto deve ereditare da ContextBoundObject'...
End Class
01.02.03.04.05.06.07.08.09.10.11.12.
Private Shared LockObject As New Object()'...Try
'Entra nel codice sincronizzatoMonitor.Enter(LockObject)'...
Catch Ex As Exception'Cattura eccezioni se ce ne sono
Finally'Ma rilascia sempre il lockMonitor.Exit()
End Try
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 decrementato di 1, fino al r aggiungimento del valor e di default; ogni
volta che si r ilascia il lock, esso viene incrementato 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 asincroniAltr 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:
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 non è 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:
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 precedentiPublic Delegate Function GetFileRecursive(ByVal Dir As String, _
ByVal Pattern As String) As List(Of String)
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.
Module Module1Public Delegate Function GetFileRecursive(ByVal Dir As String, _
ByVal Pattern As String) As List(Of String)
Public Function FindFiles(ByVal Dir As String, _ByVal Pattern As String) As List(Of String)Dim Result As New List(Of String)
'Aggiunge in un solo colpo tutti i files trovati con'GetFilesResult.AddRange(IO.Directory.GetFiles(Dir, Pattern))'Analizza le altre directoryFor Each SubDir As String In IO.Directory.GetDirectories(Dir)
Result.AddRange(FindFiles(SubDir, Pattern))Next
Return Result
End Function
Sub Main()'Nuovo oggetto di tipo delegate GetFileRecursiveDim Find As New GetFileRecursive(AddressOf FindFiles)'Con questo oggetto, monitoreremo lo stato del metodo, per'sapere se è stata completato o se è ancora'in esecuzione.'Si cercano tutti i files *.dll in una cartella di sistemaDim AsyncRes As IAsyncResult = _
Find.BeginInvoke("C:\WINDOWS\system32", "*.dll", _
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:
30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.
Nothing, Nothing)'Risultato della ricercaDim Files As List(Of String)
Console.WriteLine("Ricerca di tutti i files *.dll in System32")'Finchè non si è completato, scrive a schermo'"Ricerca in corso..."Do Until AsyncRes.IsCompleted
Console.WriteLine("Ricerca in corso...")Thread.Sleep(2000)
Loop
'Ottiene il risultatoFiles = Find.EndInvoke(AsyncRes)'Usa il metodo ForEach di Array per eseguire una stessa'operazione per ogni elemento di un array. Dato che'Files è una lista tipizzata, la converte in array'di stringhe, quindi richiama su ogni elemento il metodo'Console.WriteLine per scriverlo a schermoArray.ForEach(Files.ToArray, AddressOf Console.WriteLine)
Console.ReadKey()
End SubEnd Module
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.
Module Module1Public Delegate Function GetFileRecursive(ByVal Dir As String, _
ByVal Pattern As String) As List(Of String)
Public Function FindFiles(ByVal Dir As String, _ByVal Pattern As String) As List(Of String)Dim Result As New List(Of String)
Result.AddRange(IO.Directory.GetFiles(Dir, Pattern))For Each SubDir As String In IO.Directory.GetDirectories(Dir)
Result.AddRange(FindFiles(SubDir, Pattern))Next
Return Result
End Function
Public Sub DisplayFiles(ByVal AsyncRes As IAsyncResult)Dim Files As List(Of String) = Find.EndInvoke(AsyncRes)Array.ForEach(Files.ToArray, AddressOf Console.WriteLine)
End Sub
'Nuovo oggetto di tipo delegate GetFileRecursive'Notare che è dichiarato come variabile globale di modulo per essere'accessibile anche alla procedura callbackDim Find As New GetFileRecursive(AddressOf FindFiles)
Sub Main()
'Il terzo argomento è l'indirizzo del metodo callbackDim AsyncRes As IAsyncResult = _
Find.BeginInvoke("C:\WINDOWS\system32", _"*.dll", AddressOf DisplayFiles, Nothing)
Console.WriteLine("Ricerca di tutti i files *.dll in System32")
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.
35.36.37.38.39.40.41.
Do Until AsyncRes.IsCompletedConsole.WriteLine("Ricerca in corso...")Thread.Sleep(2000)
Loop
Console.ReadKey()End Sub
End Module
E6. BackgroundWorker e FileSystemWatcher
BackgroundWorkerDopo 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 Windows 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 bgwScan.
E 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.ComponentModel'In System.ComponentModelClass Form1
Private Sub txtDir_Click(ByVal sender As Object, _ByVal e As EventArgs) Handles txtDir.ClickDim Open As New FolderBrowserDialogOpen.Description = "Scegliere la cartella da analizzare:"If Open.ShowDialog = Windows.Forms.DialogResult.OK Then
txtDir.Text = Open.SelectedPathEnd If
End Sub
Private Sub cmdAnalyze_Click(ByVal sender As Object, _ByVal e As EventArgs) Handles cmdAnalyze.ClickIf cmdAnalyze.Text = "Analizza" Then
'Controlla che la cartella esistaIf Not IO.Directory.Exists(txtDir.Text) Then
MessageBox.Show("Cartella inesistente!", "Sizing", _MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
End If
'Fa partire il BackgroundWorker, passandogli come unico'argomento il percorso della cartella da analizzarebgwScan.RunWorkerAsync(txtDir.Text)'Modifica il testo del pulsante, così da potergli'assegnare anche un altro compitocmdAnalyze.Text = "Ferma"
Else'Il testo non è "Analizza". Deve per forza essere'"Ferma", quindi termina l'operazione forzatamentebgwScan.CancelAsync()cmdAnalyze.Text = "Analizza"
End IfEnd Sub
Private Sub bgwScan_DoWork(ByVal sender As Object, _
ByVal e As DoWorkEventArgs) Handles bgwScan.DoWork'Ottiene tutti i files presenti nella cartella:'- e.Argument ottiene lo stesso valore passato a' RunWorkerAsync: in questo caso contiene una stringa'- il pattern *.* specifica di cercare files di ogni' estensione'- l'ultimo argomento comunica di eseguire una ricerca' ricorsiva analizzando anche tutte le sottocartelleDim Files() As String = _
IO.Directory.GetFiles(e.Argument, "*.*", _IO.SearchOption.AllDirectories)
'Dimensione totaleDim Size As Double = 0'Files analizzatiDim Index As Int32 = 0
'Calcola la dimensiona totale della cartella sommando tutte'le dimensioni parzialiFor Each File As String In Files
'FileLen è una funzione di VB6, ma il VB.NET'implicherebbe di creare un nuovo oggetto FileInfo e'quindi richiamarne la proprietà Length. In'questo modo è molto più comodo, anche'se non proprio conforme alle direttive .NETSize += FileLen(File)
FileSystemWatcherL'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 war 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:
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.
Index += 1'Riporta la percentuale e genera un evento'ProgressChangedbgwScan.ReportProgress(Index * 100 / Files.Length)'Controlla se ci sono richieste di cancellazione.'Se ce ne sono, termina qui la proceduraIf bgwScan.CancellationPending Then
e.Cancel = TrueExit Sub
End IfNext
'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 complessivae.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 barraprgProgress.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 DoubleDim Values() As Double = e.ResultlblInfo.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 pulsantecmdAnalyze.Text = "Analizza"
End SubEnd Class
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 Windows, 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 librerieUna 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:
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:
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 keywor 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).
1.2.3.4.
<System.Runtime.InteropServices.DllImport("NomeLibreria.dll")> _Public Sub/Function [Nome]([Parametri]) End Sub/Function
1. Declare Auto Sub [Nome] Lib "NomeLibreria.dll" Alias "VeroNome" ([Parametri])
001.002.003.004.005.006.007.008.009.010.011.012.013.014.015.
Imports System.Runtime.InteropServices Module Module1
'Le funzioni che risiedono nelle librerie di sistema lavorano'a basso livello, come già detto, perciò i tipi'più frequentemente incontrati sono IntPtr (puntatore'intero) e Int32 (intero a 32 bit). Nonostante ciò, esse'possono anche richiedere tipi di dato molto più complessi,'come in questo caso. La funzione che useremo necessita di'un delegate come parametro.Public Delegate Function EnumCallback(ByVal Handle As IntPtr, _
ByVal lParam As Int32) As Boolean
'La funzione EnumDesktopWindows è definita nella libreria
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 Int32End Function
Public Function GetWindowTitle(ByVal Handle As IntPtr)
'Crea un nuovo string builder, con una capacità'di 255 caratteriDim Builder As New System.Text.StringBuilder(255)'Richiama la funzione di sistema per ottenere il'titolo della finestraGetWindowText(Handle, Builder, Builder.Capacity)'Dopo la chiamata a GetWindowText, Builder conterrà'il titolo: lo restituisce alla funzioneReturn Builder.ToString
End Function
Public Function FoundWindow(ByVal Handle As IntPtr, _ByVal lParam As Int32) As Boolean'Ottiene il titolo della finestraDim Title As String = GetWindowTitle(Handle)'Scrive a schermo le informazioni sulla finestraConsole.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 stessoReturn True
End Function
'Enumera tutte le finestraPublic Sub EnumerateWindows()
'Inizializza il metodo callbackDim 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 è
Il meccanismo di fondo non è difficile. Quando si r ichiama la funzione EnumDesktopWindows, 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 indows 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).
DocumentazioneIl 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 PInvoke.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.
088.089.090.091.092.093.094.095.096.097.098.099.100.101.102.103.104.105.106.107.108.109.
'quello correntemente aperto. Il secondo parametro'fornisce l'indirizzo del metodo callback, che verrà'chiamato ogni volta che sia stata trovata una nuova finestraSuccess = EnumDesktopWindows(IntPtr.Zero, Callback, 0)
'Se la funzione non ha successo, restituisce un erroreIf 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 SubEnd Module
E8. La clase Marshal e i puntatori
I puntatoriI 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 non è possibile. Esiste solo IntPtr , che r appr esenta un gener ico puntator e.
La c lasse MarshalIl marshalling (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 non pr ovveder à al r ilascio della memor ia, poiché esso oper a solo in
ambiente managed.
Ecco un semplice esempio:
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.
Imports System.Runtime.InteropServices Module Module1
Sub Main()'Un nuovo Guid. Il Guid è un tipo di identificativo'usato in gran quantità dal Framework.'È un tipo strutturato semplice del namespace'System, perciò l'ho scelto come esempioDim G As Guid = Guid.NewGuid()'Calcola la dimensione in bytes di GDim GuidSize As Int32 = Marshal.SizeOf(G)'Un puntatoreDim Pointer As IntPtr
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.
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.
'La funzione AllocHGlobal (dove H sta per Handle) alloca'un certo numero di bytes, passato come parametro, nella'memoria di lavoro e restituisce un puntatore'all'area allocata. In questo caso allochiamo un'numero di bytes pari alla dimensione di G:Pointer = Marshal.AllocHGlobal(GuidSize)'Copia la struttura G nell'area di memoria puntata'da Pointer, eventualmente eliminando una vecchia'struttura se esiste (True)Marshal.StructureToPtr(G, Pointer, True)
'Ora leggiamo un byte alla volta dall'area di memoria'allocata, ed avremo la rappresentazione binaria'della variabile G.'La funzione ReadByte legge e restituisce un byte di'informazione all'indirizzo puntato da Pointer. Come'secondo parametro accetta un intero che indica l'offset'di cui spostarsi rispetto all'indirizzo baseFor I As Int32 = 0 To GuidSize - 1
Console.Write("{0:X2} ", Marshal.ReadByte(Pointer, I))Next
'Libera la memoria puntata da Pointer. Questa funzione'è un po' più sofisticata, poiché'non solo libera la memoria strettamente indicata da'Pointer, ma elimina anche tutti i sottoriferimenti'contenuti nella struttura. Ad esempio, se la struttura'contenesse una stringa (che, come sappiamo è un'tipo reference), la rappresentazione binaria'indicherebbe solo un intero al posto della stringa,'ossia un puntatore all'oggetto stringa che risiede'in un'altra parte della memoria. DestroyStructure'elimina anche riferimenti di questo tipo, assicurando'che non rimanga alcuna area di memoria inutilizzataMarshal.DestroyStructure(Pointer, GetType(Guid))
Console.ReadKey()
End Sub
End Module
F1. Magie con le stringhe
Di tutti i tipi disponibili nel Fr amewor 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.
System.StringIl 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 nuovo
oggetto str inga e si passa il puntator e ad esso alla var iabile in assegnazione:
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 nuovo 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
1.2.3.4.
'Oggetto stringa inizialmente uguale a NothingDim S As String'Crea un nuovo oggetto stringa "Ciao" e lo assegna a SS = "Ciao"
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
ToLower : 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 funzioni 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:
Mentr e questo otter r à il r isultato:
Nei fr ammenti di codice in cui si gener ano str inghe, tuttavia, è molto meglio utilizzar e uno Str ingBuilder .
System.Text.StringBuilderQuesto 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
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 assegnazioneS.Remove(0, 1)Console.WriteLine(S)'> Ciao
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 SS = S.Remove(0, 1)Console.WriteLine(S)'> iao
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:
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.
System.Security.SecureStringQuando si memor izzano passwor 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 passwor 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.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.
Module Module1Sub Main()
Dim T As New StopwatchDim S As String = ""Dim B As New StringBuilder
'Cronometra quando si impiega a concatenare 100'000'caratteri con l'operatore &T.Start()For I As Int32 = 1 To 100000
S &= "c"NextT.Stop()Console.WriteLine("Operatore & : {0}ms", T.ElapsedMilliseconds)
'Cronometra l'operazione con StringBuilderT.Reset()T.Start()For I As Int32 = 1 To 100000
B.Append("c")NextT.Stop()Console.WriteLine("StringBuiler : {0}ms", T.ElapsedMilliseconds)
Console.ReadKey()
End SubEnd Module
01.02.03.04.05.06.07.08.
Dim Password As New SecureString Private Sub TextBox1_KeyPress(ByVal sender As Object, _
ByVal e As KeyPressEventArgs) Handles TextBox1.ClickSelect Case Asc(e.KeyChar)
Case 8'Il carattere 8 corrisponde al backspace: cancella
Un'alter nativa a questo metodo non è data dall'uso della pr opr ietà Passwor 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 passwor 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:
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
Windows. 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.
09.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.
'l'ultimo carattereIf 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 controlloCase Is >= 32
'Se il cursore non si trova alla fine della stringa,'inserisce il carattere all'indice datoIf 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 SelectEnd Sub
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 stringheDim Ptr As IntPtr = Marshal.SecureStringToBSTR(Password)'Dereferenzia il puntatore e ottiene la password veraDim 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 modoMarshal.ZeroFreeBSTR(Ptr)
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...
Descrizione del linguaggio Regular ExpressionLe 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:
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:
Caratter 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 caratter 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 -)
Asserzioni atomiche di ampiezza zero : 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)
Qualificator 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
1. [aeiou]
possibili; quelli del secondo invece quanti meno possibili. Esempio: \w * (zer o o più letter e vicine)
Costruttor i di rag g ruppamento : 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+)
Sostituzioni : 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
Costruttor i di r ifer imento all'indietro : 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)
Costruttor i di alternanza : 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:
Caratter 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 caratter 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
Asserzioni atomiche di ampiezza zero
^ : 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
Qualificator 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
Costruttor i di rag g ruppamento
(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
Sostituzioni
$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)
Costruttor i di r ifer imento all'indietro
\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"
Costruttor i di alternanza
| : 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 RegexLa 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 Match e MatchCollectionUn 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 praticoEcco un esempio:
E un esempio più complesso:
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.17.18.19.20.21.22.
Module Module1Sub Main()
Dim Text As String = _"Questo è un testo intervallato da alcuni spazi e " & vbCrLf & _"un a capo. Inoltre, viene supportata anche la punteggiatura."'Questa espressione ricerca tutti gli insiemi di almeno un'carattere separati dal resto del testo da spazi bianchi o'segni di punteggiatura.'Ergo: cerca tutte le singole paroleDim R As New Regex("\b\w+\b")Dim Matches As MatchCollection = R.Matches(Text)
For Each M As Match In Matches
'chr(34) rappresenta il carattere 34 della tabella ASCII,'ossia le virgoletteConsole.WriteLine("All'indice {0}, la sottostringa {1}{2}{1}", _
M.Index, Chr(34), M.Value)Next
Console.ReadKey()
End SubEnd Module
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.
Module Module2Sub Main()
Dim Text As String = String.Format( _"Sub Prova(){0}" & _" Dim Int As Int32 = 4{0}" & _" Dim Str As String = {1}Ciao {1}{0}" & _" For I As Int32 = Int To 48{0}" & _" Dim Str2 As String = Str & I{0}" & _" Console.WriteLine(Str2){0}" & _" Next{0}" & _"End Sub", vbCrLf, Chr(34))
'Questa espressione ricerca tutte le dichiarazioni di'variabili nel codice sopraDim R As New Regex( _"\s*Dim\s+(?<Name>\w+)\s+As\s+(?<Type>\w+)(\s+=\s+(?<Value>[\w"" ]+))?", _RegexOptions.Multiline)'Ecco la spiegazione di ogni parte del codice:'\s* : le dichiarazioni possono essere a inizio riga o precedute' da tabulazioni o spazi. Perciò si deve usare *, che' indica zero o più ripetizioni''Dim : ovviamente deve essere presenta la keyword Dim''(?<Name>\w+) : dopo Dim viene il nome della variabile,' rappresentato con \w+, ossia almeno un carattere o' underscore. Questo gruppo è chiamato Name, cosicchè' lo potremo riprendere in seguito''As : la clausola As, separata da almeno uno spazio (\s+)' del nome e dal tipo della variabile''(?<Type>\w+) : come Name''(...)? : tutto quello che viene ora è posto in una coppia di' parentesi tonde per poter usare il qualificatore ?. Quindi' tutta questa espressione può apparire 0 o una volta,' ossia è opzionale. Si tratta dell'inizializzazione' della variabile in-line'
42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57.58.59.60.61.62.63.64.65.
'\s+=\s+ : un segno uguale, separato da spazi dal resto''(?<Value>[\w" ]+) : il valore della variabile può' contenere lettere, underscore, spazi bianchi singoli' o virgolette.Nell'esempio ci sono due virgolette' poichè ci si trova in una stringa e una' sola sarebbe interpretata come fine della stringa.' Due di seguito vengono lette invece come una' virgoletta nel testoDim Matches As MatchCollection
Matches = R.Matches(Text)
For Each M As Match In Matches
Console.Write("- Nome: {1}{0} Tipo: {2}{0}", _vbCrLf, M.Groups("Name").Value, M.Groups("Type").Value)
If M.Groups("Value").Success Then
Console.WriteLine(" Valore: {0}", M.Groups("Value").Value)End If
Next
Console.ReadKey()End Sub
End Module
F3. Espressioni regolari in azione
Ricerca di paroleLa 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 keywor ds usate per l'indicizzazione dei motor i di r icer ca:
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.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.
Module Module1Sub Main()
Dim Text As String = IO.File.ReadAllText("74.php")
'Cerca la definizione del tag, ottenendo l'elenco delle'paroleDim Keywords As New Regex("\<meta name=""keywords"" content='(?<Keywords>[\w, ]+)'\>",
RegexOptions.Multiline)Dim Match As MatchDim Matches As MatchCollection
Match = Keywords.Match(Text)
'Se la ricerca ha avuto successo, scandisce ogni parolaIf Match.Success Then
Dim Word As New Regex("\b\w+\b")Dim Index As Int32 = 1'Dal gruppo Keywords, preleva ogni singola parolaMatches = Word.Matches(Match.Groups("Keywords").Value)Console.WriteLine("Parole chiave:")For Each M As Match In Matches
'E le scrive a schermo numerandoleConsole.WriteLine("{0} - {1}", Index, M.Value)Index += 1
NextEnd If
Console.ReadKey()
End SubEnd Module
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.17.
Class Form1Private Sub cmdSearch_Click(ByVal sender As Object, _
ByVal e As EventArgs) Handles cmdSearch.Click'Clipboard è una proprietà di My.Computer, di tipo'Microsoft.VisualBasic.MyService.ClipboardProxy: è un'oggetto singleton che rappresenta gli appunti. Questo'codice ottiene il testo copiato nella clipboard con'la funzione CopiaDim Text As String = Clipboard.GetText'Questa espressione deve ricercare tutti gli indirizzi'e-mail presenti nel testo, quindi aggiungerli alla lista:'\b(\w+) : la prima serie di caratteri costituisce' l'username dell'utente. Ad esempio in' [email protected], è gianni90''\s* : zero o più spazi. Può capitare che per non rendere
Per pr ovar e il pr ogr amma potete copiar e questo testo:
Salve, ragazzi! Il mio indirizzo è [email protected], 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.
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.
' 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 dominioDim 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 dominiolstEmail.Items.Add(String.Format( _"{0}@{1}.{2}", M.Groups(1).Value, M.Groups(3).Value, _M.Groups(5).Value), True)
NextEnd Sub
Private Sub cmdSave_Click(ByVal sender As Object, _
ByVal e As EventArgs) Handles cmdSave.Click'Salva gli indirizzi spuntatiDim Save As New SaveFileDialogSave.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 spuntatiFor Each Item As String In lstEmail.CheckedItems
Writer.WriteLine(Item)NextWriter.Close()
End IfEnd Sub
End Class
Validazione di espressioniLe 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:
Oppur e contr ollar e che numer i e date siano immessi cor r ettamente:
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.
Module Module2Sub Main()
'Controlla la formattazione dell'indirizzoDim R As New Regex("(\w+)@(\w+)\.(\w+)")Dim Input As String
Do
Console.Write("Inserire il proprio indirizzo e-mail: ")Input = Console.ReadLine
Loop Until R.IsMatch(Input)Console.WriteLine("Indirizzo accettato!")
Console.ReadKey()
End SubEnd Module
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.17.18.
Module Module3Sub Main()
'Controlla la formattazione del numero. È uso frequente'nelle validazioni usare i caratteri ^ e $, che indicano'inizio e fine della stringa, per controllare che all'interno'ci sia solo il valore che si vuole e non altre coseDim R As New Regex("^\d{4}$")Dim Input As String
Do
Console.Write("Inserire un numero intero tra 1000 e 9999: ")Input = Console.ReadLine
Loop Until R.IsMatch(Input)Console.WriteLine("Numero accettato!")
Console.ReadKey()
End SubEnd Module
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.
Module Module4Sub Main()
'Controlla la formattazione del numero.'\d+ : almeno una cifra''(...)? : tutto quello che viene dopo è messo tra parentesi' per poter usare il qualificatore ?. Il numero può ' infatti anche non essere decimale''(\.|,) : virgola o punto''\d+ : le cifre decimali, almeno unaDim R As New Regex("^\d+((\.|,)\d+)?$")Dim Input As String
Do
Console.Write("Inserire un numero anche decimale: ")Input = Console.ReadLine
Loop Until R.IsMatch(Input)Console.WriteLine("Numero accettato!")
Console.ReadKey()
End Sub
Parsing di file di datiQuando 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 softwar 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:
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:
End Module
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.17.18.
Module Module5Sub Main()
'Controlla la formattazione della data'Una o due cifre, seguite da /, - o |, seguite dalla stessa'cosa e da due o quattro cifre indicanti l'anno'Ovviamente non viene controllata la coerenza della dataDim R As New Regex("^\d{1,2}[/\-\|]\d{1,2}[/\-\|](\d{2}|\d{4})$")Dim Input As String
Do
Console.Write("Inserire una data: ")Input = Console.ReadLine
Loop Until R.IsMatch(Input)Console.WriteLine("Data accettata!")
Console.ReadKey()
End SubEnd Module
1. Campo1|Campo2|Campo3|...
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.
Module Module6Sub Main()
'Scandisce una riga del file, ottenendo i vari valoriDim R As New Regex( _"^(?<FirstName>[\w\s]+)\|(?<LastName>[\w\s]+)\|(?<BirthDay>.+)$", _RegexOptions.Multiline)Dim File As String
Console.WriteLine("Inserire il nome del file da caricare:")File = Console.ReadLine
If Not IO.File.Exists(File) Then
Console.WriteLine("File inesistente!")Exit Sub
End If
'Classe creata nelle prime lezioni sulle classiDim P As Person'Come sopraDim List As New PersonCollectionDim M As MatchDim Line As StringDim Reader As New IO.StreamReader(File)
While Not Reader.EndOfStream
'Legge una linea di testoLine = Reader.ReadLine'La confronta con l'espressione regolareM = R.Match(Line)'E se ha successo...If M.Success Then
Un file di esempio:
Parsing di codiceÈ 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:
34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.
'Crea un nuovo oggetto PersonP = New Person(M.Groups("FirstName").Value, _
M.Groups("LastName").Value, _Date.Parse(M.Groups("BirthDay").Value))
'Lo visualizza a schermoConsole.WriteLine("{0}, nato il {1}", P.CompleteName, _
P.BirthDay.ToShortDateString)'E lo aggiunge alla listaList.Persons.Add(P)
End IfEnd While
Reader.Close()Console.WriteLine("L'età media è {0} ", List.AverageAge)
Console.ReadKey()
End SubEnd Module
1.2.3.4.
Tizio Caio|Sempronio|21/12/2006Pinco|Pallino|13/01/2000Chissoio|Nonso|06/07/1990Mario|Rossi|19/06/1994
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.
Module Module7Sub Main()
'Scandisce una riga del file, ottenendo i vari valoriDim Argument As String = "(ByVal|ByRef) (?<Arg>\w+) As (?<Type>\w+)"Dim R As New Regex( _"\s*[\w\s]*\s*Sub\s+(?<Name>\w+)\((" & Argument & "\W*)*\)\s*$", _RegexOptions.Multiline)Dim File As String
Console.WriteLine("Inserire il nome del file da caricare:")File = Console.ReadLine
If Not IO.File.Exists(File) Then
Console.WriteLine("File inesistente!")Exit Sub
End If
For Each M As Match In R.Matches(IO.File.ReadAllText(File))Console.Write(M.Groups("Name").Value)Console.Write("(")'Dato che ci possono essere più argomenti, ogni gruppo Arg'e Type può venire catturato più volte. In questo caso'la proprietà Captures restituisce ogni singola'istanza del gruppoFor I As Int32 = 0 To M.Groups("Arg").Captures.Count - 1
Console.Write("{0} As {1}", M.Groups("Arg").Captures(I), _M.Groups("Type").Captures(I))If I < M.Groups("Arg").Captures.Count - 1 Then
Console.Write(", ")End If
NextConsole.WriteLine(")")
Next
36.37.
Console.ReadKey()End Sub
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à AllowDr 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 MouseDown. Si cr ei ad esempio un for m
con due tex tbox vuote, e AllowDr op di una su Tr ue:
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.
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:
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 indows 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.
1.2.3.4.5.6.7.
Private Sub TextBox1_MouseDown(ByVal sender As Object, _ByVal e As EventArgs) Handles TextBox1.MouseDown'Inizia l'operazione di Drag e Drop dalla textbox numero 1,'usando come dati da trasportare il suo testo. L'effetto'del mouse, invece, deve essere quello usato per la copiaTextBox1.DoDragDrop(TextBox1.Text, DragDropEffects.Copy)
End Sub
01.02.03.04.05.06.07.08.09.10.11.
Private Sub TextBox2_DragEnter(ByVal sender As Object, _ByVal e As DragEventArgs) Handles TextBox2.DragEnter'Se contiene i dati giusti di tipo StringIf e.Data.GetDataPresent(GetType(String)) Then
'Continua a copiaree.Effect = DragDropEffects.Copy
Else'Altrimenti annulla l'azionee.Effect = DragDropEffects.None
End IfEnd Sub
1.2.3.4.5.6.7.8.
Private Sub TextBox2_DragDrop(ByVal sender As Object, _ByVal e As DragEventArgs) Handles TextBox2.DragDrop'Ottiene i dati di tipo string presenti in memoriaDim S As String = e.Data.GetData(GetType(String))'Imposta il testo della seconda textbox uguale a quello'della primaTextBox2.Text = S
End Sub
F5. La classe Graphics
La gr afica è una delle par ti meno usate, o meno compr ese, del Fr amewor 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 non è 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:
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
1.2.
e.Graphics.CopyFromScreen(New Point(0, 0), New Point(50, 50), _New Size(200, 200))
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 omHwnd(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.
GraphItemsLa 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.002.003.004.005.006.007.008.009.010.011.012.013.014.015.016.017.018.019.020.021.022.023.024.
'Questa classe astratta costituisce la base per ogni'elemento che andrà ad essere disegnato sul controlloPublic MustInherit Class GraphItem
'Per disegnare delle forme geometriche con i metodi Draw'si usano oggetti di tipo Pen (penna): una penna definisce'il colore usato per tracciare le linee e il loro'spessore. Sono presenti delle penne predefinite nella'classe statica Pens: una per ogni colore (per tutte,'l'ampiezza del tratto è costante e pari a 1).'Noi useremo sempre delle penne nere per il contorno'dei prossimi oggetti, ma ho voluto aggiungere'questo membro per completezza.Private _ColorPen As Pen 'Allo stesso modo, per riempire delle forme deometriche'coi metodi Fill, si usano i pennelli (Brush). Brush è'una classe astratta che costituisce la base di tutti'i pennelli derivati. Noi useremo dei SolidBrush, oggetti'che colorano un'area con colore uniforme. Tuttavia, come'spiegherò in seguito, esistono molti altri tipi'di pennelli, ad esempio per eseguire sfumature o per'riempire un'area con delle immaginiPrivate _ColorBrush As Brush
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.094.095.096.
Public Property ColorPen() As Pen
GetReturn _ColorPen
End GetSet(ByVal Value As Pen)
_ColorPen = ValueEnd Set
End Property
Public Property ColorBrush() As BrushGet
Return _ColorBrushEnd GetSet(ByVal Value As Brush)
_ColorBrush = ValueEnd Set
End Property
'Ogni elemento deve esporre la procedura Draw,'per mezzo della quale esso disegnerà la propria'rappresentazione sul supporto grafico specificato'da G. Come già accennato, disegneremo tutto'su un'immagine vuota creata da noiPublic MustOverride Sub Draw(ByVal G As Graphics)
End Class 'Un pezzo di torta XDPublic Class PiePiece
Inherits GraphItem
'I parametri necessari a disegnarla sono: il centro'della torta, il raggio, l'ampiezza (in gradi) e'l'angolo inizialePrivate _Center As PointPrivate _Radius As Int32Private _StartAngle, _EndAngle As Single
'CentroPublic Property Center() As Point
GetReturn _Center
End GetSet(ByVal Value As Point)
_Center = ValueEnd Set
End Property
'RaggioPublic Property Radius() As Int32
GetReturn _Radius
End GetSet(ByVal Value As Int32)
_Radius = ValueEnd Set
End Property
'Angolo di partenzaPublic Property StartAngle() As Single
GetReturn _StartAngle
End GetSet(ByVal Value As Single)
_StartAngle = ValueEnd Set
End Property
'AmpiezzaPublic Property SweepAngle() As Single
GetReturn _EndAngle
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 GetSet(ByVal Value As Single)
_EndAngle = ValueEnd Set
End Property
Sub New(ByVal Center As Point, ByVal Radius As Int32, _ByVal StartAngle As Single, ByVal SweepAngle As Single)Me.Center = CenterMe.Radius = RadiusMe.StartAngle = StartAngleMe.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 parteDim UpperLeft As New Point(Me.Center.X - Me.Radius, _
Me.Center.Y - Me.Radius)'Calcola la dimensione di tale quadratoDim 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 SweepAngleG.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 1G.DrawPie(Pens.Black, New Rectangle(UpperLeft, Size), _
Me.StartAngle, Me.SweepAngle)End Sub
End Class 'Un'etichetta che visualizza il colore e il testo'corrispondentePublic Class ColorLabel
Inherits GraphItem
'I parametri necessari a disegnarla sono: il testo,'le coordinate e il colore, che viene definito'nella classe basePrivate _Text As StringPrivate _Location As Point
'TestoPublic Property Text() As String
GetReturn _Text
End GetSet(ByVal Value As String)
_Text = ValueEnd Set
End Property
'CoordinatePublic Property Location() As Point
GetReturn _Location
End GetSet(ByVal Value As Point)
_Location = ValueEnd Set
End Property
Sub New(ByVal Text As String)Me.Text = Text
End Sub
L'applicazione princ ipaleL'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.
169.170.171.172.173.174.175.176.177.178.179.180.
181.182.
Public Overrides Sub Draw(ByVal G As System.Drawing.Graphics)
'Disegna un quadratino coloratoG.FillRectangle(Me.ColorBrush, New Rectangle(Me.Location, _
New Size(20, 10)))'Disegna il contorno nero al quadratinoG.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 aggiuntiviG.DrawString(Me.Text, New Font("Microsoft Sans Serif", 12, FontStyle.Regular),
Brushes.Black, Me.Location.X + 30, Me.Location.Y - 5)End Sub
End Class
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.
Class Form1Private Items As New List(Of GraphItem) Private Sub cmdDraw_Click(ByVal sender As Object, ByVal e As EventArgs) _
Handles cmdDraw.ClickDim Total As Single
Items.Clear() 'Calcola il totaleFor Each Row As DataGridViewRow In dgvValues.Rows
'Controlla che il valore sia diverso da NULLIf Row.Cells Is Nothing Then
Continue ForEnd If'Quindi somma il valore della cella al totaleTotal += Row.Cells(0).Value
Next
'Costruisce gli spicchi'Valore di una rigaDim Value As Single'Variabile ausiliare del ciclo: tiene traccia dell'angolo a cui'si è arrivatiDim PrevAngle As Single = 0'Anche questa, come sopra: tiene traccia a quale altezza si'è arrivati con la legendaDim Y As Int32 = 20'Testo della rigaDim Text As String'Pennello > coloreDim Br As Brush'Una etichetta della legendaDim Lab As ColorLabel'Un pezzo della tortaDim Piece As PiePiece
For Each Row As DataGridViewRow In dgvValues.Rows
'Controlla che i valori esistano e che la cella non'sia l'ultima (che è sempre vuota)If Row.Cells Is Nothing OrElse _
Row.Index = dgvValues.RowCount - 1 ThenContinue For
End If
Value = Row.Cells(0).Value'Costruisce il testo della legenda, formato da quello della
Ed ecco un esempio di come si pr esenter à alla fine, tutta l'applicazione:
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.
'riga, con la specificazione, tra parentesi, del valore'corrispondente e della percentualeText = String.Format("{0} ({1:N2} - {2:N2}%)", _
Row.Cells(1).Value, Value, Value * 100 / Total)'Questo sempre per l'intelligenza di DataGridView,Select Case Row.Cells(2).Value
Case "Rosso"Br = Brushes.Red
Case "Arancio"Br = Brushes.Orange
Case "Giallo"Br = Brushes.Yellow
Case "Verde"Br = Brushes.Green
Case "Azzurro"Br = Brushes.LightBlue
Case "Indaco"Br = Brushes.Blue
Case "Viola"Br = Brushes.Violet
Case "Nero"Br = Brushes.Black
End Select
'Inizializza la nuova etichettaLab = New ColorLabel(Text)Lab.ColorBrush = BrLab.Location = New Point(280, Y)
'E il nuovo pezzo di torta. Value * 360 / Totale è'l'ampiezza dell'angolo, ottenuta con la proporzione:'Value : Total = x : 360Piece = New PiePiece(New Point(150, 125), 100, _
PrevAngle, Value * 360 / Total)Piece.ColorBrush = Br
'Tiene traccia dell'angoloPrevAngle += Value * 360 / Total'Si sposta più in giù per la prossima etichettaY += 20'Aggiunge gli elementiItems.Add(Lab)Items.Add(Piece)
Next
'Crea una nuova immagine vuotaDim Img As New Bitmap(imgPreview.Width, imgPreview.Height)'Prende l'oggetto Graphics associato a quell'immagineDim G As Graphics = Graphics.FromImage(Img)'Attiva l'anti-aliasG.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias'E disegna ogni elementoFor Each Item As GraphItem In Me.Items
Item.Draw(G)Next'Ogni cosa disegnata mediante G verrà trasferita'sull'immagine Img associataG.Flush()
imgPreview.Image = Img
End SubEnd Class
F6. Utilizzo avanzato della classe Graphics
Penne alternativeNel 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:
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:
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.AntiAliasg.Clear(Color.White)g.DrawLine(p, 10, 10, 80, 100)
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 alternativiOltr 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.
Esempio
Esempio 1: Creare una userbarPer 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 Visitor TT2
BRK.
Noi automatizzer emo tutto questo cr eando una classe apposita:
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.
Namespace Userbars
Class UserbarPrivate _BackgroundImage As ImagePrivate _BackgroundImagePosition As Int32Private _Text As StringPrivate _Size As Size = New Size(350, 25)
Public Property Size() As Size
GetReturn _Size
End GetSet(ByVal value As Size)
If value.Width > 0 And value.Height > 0 Then_Size = value
Else_Size = New Size(350, 25)
End IfEnd Set
End Property
Public Property BackgroundImage() As ImageGet
Return _BackgroundImageEnd Get
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 = valueEnd If
End SetEnd Property
Public Property BackgroundImagePosition() As Int32
GetReturn _BackgroundImagePosition
End GetSet(ByVal value As Int32)
If value > Me.Size.Width ThenThrow New ArgumentOutOfRangeException()
Else_BackgroundImagePosition = value
End IfEnd Set
End Property
Public Property Text() As StringGet
Return _TextEnd GetSet(ByVal value As String)
_Text = valueEnd Set
End Property
Public Function Create() As ImageDim 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'altezzaStatic 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'trasparenteStatic 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
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 analogicoEcco uno stupidissimo codice di esempio per disegnar e un or ogoloio:
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.
'precedenti, ma vi si applichino sopra rispettandone'le trasparenzeG.CompositingMode = CompositingMode.SourceOver'Disegna l'immagine di sfondoG.DrawImage(Me.BackgroundImage, Me.BackgroundImagePosition, 0)'Disegna le righe su tutta l'immagineG.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'aspettoG.FillEllipse(TranspBrush, -5, -Me.Size.Height + 3, Me.Size.Width + 10,
CInt(Me.Size.Height * 1.5))'Disegna il contorno della barraG.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)
'Restituisce il risultatoReturn Result
End Function
End Class
End Namespace
01.02.03.04.05.
06.07.08.09.10.11.12.13.14.15.16.17.18.
Class Form1Private ClockImage As New Bitmap(230, 230)Private G As Graphics = Graphics.FromImage(ClockImage)
Private Sub tmrClock_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs)
Handles tmrClock.TickG.Clear(Me.BackColor)
Dim Time As Date = Date.Now
Static BorderPen As New Pen(Color.Black, 5)Static HourPen As New Pen(Color.Red, 4) With {.EndCap = LineCap.Triangle}Static MinutePen As New Pen(Color.Blue, 2) With {.EndCap = LineCap.Triangle}Static SecondPen As New Pen(Color.Green, 1)
Dim C As Point = New Point(ClockImage.Width / 2, ClockImage.Height / 2)Dim h, m, s As Single
Ulteriori esempiNella sezione Download 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.
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.
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
G.DrawLine(HourPen, C.X, C.Y, _C.X + CInt(ClockImage.Width / 4 * Math.Cos(Math.PI / 2 - h / 6 * Math.PI)), _C.Y - CInt(ClockImage.Width / 4 * Math.Sin(Math.PI / 2 - h / 6 * Math.PI)))
G.DrawLine(MinutePen, C.X, C.Y, _
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)))
G.DrawLine(SecondPen, C.X, C.Y, _
C.X + CInt(ClockImage.Width / 2.2 * Math.Cos(Math.PI / 2 - s / 30 * Math.PI)), _C.Y - CInt(ClockImage.Width / 2.2 * Math.Sin(Math.PI / 2 - s / 30 * Math.PI)))
imgClock.Image = ClockImage
End SubEnd Class
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.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.50.51.52.
'Ricordarsi di importare questo namespaceImports System.Drawing.PrintingClass Form1
'...
Private Sub cmdPrint_Click(ByVal sender As Object, _
ByVal e As EventArgs) Handles cmdPrint.Click'Una nuova finestra di dialogo per la stampanteDim PrintDialog As New PrintDialog'Il nuovo oggetto che fa da mediatore tra l'utente e'la stampanteDim PrintDoc As New PrintDocument
With PrintDialog
'Determina se sia possibile stampare su un file.AllowPrintToFile = False'Determina se sia possibile stampare solo la'selezione. In questo caso non c'è nessuna selezione'quindi non ci sono problemi a disativare'l'impostazione.AllowSelection = False'Determina se sia possibile stampare delle pagine in'particolare. Vale lo stesso discorso fatto sopra.AllowSomePages = False'Assegna l'oggetto PrintDoc a Document, così da'collegare le impostazioni selezionate al documento.Document = PrintDoc
End With
If PrintDialog.ShowDialog = Windows.Forms.DialogResult.OK Then'Copia le impostazioni di stampa nel documentoPrintDoc.PrinterSettings = PrintDialog.PrinterSettings'Quindi aggiunge l'handler d'evento per PrintpageAddHandler PrintDoc.PrintPage, AddressOf PrintDocument_PrintPage'Il nome del documento visualizzato sulla finestra'di stampaPrintDoc.DocumentName = "Grafico a torta"'Fa partire il processo di stampaPrintDoc.Print()
End IfEnd Sub
Private Sub PrintDocument_PrintPage(ByVal sender As Object, _
ByVal e As PrintPageEventArgs)'Bisogna considerare anche i margini della pagina, perciò'sposta l'origine più in basso, come specificato'dai margini superiore e sinistro selezionati in PrintDialoge.Graphics.RenderingOrigin = New Point(e.MarginBounds.Left, _
e.MarginBounds.Top)
Or a spostiamoci su qualcosa di meno semplice. Bisogna stampar e un file di testo. Ecco un esempio del codice di stampa:
53.54.55.56.57.58.59.60.61.62.63.64.65.
'In questo caso le operazioni sono molto semplici: basta'"disegnare" sulla stampante (ossia sullo stream che'permette l'interazione con essa) gli stessi elementi'presenti nella lista ItemsFor Each Item As GraphItem In Me.Items
Item.Draw(e.Graphics)Next
'Sicuramente ci sta tutto in una pagina, quindi specifica'che non ci sono più pagine da stampare.e.HasMorePages = False
End Sub End Class
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.50.51.52.53.54.
Class Form1Private Reader As IO.StreamReader
Private Sub cmdPrintFile_Click(ByVal sender As Object, _
ByVal e As EventArgs) Handles cmdPrintFile.ClickDim PrintDialog As New PrintDialogDim Open As New OpenFileDialogDim PrintDoc As New PrintDocument
With PrintDialog
.AllowPrintToFile = False
.AllowSelection = False
.AllowSomePages = False
.Document = PrintDocEnd With
Open.Filter = "File di testo|*.txt"
If Open.ShowDialog = Windows.Forms.DialogResult.OK Then
Reader = New IO.StreamReader(Open.FileName)Else
Exit Sub
End If
If PrintDialog.ShowDialog = Windows.Forms.DialogResult.OK ThenPrintDoc.PrinterSettings = PrintDialog.PrinterSettingsAddHandler PrintDoc.PrintPage, AddressOf PrintFile_PrintPagePrintDoc.DocumentName = IO.Path.GetFileName(Open.FileName)PrintDoc.Print()
End If
End Sub
Private Sub PrintFile_PrintPage(ByVal sender As Object, _ByVal e As PrintPageEventArgs)'Imposta l'unità di misura per le misurazioni'successivee.Graphics.PageUnit = GraphicsUnit.Pixel'Il font per il testo da stampare: Times New Romand 12ptStatic Font As New Font("Times New Roman", 12)'Per sapere quante righe ci possono stare nella pagine,'bisogna misurare l'altezza dei caratteriStatic CharHeight As Single = Font.GetHeight(e.Graphics)'Calcola le linee di testo che possono stare in una paginaStatic TotalLines As Int16 = e.MarginBounds.Height / CharHeight
'La linea a cui si è arrivati a leggereDim LineIndex As Int16 = 1'Il testo della rigaDim Line As String
'Tiene conto della posizione attuale
In conclusione, si tr atta di far e pr atica, poiché la teor ia è molto semplice.
Eccezioni 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:
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.
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.
Dim Y As Int16 = e.MarginBounds.Top
e.Graphics.RenderingOrigin = New Point(e.MarginBounds.Left, _e.MarginBounds.Top)
Do
'Legge la riga di testo dal fileLine = Reader.ReadLine
'Si suppone che la larghezza della stringa sia minore'di quella della paginae.Graphics.DrawString(Line, Font, Brushes.Black, _
e.MarginBounds.Left, Y)Y += CharHeightLineIndex += 1
Loop While (LineIndex < TotalLines) And Not (Reader.EndOfStream)
'Se il file è alla fine, non ci sono più pagine'da stampare, altrimenti continuae.HasMorePages = Not Reader.EndOfStreamIf Reader.EndOfStream Then
Reader.Close()End If
End Sub
End Class
1.2.3.4.
Dim P As New ProcessP.StartInfo.FileName = "file da stampare"P.StartInfo.Verb = "Print"P.Start()
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 web 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 rammatica
con cui il documento viene scr itto.
Pr ima di iniziar e, segue una br eve intr oduzione all'XML.
Introduzione all'XMLUn 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 elemento (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 elemento root o semplicemente root. 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:
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 contenuto si può usar e la sintassi abbr eviata
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
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>
1. <elemento attributo="valore" ... />
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 parser , 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 XML in ambiente .NETDescr 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.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.
Imports System.XmlModule Module1
Sub Main()'Crea un nuovo lettore xml.Dim Reader As New Xml.XmlTextReader("C:\libri.xml")Dim Indent, Content As String
'La funzione Reader.Read legge il nodo successivo e'restituisce False se non c'è niente altro da'leggere. Il ciclo legge tutti gli elementiDo While Reader.Read
'Indenta il codice a seconda della profondità'del nodoIndent = New String(" ", Reader.Depth * 2)'Se il nodo è un tag di chiusura, come ad
Il r isultato sar à questo:
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:
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.
'esempio </libro>, lo saltaIf Not Reader.IsStartElement Then
'Viene considerato un nodo anche il testo all'interno'di un elemento, perciò bisogna controllare'se questo non sia effettivamente un testoIf Reader.HasValue Then
Console.WriteLine(Reader.Value)End IfContinue Do
End If
'Scrive il nome del tag a schermo, tra parentesi quadre,'come nei file INIConsole.WriteLine("{0}[{1}]", Indent, Reader.Name)'Se l'elemento ha un contenuto, lo memorizza per scriverlo'successivamente a schermoIf Reader.HasValue AndAlso Reader.Value <> vbCrLf Then
Content = Reader.ValueElse
Content = NothingEnd If'Se l'elemento possiede attributi, li scriveIf Reader.HasAttributes Then
For I As Int16 = 0 To Reader.AttributeCount - 1Reader.MoveToAttribute(I)Console.WriteLine("{0}{1} = {2}", Indent, _
Reader.Name, Reader.Value)Next
End If
If Content IsNot Nothing ThenConsole.WriteLine("{0}Contenuto = {1}", Indent, Content)
End IfLoopReader.Close()
Console.ReadKey()
End SubEnd Module
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.17.18.19.20.
[biblioteca][libro]titolo = I sei numeri dell'universoautore = Martin Rees[capitolo]titolo = Il cosmo e il microcosmonumero = 1pagina = 11
Alla base della struttura del nostro universo - non solo ...
[libro]titolo = Le ostinazioni di un matematicoautore = Didier Nordon[capitolo]titolo = Dalle stalle alle stellenumero = 1pagina = 11
Ecco a voi un romanzo il cui protagonista conosce una morte ...
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.TextImports System.XmlModule 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 accentatiDim 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 WithConsole.ReadKey()
End SubEnd 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 amewor 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 formatter , ossia un oggetto capace di tr asfer ir e i dati sul suppor to:
Binary : 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
SOAP : 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 amewor k 2.0, a favor e della più r apida e meno dispendiosa Binar y
XML : 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 oggettiLe 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.002.003.004.005.006.007.008.009.010.011.012.013.014.015.016.017.018.019.020.021.
Imports System.Runtime.Serialization.FormattersModule Module1
<Serializable()> _Class Person
Implements IComparableProtected _FirstName, _LastName As StringPrivate ReadOnly _BirthDay As Date
Public Property FirstName() As String
GetReturn _FirstName
End GetSet(ByVal Value As String)
If Value <> "" Then_FirstName = Value
End IfEnd Set
End Property
Public Overridable Property LastName() As String
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.
GetReturn _LastName
End GetSet(ByVal Value As String)
If Value <> "" Then_LastName = Value
End IfEnd Set
End Property
Public ReadOnly Property BirthDay() As DateGet
Return _BirthDayEnd Get
End Property
Public Overridable ReadOnly Property CompleteName() As StringGet
Return _FirstName & " " & _LastNameEnd Get
End Property
Public Overloads Overrides Function ToString() As StringReturn MyBase.ToString
End Function
Public Overloads Function ToString(ByVal FormatString As String) _As StringDim Temp As String = FormatStringTemp = 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 1End IfDim 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 = FirstNameMe.LastName = LastNameMe._BirthDay = BirthDay
End SubEnd Class Sub Main()
'Crea un nuovo oggetto Person da serializzareDim P As New Person("Pinco", "Pallino", New Date(1990, 6, 1))'Crea un nuovo Formatter binarioDim Formatter As New Binary.BinaryFormatter()'Crea un nuovo file su cui salvare l'oggettoDim 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
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 amewor 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:
094.095.096.097.098.099.100.101.102.103.104.105.106.107.108.109.110.111.112.113.114.115.
'una copia sopraFormatter.Serialize(File, P)'E chiude il fileFile.Close()Console.WriteLine("Salvataggio completato!")
'Crea una nuova variabile di tipo Person per contenere'i dati caricati dal fileDim F As Person'Apre lo stesso file di prima, ma in letturaDim Data As New IO.FileStream("C:\person.dat", IO.FileMode.Open)
'Carica le informazioi salvate nel fileF = Formatter.Deserialize(Data)
'Verifica che F e P siano perfettamente ugualiConsole.Write("F = P -> ")Console.Write(F.CompareTo(P))'> Ricordate che CompareTo restituisce 0 nel caso di uguaglianza
Console.ReadKey()
End SubEnd Module
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.
Public Module Module1'...Sub Main()
'Crea nuovi oggetti Person da serializzareDim P1 As New Person("Pinco", "Pallino", New Date(1990, 6, 1))Dim P2 As New Person("Tizio", "Caio", New Date(1967, 4, 13))Dim P3 As New Person("Mario", "Rossi", New Date(1954, 8, 12))'Ho creato un array perchè è più veloce, ma nulla'vieta di usare liste generics o qualsiasi altro tipo'di collezioneDim Persons() As Person = {P1, P2, P3}'Crea un nuovo Formatter Xml. Il serializzatore in questo'caso ha bisogno anche dell'oggetto Type relativo'all'oggetto da serializzare (un array di person).Dim Formatter As New Serialization.XmlSerializer(GetType(Person()))'Crea un nuovo file su cui salvare l'oggettoDim File As New IO.FileStream("C:\persons.dat", IO.FileMode.Create)
'Serializza l'oggetto'Attenzione! Se gli XmlSerializer non hanno bisogno che'l'oggetto in questione possegga l'attributo Serializable,'hanno invece bisogno che questo esponga almeno un'costruttore senza parametri. Quindi bisogna aggiungere'un nuovo New() a Person. Inoltre, altra limitazione'importante, con questo formatter è possibile'serializzare solo tipi pubblici.Formatter.Serialize(File, Persons)'E chiude il fileFile.Close()Console.WriteLine("Salvataggio completato!")'Potrete constatare che il salvataggio impiega un'tempo notevolmente maggiore
'Crea una nuova variabile di tipo Person per contenere'i dati caricati dal fileDim F As Person()'Apre lo stesso file di prima, ma in lettura
Se si pr ova ad apr ir e il file per sons.dat, si tr ova un nor malissimo file x ml:
Problemi legati alla serializzazionePotr 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 rafo: 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 Shallow). 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 è
39.40.41.42.43.44.45.46.47.48.49.50.51.52.
Dim Data As New IO.FileStream("C:\persons.dat", IO.FileMode.Open)
'Carica le informazioi salvate nel fileF = Formatter.Deserialize(Data)
'Verifica che F e P siano perfettamente ugualiFor I As Byte = 0 To 2
Console.WriteLine("P{0} = F{0} -> {1}", I, _Persons(I).CompareTo(F(I)))
Next'> Ricordate che CompareTo restituisce 0 nel caso di uguaglianza
Console.ReadKey()
End SubEnd Module
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.
<?xml version="1.0"?><ArrayOfPerson xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"><Person><FirstName>Pinco</FirstName><LastName>Pallino</LastName>
</Person><Person><FirstName>Tizio</FirstName><LastName>Caio</LastName>
</Person><Person><FirstName>Mario</FirstName><LastName>Rossi</LastName>
</Person></ArrayOfPerson>
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:
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
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.
Module Module1Public Class EventLauncher
Private _ID As Int32'Negli eventi custom, bisogna usare una variabile privata'dello stesso tipo dell'evento da lanciare. Si può'vedere un elemento custom un pò come una proprietà,'che media l'interazione con il vero delegate gestorePrivate _IDChangedEventHandler As EventHandler
'Dichiara il nuovo eventoPublic Custom Event IDChanged As EventHandler
'Proprio come nelle proprietà ci sono i'blocchi Get e Set per ottenere e assegnare un valore,'qua di sono i blocchi AddHandler e RomveHandler'per aggiungere o rimuovere un gestore d'evento e'il blocco RaiseEvent per lanciarloAddHandler(ByVal Value As EventHandler)
'Basta richiamare System.Delegate.Combine per'aggiungere il nuovo gestore Value_IDChangedEventHandler = _
[Delegate].Combine(_IDChangedEventHandler, Value)End AddHandler
RemoveHandler(ByVal Value As EventHandler)
_IDChangedEventHandler = _[Delegate].Remove(_IDChangedEventHandler, Value)
End RemoveHandler
RaiseEvent(ByVal sender As Object, ByVal e As EventArgs)'Controlla che ci sia almeno un gestore, quindi li'richiama tuttiIf _IDChangedEventHandler IsNot Nothing Then
_IDChangedEventHandler(sender, e)End If
End RaiseEventEnd Event
Public Property ID() As Int32
GetReturn _ID
End GetSet(ByVal Value As Int32)
_ID = ValueRaiseEvent IDChanged(Me, EventArgs.Empty)
End SetEnd Property
End Class'...
End Module
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 customI tipi for niti dal Fr amewor 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.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.
Module Module2<Serializable()> _Public Class Client
Implements ISerializablePrivate _Name As String<OptionalField()> _Private _IP As StringPrivate _IsIPStatic As Boolean
Public Property Name() As String
GetReturn _Name
End GetSet(ByVal Value As String)
_Name = ValueEnd Set
End Property
'ReadOnly perchè l'IP viene deciso alla connessione'e poi non subisce cambiamentiPublic ReadOnly Property IP() As String
GetReturn _IP
End GetEnd Property
'L'IP rilevato dal server, normalmente, cambia ad ogni'connessione e quindi sarebbe inutile serializzarlo.'Tuttavia, se viene reso statico, ad esempio con'l'uso di un DNS, allora lo si dovrebbe serializzare'e ricaricare al successivo avvio. Questa proprietà'ne definisce lo stato e influenza i processi di'serializzazione e deserializzazionePublic Property IsIPStatic() As Boolean
GetReturn _IsIPStatic
End GetSet(ByVal Value As Boolean)
_IsIPStatic = ValueEnd Set
End Property
'GetDataObject seleziona i campi da serializzarePrivate Sub GetDataObject(ByVal info As SerializationInfo, _
ByVal context As StreamingContext) _Implements ISerializable.GetObjectData'Info si comporta come un dictionary(of String, Object).'Basta aggiungere i valori da salvareinfo.AddValue("Name", Me.Name)info.AddValue("IsIPStatic", Me.IsIPStatic)'Questo passaggio è attuabile solo con la
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.
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.
'serializzazione customIf Me.IsIPStatic Then
info.AddValue("IP", Me.IP)End If
End Sub
'Private New viene richiamato dal formatter dopo la'deserializzaziore per impostare i valoriPrivate 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 esserciSub New(ByVal Name As String, ByVal IP As String)
Me.Name = Name_IP = IP
End SubEnd Class Sub Main()
'Crea un nuovo clientDim 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 dinamicoC.IsIPStatic = FalseFormatter.Serialize(File, C)File.Close()
'Lo ricarica, e osserva che l'IP è stato impostato'su quello della macchina localeDim Data As New IO.FileStream("C:\client.dat", IO.FileMode.Open)
C = Formatter.Deserialize(Data)Console.WriteLine(C.IP)' > 127.0.0.1Data.Close()
Console.ReadKey()
End SubEnd Module
F10. Compressione di dati
Il .NET Fr amewor 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.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.50.51.52.53.54.55.56.
Imports System.IOImports System.IO.CompressionModule Module1
Sub Main()'I percorsi del file da comprimere e di quello compressoDim File, CompressedFile As String'Lo stream che legge i dati da FileDim Input As FileStream'Lo stream di scrittura associato al file compressoDim Output As FileStream'Lo stream compresso che scrive i dati codificati per mezzo'dell'output streamDim Zipped As DeflateStream
Console.WriteLine("Inserire il percorso del file da compriemere:")File = Console.ReadLineConsole.WriteLine("Inserire il percorso in cui salvare i dati compressi:")CompressedFile = Console.ReadLine
'Controlla che il file esistaIf Not IO.File.Exists(File) Then
Console.WriteLine("File inesistente!")Exit Sub
End If
'Inizializza lo stream di inputInput = New FileStream(File, IO.FileMode.Open)'Inizializza lo stream di outputOutput = New FileStream(CompressedFile, IO.FileMode.Create)'Inizializza lo zipperZipped = New DeflateStream(Output, CompressionMode.Compress)
'Buffer temporaneo che contiene pacchetti di datiDim Buffer(4095) As Byte
'Legge i bytes a blocchi di 4KiBFor I As Int64 = 0 To Input.Length - 1 Step 4096
If Input.Length - I >= 4096 ThenInput.Read(Buffer, 0, 4096)
ElseInput.Read(Buffer, 0, Input.Length - I)
End If'Li scrive sullo stream compressoZipped.Write(Buffer, 0, Buffer.Length)
Next
'Trasferisce dati compressi sullo streamZipped.Flush()'Quindi chiude tutti gli streamZipped.Close()Output.Close()Input.Close()
'Alla fine calcola la compressione totale'Ottiene la dimensione iniziale del file
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.
57.58.59.60.61.62.63.64.
Dim StartSize As Int64 = FileLen(File)'E quella finale del file compressoDim EndSize As Int64 = FileLen(CompressedFile)
Console.WriteLine("Compressione totale: {0:N2}%", _
100 - (EndSize * 100 / StartSize))Console.ReadKey()
End SubEnd Module
F11. Sicurezza e criptazione
Anche in questo ambito, il Fr amewor 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 criptazioneLa 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:
Simmetr ici
Sono i più semplici e dir etti metodi di cifr atur a. Per funzionar e necessitano di una chiave (o passwor 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: cifrar i a blocchi e cifrar i basati
su stream .
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 vettore 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
Asimmetr 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 chiave pubblica, che può esser e comunicata a tutti, e una chiave
pr ivata, 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 amewor 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 praticaNei 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 prov ider cr ittog rafico, 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.SecurityImports System.Security.CryptographyImports System.Text.UTF8EncodingModule Module1
'Vettore di bytes casuali usati per oscurare la chiave:'verrà usato nella funzione di derivazione della passwordPrivate 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 bytesPrivate 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 bytesPrivate Function DerivePassword(ByVal Key As String) As Byte()
'Il provider crittograficoDim Derive As Rfc2898DeriveBytes'Il risultato dell'operazioneDim 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 arbitrariamenteDerive = 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 DerivedBytesEnd Function
'Data una chiave Key e un messaggio Text, usa l'algoritmo simmetrico'a blocchi Rijndael (AES) per ottenere un insieme di dati criptatoPublic Function RijndaelEncrypt(ByVal Key As String, _
ByVal Text As String) As Byte()'Crea il nuovo provider crittografico per questo algoritmoDim Provider As New RijndaelManaged'La password derivataDim BytePassword As Byte()'L'oggetto che ha il compito di processare le informazioniDim Encryptor As ICryptoTransform'L'output della funzioneDim 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 accentatiDim Input As Byte() = UTF8.GetBytes(Text)
'Imposta la dimensione della chiaveProvider.KeySize = 256'Imposta la dimensione del bloccoProvider.BlockSize = 256'Ottiene la password tramite derivazione dalla chiaveBytePassword = DerivePassword(Key)'Crea un nuovo oggetto codificatoreEncryptor = Provider.CreateEncryptor(BytePassword, IV32)'Cripta il testoOutput = Encryptor.TransformFinalBlock(Input, 0, Input.Length)
'Elimina le informazioni fornite al providerProvider.Clear()'Distrugge l'oggetto codificatoreEncryptor.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 partenzaPublic Function RijndaelDecrypt(ByVal Key As String, _
ByVal Data() As Byte) As String'Crea un nuovo provider crittograficoDim Provider As New RijndaelManaged'La password derivataDim BytePassword As Byte()'L'oggetto che ha il compito di processare le informazioniDim Decryptor As ICryptoTransform'L'output della funzione in bytesDim Output As Byte()
Provider.KeySize = 256Provider.BlockSize = 256BytePassword = DerivePassword(Key)'Ottiene l'oggetto decodificatoreDecryptor = Provider.CreateDecryptor(BytePassword, IV32)
'Tenta di decriptare il messaggio: se la chiave è'sbagliata, lancia un'eccezioneTry
Output = Decryptor.TransformFinalBlock(Data, 0, Data.Length)Catch Ex As Exception
Throw New CryptographicException("Criptazione fallita!")Finally
Provider.Clear()Decryptor.Dispose()
End Try
E questo per l'Hash:
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.
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 cifratoPublic 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 sinistraResult.AppendFormat("{0:X2}", Bytes(I))
Next
Return Result.ToStringEnd Function
Sub Main()
Dim Input, Output As StringDim Key As String
Console.WriteLine("Inserire un testo qualsiasi:")Input = Console.ReadLineConsole.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 CryptographicExceptionConsole.WriteLine("Password errata!")
End Try
Console.ReadKey()End Sub
End Module
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.SecurityImports System.Security.CryptographyImports System.Text.UTF8EncodingModule Module2
'Questa semplice funzione genera un hash MD5Public 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 Md5Output = 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)))
25.26.
Console.ReadKey()End Sub
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 amewor 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 web, 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à non 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 SingleFramePerSecond = 1 / Video.AverageTimePerFrame
Caption : se il video viene avviato senza un'adeguata pr opr ietà Owner (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
Owner : 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
ShowCur 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:
Windows Media Video (*.wmv)
Moving Pictur es Ex per t Gr oup 1 for mat (*.mpg)
Audio Video Inter leave (*.avi)
Micr osoft Audio Wave (*.wav)
Moving Pictur es Ex per t Gr oup 1 Layer 3 (*.mp3)
Windows Media Audio (*.wma)
F13. Sintesi vocale
Questo capitolo è scr itto per VB2008!
Installazione del softwareNonostante esistano già libr er ie appar tenenti al .Net Fr amewor 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 Windows Xp. Recatevi a questo indir izzo e tr over ete un elenco di files da scar icar e:
msttss22L.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
1.
sapi.chm : documentazione completa delle SAPI 5.1. Se volete appr ofondir e l'ar gomento scar icatela pur e2.
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
3.
SpeechSDK51.exe : contiene tutti i files per l'installazione - download obbligator io4.
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
5.
speechsdk51msm.exe : questo è un pacchetto che contiene tutti gli altr i files messi insieme6.
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 vocalePer 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 Windows. 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:
Resta ancor a da capir e in che for mato vengano salvati i dati, dato che con *.wav 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 wave 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 :
Questo metodo r egistr a efficacemente la fr ase "Hello!" sul file Voce.wav
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:
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()
1.2.3.
Synt.SetOutputToWaveFile("Voce.wav")Synt.Speak("Hello!")Synt.Dispose()
01.02.03.04.05.06.07.08.09.10.11.
Imports System.SpeechImports System.Speech.RecognitionImports System.Speech.Synthesis 'Ricordatevi anche di importare la libreria System.Speech nel progetto,'con Add ReferenceModule Module1
Sub Main()'Inizializza il nuovo sintetizzatoreDim Synt As New SpeechSynthesizer
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.
'Sceglie la classica voce di Microsoft SamSynt.SelectVoice("Microsoft Sam") 'Imposta l'output sulle casse del computer'In questo passaggio è obbligatorio usare un thread.'La ragione non è ben chiara, ma se non si fa in questo'modo, risulta sempre un errore di tipo ArgumentExceptionDim T As Threading.Thread'Imposta il nuovo thread: il suo compito principale sarà'di eseguire il metodo Synt.SetOutputToDefaultAudioDeviceT = New Threading.Thread(AddressOf _
Synt.SetOutputToDefaultAudioDevice)'Inizia il nuovo threadT.Start()'Aspetta che abbia finito per continuareT.Join() Dim Text As StringDo
Console.WriteLine("Inserisci una frase:")Text = Console.ReadLine 'Fa leggere la frase a MS SamSynt.Speak(Text)
Loop Until Text = "" 'Rilascia le risorseSynt.Dispose()
End SubEnd Module
F14. Riconoscimento vocale
Questo capitolo è scr itto per VB2008!
Costruire la grammaticaPer 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:
In questo modo si è aggiunta al Gr ammar Builder una gamma di par ole possibili: one, two, 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 solo 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:
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:
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.
1.2.3.4.5.6.7.
Imports System.SpeechImports System.Speech.RecognitionImports System.Speech.Synthesis '...Dim GrammarBuilder As New GrammarBuilderGrammarBuilder.Append(New Choices("one", "two", "three", "four"))
1. GrammarBuilder.Culture = Globalization.CultureInfo.GetCultureInfo("en-US")
1.2.3.
Dim Grammar As Grammar'...Grammar = New Grammar(GrammarBuilder)
Esempio: Tell me how muchPer 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.SpeechImports System.Speech.RecognitionImports System.Speech.Synthesis Public Class Form2
'Nuovo Engine di riconoscimento vocalePrivate Engine As New SpeechRecognitionEngine'GrammarBuilder per costruire la grammaticaPrivate GrammarBuilder As New GrammarBuilder'Oggetto Grammar che rappresenta la grammaticaPrivate 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ò rilevarePrivate 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 immessoPrivate Prev As Int32'Result contiene il numero finalePrivate 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 precedenteDim 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 codiceTextNumber = 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 indicaFor 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+70For 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 GrammarBuilderGrammarBuilder.Append(New Choices(Numbers))'Imposta la lingua a ingleseGrammarBuilder.Culture = _
Globalization.CultureInfo.GetCultureInfo("en-US")'Costruisce la nuova "grammatica" con il GrammarBuilderGrammar = 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 nuoveEngine.UnloadAllGrammars()'Quindi carica la grammatica Grammar. Ora Engine è in'grado di riconoscere le parole dell'array NumbersEngine.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 grammaticaAddHandler 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 soloEngine.RecognizeAsync(RecognizeMode.Multiple)
'Disabilita Start e abilita StopbtnStart.Enabled = FalsebtnStop.Enabled = True
End Sub
Private Sub btnStop_Click(ByVal sender As Object, _ByVal e As EventArgs) Handles btnStop.Click'Termina il riconoscimento asincronoEngine.RecognizeAsyncCancel()
'Abilita Start e disabilita StopbtnStart.Enabled = TruebtnStop.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 pronunciataDim Text As String = e.Result.Text
'Se il testo è "reset", annulla tuttoIf Text = "reset" Then
Result = 0End If
'Se il testo è contenuto nel dizionario, allora'è un numero validoIf TextNumber.ContainsKey(Text) Then
'Ottiene il numeroN = TextNumber(Text)
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:
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.
'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 9If (N = 100) And (Prev > 0 And Prev < 10) Then
'Toglie l'unitàResult -= Prev'E la trasforma in centinaiaResult += Prev * 100
End If'Parimenti, si può usare "thousand" solo dopo un'numero minore di mille. Anche se lecito, nessuno direbbe'"a thousand thousand", ma piuttosto "a million"If (N = 1000) And (Result < 1000) Then
Result *= 1000End If'Se il numero è minore di 100, semplicemente lo'aggiunge. Se quindi si pronunciano "twenty" e "thirty"'di seguito, si otterà 50. Non chiedetemi perchè'l'ho fatto così...If (N < 100) Then
Result += NEnd If
ElseN = 0
End If
Prev = N
'Imposta il testo della labellblNumber.Text = String.Format("{0:N0}", Result)
End SubEnd Class
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.SpeechImports System.Speech.RecognitionImports System.Speech.Synthesis Public Class Form2
'Nuovo Engine di riconoscimento vocalePrivate Engine As New SpeechRecognitionEngine'GrammarBuilder per costruire la grammaticaPrivate GrammarBuilder As New GrammarBuilder'Oggetto Grammar che rappresenta la grammaticaPrivate 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ò rilevarePrivate 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à dopoPrivate Delegate Sub SetLabel(ByVal Res As RecognitionResult)'Prev ricorda l'ultimo numero immessoPrivate Prev As Int32'Result contiene il numero finalePrivate 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 precedenteDim 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 codiceTextNumber = 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 indicaFor 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+70For 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 GrammarBuilderGrammarBuilder.Append(New Choices(Numbers))'Imposta la lingua a ingleseGrammarBuilder.Culture = Globalization.CultureInfo.GetCultureInfo("en-US")'Costruisce la nuova "grammatica" con il GrammarBuilderGrammar = 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 nuoveEngine.UnloadAllGrammars()'Quindi carica la grammatica Grammar. Ora Engine è in'grado di riconoscere le parole dell'array NumbersEngine.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 grammaticaAddHandler 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 soloEngine.RecognizeAsync(RecognizeMode.Multiple)
'Disabilita Start e abilita StopbtnStart.Enabled = FalsebtnStop.Enabled = True
End Sub
Private Sub btnStop_Click(ByVal sender As Object, _ByVal e As EventArgs) Handles btnStop.Click'Termina il riconoscimento asincronoEngine.RecognizeAsyncCancel()
'Abilita Start e disabilita StopbtnStart.Enabled = TruebtnStop.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 tuttoDim 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 parametroMe.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 pronunciataDim Text As String = Res.Text
'Se il testo è "reset", annulla tuttoIf Text = "reset" Then
Result = 0End If
'Se il testo è contenuto nel dizionario, allora'è un numero validoIf TextNumber.ContainsKey(Text) Then
'Ottiene il numeroN = 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 9If (N = 100) And (Prev > 0 And Prev < 10) Then
'Toglie l'unitàResult -= Prev'E la trasforma in centinaia
Potr ebbe anche ver ificar si un altr o er r or e, qui:
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).
173.174.175.176.177.178.179.180.181.182.183.184.185.186.187.188.189.190.191.192.193.194.195.196.
Result += Prev * 100End If'Parimenti, si può usare "thousand" solo dopo un'numero minore di mille. Anche se lecito, nessuno direbbe'"a thousand thousand", ma piuttosto "a million"If (N = 1000) And (Result < 1000) Then
Result *= 1000End If'Se il numero è minore di 100, semplicemente lo'aggiunge. Se quindi si pronunciano "twenty" e "thirty"'di seguito, si otterà 50. Non chiedetemi perchè'l'ho fatto così...If (N < 100) Then
Result += NEnd If
ElseN = 0
End If
Prev = N
'Imposta il testo della labellblNumber.Text = String.Format("{0:N0}", Result)
End SubEnd Class
1. Me.Invoke(New SetLabel(AddressOf AnalyzeText), New Object() {Res})
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 amewor 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 web e r ichiamar e metodi web senza dover e scr iver e lo stesso
codice più volte
My.ApplicationEcco 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 non è 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 Windows Application, ottiene una collezione di tutti i for m aper ti
SaveMySettingsOnEx it : se il pr ogetto cor r ente è una Windows 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 Windows Application, pr ocessa tutti i messaggi w indows 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 var iabili d'ambiente
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 dwar e e del softwar 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 Windows
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 Shutdown 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 Networ 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.ComputerAd 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 (*.wav), 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 Windows, 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 wr 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 indows, 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
AltKeyDown, Ctr lKeyDown, ShiftKeyDown : 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
ButtonsSwapped : 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
Networ k
DownloadFile(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 passwor 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 indows; 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 DownloadFile
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.UserEspone 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.ResourcesÈ 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 softwar 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.SettingsPer 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 ima 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.WebServicesEspongono 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 keywor 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:
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.02.03.04.05.06.07.08.09.10.
Namespace MyPublic Class Utilities
'Con questa versione, si utilizza la classe come contenitore'di metodi o membri statici, rendendola simile a un moduloPublic Shared Function Percent(ByVal Num As Int32, _
ByVal Max As Int32) As SingleReturn (Num * 100) / Max
End FunctionEnd Class
End Namespace
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.17.18.19.20.21.22.23.
Namespace My'L'attributo HideModuleName permette di nascondere il'nome del modulo nella scala gerarchica dell'IntelliSense'e accedere direttamente a Utilities<DebuggerNonUserCode(), _Microsoft.VisualBasic.HideModuleName()> _Public Module MyUtilities
Private _Utilities As New Utilities
Public ReadOnly Property Utilities() As UtilitiesGet
Return _UtilitiesEnd Get
End PropertyEnd Module
End Namespace Public Class Utilities
'Con questa versione, si utilizza la classe come vero oggetto,'ma i metodi di istanza che non utilizzano altri campi d'istanza'possono comunque essere utilizzati allo stesso modo dei metodi'statici, poichè l'oggetto è già inizializzato all'interno del
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:
24.25.26.27.28.
'modulo sopra definitoPublic Function Percent(ByVal Num As Int32, _
ByVal Max As Int32) As SingleReturn (Num * 100) / Max
End FunctionEnd Class
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.
Namespace My'Aggiunge membri a My.ApplicationPartial Friend Class MyApplication
Private _ServerName As String = "http://www.example.com"
Public Property ServerName() As StringGet
Return _ServerNameEnd GetSet(ByVal Value As String)
_ServerName = ValueEnd Set
End PropertyEnd Class
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:
Panoramia 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 ExplorerNella 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:
Show 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 owser
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 progettoQuesta 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:
Propr ietà - Application
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: Windows 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 shutdown : 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
Shutdown 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
Propr ietà - Compile
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 war ning. Queste azioni sono, nell'or dine, dall'alto in basso:
Conver sioni implicite fr a tipi. Ad esempio:
Late binding, ossia r ichiamar e membr i da una var iabile Object. Ad esempio:
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:
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:
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:
Dichiar azione di funzioni od oper ator e che non r estituiscono alcun tipo. Ad esempio:
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:
1. Dim I As Int32 = 34.78
1.2.3.4.5.
Private Click(ByVal sender As Object, ByVal e As EventArgs)'sender è un generico Object: non si è sicuri che'a runtime possa risultare proprio di tipo Controlsender.Enabled = False
End Sub
1. Dim S
01.02.03.04.05.06.07.08.09.10.11.
Dim I As Int16'I non è inizializzata: sarà 0 e produrrà un erroreDim 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 runtimeStr.Append("Ciao")
1.2.3.4.5.6.7.8.
Dim I As Int16 If K >= 26 Then
I = 2 * K + 1End If 'Se K < 26, I varrà 0K /= I
1.2.3.4.5.6.7.8.
Public Function GetName(ByVal Text As String)'...
End Function 'OppurePublic Shared Operator +(ByVal P1 As Person, ByVal P2 As Person)
'...End Operator
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
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:
Blocchi Catch uguali
Le due checkbox in fondo, invece, per mettono o di soppr imer e ogni war ning, oppur e di tr attar e ogni war 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.
Propr ietà - References
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
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
01.02.03.04.05.06.07.08.09.10.11.
Private _Name As StringPublic ReadOnly Property Name() As String
Get'Questo statement deve ottenere il valore della'proprietà Name, della quale si sta definendo ora'il corpo. In questo modo si richiederà il blocco'Get, che a sua volta richiamerà se stesso un'numero infinito di volteReturn Name
End GetEnd Property
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ò Windows 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.
Propr 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 softwar 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
Object BrowserL'Object br owser è una ver sione molto (ma molto!) più sofisticata e dettagliata del nostr o Br owser 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 Browser
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 documentazione 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
IntelliSenseC'è 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 azione!
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:
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:
1. dim Str as new sys.te.s[Invio]
IntelliSense in azione!
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 utiliIn 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:
Code 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 IntelliSensePer 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, nelle opzioni
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 var iabile
Accesso ai membr i di un og g etto
Passag g io di parametr i a un metodo
Asseg nazione di valor i a un enumeratore
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 lasseTutte 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 suggerisce i parametri di ogni metodoQuando 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 nuoveUna 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 Networ 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.Networ 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 amicoQuindi 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 erroriIl 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 war 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 war 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, war 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.
BreakpointI 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 keywor d Stop. Ad esempio:
Finestra WatchQuando 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 watch". 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 DebugLa 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:
1.2.3.4.
'...If I = 50 Then
StopEnd If
01.02.03.04.05.06.07.08.09.10.11.12.13.14.15.16.17.
Module Module1Sub Main()
Dim P As New Person("Pinco", "Pallino", New Date(2008, 1, 1))'...
Debug.WriteLine("Nome completo: " & P.CompleteName)'Si possono scrivere messaggi solo se vige una certa condizioneDebug.WriteLineIf(P.BirthDay > New Date(2007, 9, 27), _
"Data di nascita posteriore al 27/9/2007")
If P IsNot Nothing ThenStop
End If
Console.ReadKey()End Sub
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 documentazioneEcco una lista dei pr incipali attr ibuti/tag di documentazione:
summar y : una descr izione del membr o e della sua funzione. Ad esempio:
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:
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:
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.
''' <summary>''' Descrizione del metodo''' </summary>Sub DoSomething()
'...End Sub
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
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
1.2.3.4.5.6.
''' <summary>''' Fa qualcosa...''' </summary>''' <typeparam name="T">Un qualsiasi tipo reference.</typeparam>Sub DoSomething(Of T As Class)()
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:
7. End Sub
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 Int16End 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>
E viene visualizzato così:
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
GetReturn Me.VariabileIstanza
End GetSet(ByVal Value As Boolean)
Me.VariabileIstanza = ValueEnd 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
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 wizardInno 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 ura 1
Cliccar e Nex t per pr oseguir e.
Fig ura 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 softwar e
Il sito di r ifer imento per quel softwar e (ver r à inser ito nel menù di Installazione Applicazioni di Windows)
Fig ura 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 ura 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 softwar e
alla fine del setup e se non ci sia un eseguibile pr incipale.
Fig ura 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 softwar 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 ura 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 passwor 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 editorNon 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:
Ag g iung ere un'immag 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 ProgramAppVerName=My Program 1.5AppPublisher=My Company, Inc.AppPublisherURL=http://www.example.com/AppSupportURL=http://www.example.com/AppUpdatesURL=http://www.example.com/DefaultDirName={pf}\My ProgramDefaultGroupName=My ProgramOutputBaseFilename=setupCompression=lzmaSolidCompression=yes; Carica l'immagine. Sono supportate solo bitmap, massimo 164x314WizardImageFile=C:\immagine.bmp; Se l'immagine è più piccola, ma si vuole che occupi; lo stesso spazio, si può attivare l'opzione StretchWizardImageStretch=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 RGBWizardImageBackColor=$FFFFFF
Ag g iung ere chiav i di reg istro
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 installazioneRoot: 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