Guida Visual Basic
Guida Visual Basic
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.
Ambiente di sviluppo
L'ambiente di sviluppo che pr ender come r ifer imento per questa guida Visual Basic Ex pr ess 2008 (scar icabile dal Sito Ufficiale della M icr o so ft; se si ha un pr ofilo Passpor t.NET possibile r egistr ar e il pr odotto e ottener e una ver sione completa). Potete comunque scar icar e Shar pDevelop da qui (vedi sezione dow nloads), un pr ogr amma gr atis e molto buono (tr over ete una r ecensione nella sezione Sofw tar e di Pier oTofy.it r edatta da me e HeDo qui). Dato che le ver sioni pr ecedenti della guida, dalle quali r ipr esa la maggior anza dei sor genti pr oposti, sono state r edatte pr endendo come esempio Visual Basic Ex pr ess 2005, potete scar icar e anche quello da qui.
Le Classi
Come dicevo, una car atter istica par ticolar e di questa categor ia di linguaggi che essi sono basati su un unico impor tantissimo concetto fondamentale: gli o g g etti, i quali vengono r appr esentati da classi. Una classe non altr o che la r appr esentazio ne - o v v iam ente astr atta - di qualco sa di co ncr eto , mentr e l'oggetto sar una concr etizzazione di questa r appr esentazione (per una discussione pi appr ofondita sulla differ enza tr a classe e oggetto, veder e capitolo A7). Ad esempio, in un pr ogr amma che deve gestir e una videoteca, ogni videocassetta o DVD r appr esentato da una classe; in un pr ogr amma per la fattur azione dei clienti, ogni cliente e ogni fattur a vengono r appr esentati da una classe. Insomma, ogni cosa, ogni entit, ogni r elazione - per fino ogni er r or e - tr ova la sua r appr esentazione in una classe. Detto questo, viene spontaneo pensar e che, se ogni cosa astr atta da una classe, questa classe dovr anche contener e dei dati su quella cosa. Ad esempio, la classe Utente dovr contener e infor mazioni sul nome dell'utente, sulla sua passw or d, sulla sua data di nascita e su molto altr o su cui si pu sor volar e. Si dice che tutte queste infor mazioni sono espo ste dalla classe: ognuna di esse, inoltr e, r appr esentata da quello che viene chiamato m em br o. I membr i di una classe sono tutti quei dati e quelle funzionalit che essa espone. Per esser e usabile, per , una classe deve venir e pr ima dichiar ata, mediante un pr eciso codice. L'atto di dichiar ar e una qualsiasi entit le per mette di iniziar e ad "esister e": il pr ogr ammator e deve infatti ser vir si di qualcosa che gi stato definito da qualche par te, e senza di quello non pu costr uir e niente. Con la par ola "entit" mi r ifer isco a qualsiasi cosa si possa usar e in pr ogr ammazione: dato che le vostr e conoscenze sono limitate, non posso che usar e dei ter mini gener ici e piuttosto vaghi, ma in br eve il mio lessico si far pi pr eciso. Nella pr atica, una classe si dichiar a cos: 1. Class [NomeClasse] 2. ... 3. End Class dove [NomeClasse] un qualsiasi nome che potete decider e ar bitr ar iamente, a seconda di cosa debba esser e r appr esentato. Tutto il codice compr eso tr a le par ole sopr a citate inter no alla classe e si chiama co r po ; tutte le entit esistenti nel cor po sono dei membr i. Ad esempio, se si volesse idealizzar e a livello di codice un tr iangolo, si scr iver ebbe questo: 1. Class Triangolo 2. ... 3. End Class Nel cor po di Tr iangolo si potr anno poi definir e tutte le infor mazioni che gli si possono attr ibuir e, come la lunghezza
I Moduli
Nonostante il nome, i moduli non sono niente altr o che dei tipi speciali di classi. La differ enza sostanziale tr a i due ter mini ver r chiar ita molto pi avanti nella guida, poich le vostr e attuali competenze non sono sufficienti a un completo appr endimento. Tuttavia, i moduli sar anno la tipologia di classe pi usata in tutta la sezione A.
I Namespac e
Possiamo definir e classi e moduli come un it fun zion ali: essi r appr esentano qualcosa, possono esser e usate, manipolate, istanziate, dichiar ate, ecceter a... Sono quindi str umenti attivi di pr ogr ammazione, che ser vono a r ealizzar e concr etamente azioni e a pr odur r e r isultati. I namespace, invece, appar tengono a tutt'altr o gener e di categor ia: essi sono solo dei r aggr uppamenti "passivi" di classi o di moduli. Possiamo pensar e a un namespace come ad una car tella, entr o la quale possono star e files, ma anche altr e car telle, ognuna delle quali r aggr uppa un par ticolar e tipo di infor mazione. Ad esempio, volendo scr iver e un pr ogr amma che aiuti nel calcolo geometr ico di alcune figur e, si potr ebbe usar e un codice str uttur ate come segue: 01. 02. 03. 04. 05. 06. 07. 08. 09. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. Namespace Triangoli Class Scaleno '... End Class Class Isoscele '... End Class Class Equilatero '... End Class End Namespace Namespace Quadrilateri Namespace Parallelogrammi Class Parallelogramma '... End Class Namespace Rombi Class Rombo '... End Class Class Quadrato '... End Class End Namespace End Namespace End Namespace
Come si vede, tutte le classi che r appr esentano tipologie di tr iangoli (Scaleno, Isoscele, Equilater o) sono all'inter no del namespace Tr iangoli; allo stesso modo esiste anche il namespace Quadr ilater i, che contiene al suo inter no un altr o namespace Par allelogr ammi, poich tutti i par allelogr ammi sono quadr ilater i, per definizione. In quest'ultimo esiste la classe Par allelogr amma che r appr esenta una gener ica figur a di questo tipo, ma esiste ancor a un altr o namespace Rombi: come noto, infatti, tutti i r ombi sono anche par allelogr ammi. Dall'esempio si osser va che i namespace categor izzano le unit funzionali, dividendole in insiemi di per tinenza. Quando un namespace si tr ova all'inter no di un altr o namespace, lo si definisce nidificato: in questo caso, Par alleloogr ammi e Rombi sono namespace nidificati. Altr a cosa: al contr ar io della classi, gli spazi di nomi (italianizzazione dell'inglese name-space) non possiedono un "cor po", poich questo ter mine si pu usar e solo quando si par la di qualcosa di attivo;
1. Sistema operativo
Il Fr amew or k .NET pr esenta una str uttur a str atificata, alla base della quale r isiede il sistema oper ativo, Window s. Pi pr ecisamente, si consider a il sistema oper ativo e l'API (Application Pr ogr amming Inter face) di Window s, che espone tutti i metodi r esi disponibili al pr ogr ammator e per svolger e un dato compito.
4. X ML
Successivamente tr oviamo i dati, le r isor se. Per salvar e i dati viene usato quasi sempr e il for mato XM L (eXtensible Mar kup Language), che utilizza dei tag spesso nidificati per contener e i campi necessar i. La str uttur a di questo tipo di file, inoltr e, adatta alla r appr esentazione ger ar chica, un metodo che nell'ambiente .net impor tantissimo. I file di configur azione e quelli delle opzioni impostate dell'utente, ad esempio, vengono salvati in for mato XML. Anche la nuova tecnologia denominata Window s Pr esentation Foundation (WPF), intr odotta nella ver sione 3.5 del Fr amew or k, che per mette di cr ear e contr olli dalla gr afica accattivante e str avagante, si basa su un linguaggio di contr assegno (di mar kup) sur r ogato dell'XML.
7. Linguaggi .NET
In cima alla str uttur a ci sono tutti i linguaggi .net: Vb, C#, J#, ecceter a.
Nello scr eenshot pr oposto qui sopr a si possono veder e le tr e ar ee in cui solitamente divisa l'inter faccia del compilator e: non vi pr eoccupate se la vostr a appar e differ ente, poich, essendo modificabile a piacimento, la mia potr ebbe esser e diver sa dal layout pr eimpostato del compilator e. Per or a, le finestr e impor tanti sono due: quella del codice, dove andr emo a scr iver e le istr uzioni, e quella degli er r or i, dove potr ete tener e costantemente sott'occhio se avete commesso degli er r or i di sintassi. Nello scr eenshot la seconda di queste non visibile, ma la si pu por tar e in pr imo piano tenendo pr emuto Ctr l e digitando in successione "\" ed "E". Per quanto r iguar da il codice che appar e, ho gi specificato in pr ecedenza che i moduli sono dei tipi speciali di classe, e fin qui vi baster saper e questo. Quello che potr este non conoscer e la par te di sor gente in cui appaiono le par ole Sub ed End Sub: anche in questo caso, la tr attazione par ticolar e di queste keyw or ds sar r imandata pi in l. Per or a possiamo consider ar e la Sub Main() come il pr ogr amma inter o: ogni cosa che viene scr itta tr a "Sub Main()" ed "End Sub" ver r eseguita quando si pr emer il pulsante Star t (il tr iangolino ver de in alto sulla bar r a degli str umenti), o in alter nativa F5.
Tr over ete l'eseguibile compilato nella car tella Documenti\Visual Studio 2008\Pr ojects\[Nome pr ogetto]\bin\Release.
un testo; Object: r appr esenta un qualsiasi tipo (ma non un tipo base). I tipi base vengono detti anche ato m ici o pr im itiv i, poich non possono esser e ulter ior mente scomposti. Esistono, quindi, anche tipi der iv ati, appar tenenti a svar iate tipologie che analizzer emo in seguito, fr a cui si annover ano anche le classi: ogni tipo der ivato scomponibile in un insieme di tipi base. Or a, quindi, possiamo estr apolar e delle infor mazioni in pi dal codice pr oposto: dato che segue la keyw or d Dim, "Ciao" l'identificator e di una var iabile di tipo Int16 (infatti dopo As stato specificato pr opr io Int16). Questo significa che "Ciao" pu contener e solo numer i inter i che, in valor e assoluto, non super ino 32767. Ovviamente, la scelta di un tipo di dato piuttosto che un altr o var ia in funzione del compito che si intende svolger e: maggior e la pr ecisione e l'or dine di gr andezza dei valor i coinvolti e maggior e sar anche l'uso di memor ia che si dovr sostener e. Continuando a legger e, si incontr a, nella r iga successiva, un'assegnazione, ossia una oper azione che pone nella var iabile un cer to valor e, in questo caso 78; l'assegnazione avviene mediante l'uso dell'oper ator e uguale "=". L'istr uzione successiva simile a questa, ma con una sostanziale differ enza: il valor e assegnato alla var iabile influenzato dalla var iabile stessa. Nell'esempio pr oposto, il codice: 1. Ciao = Ciao + 2 ha la funzione di incr ementar e di due unit il contenuto di Ciao. Questa istr uzione potr ebbe sembr ar e algebr icamente scor r etta, ma bisogna r icor dar e che si tr atta di un comando (e non di un'equazione): pr ima di scr iver e nella cella di memor ia associata alla var iabile il numer o che il pr ogr ammator e ha designato, il pr ogr amma r isolve l'espr essione a destr a dell'uguale sostituendo ad ogni var iabile il suo valor e, e ottenendo, quindi, 78 + 2 = 80. Le ultime due r ighe, invece, fanno visualizzar e a scher mo il contenuto di Ciao e fer mano il pr ogr amma, in attesa della pr essione di un pulsante. Come si visto dall'esempio pr ecedente, con le var iabili di tipo numer ico si possono eseguir e oper azioni ar itmetiche. Gli oper ator i messi a disposizione dal Fr amew or k sono: + : addizione; - : sottr azione; * : pr odotto; / : divisione; \ : divisione tr a inter i (r estituisce come r isultato un numer o inter o a pr escinder e dal tipo degli oper andi, che possono anche esser e decimali); Mod : r estituisce il r esto di una divisione inter a; = : assegna alla var iabile posta a sinistr a dell'uguale il valor e posto dopo l'uguale; & : concatena una str inga con un numer o o una str inga con un'altr a str inga. 01. Module Module1 02. Sub Main() 03. 'Interi 04. Dim Intero, Ese As Int16 05. 'Decimale 06. Dim Decimale As Single 07. 'Booleano 08. Dim Vero As Boolean 09. 'Stringa 10. Dim Frase As String 11. 12. Intero = 90 13. Ese = Intero * 2 / 68 14. Intero = Ese - Intero * Intero 15. Decimale = 90.76 16. Decimale = Ese / Intero 17. Vero = True 18. Frase = "Ciao." 19. 'L'operatore "+" tra stringhe concatena due stringhe. Dopo la 20.
'prossima istruzione, la variabile Frase conterr: 21. ' "Buon giornoCiao" 22. Frase = "Buon giorno" + "Ciao" 23. 'L'operatore "&" pu concatenare qualsiasi dato e 24. 'restituisce una stringa. Dopo la prossima istruzione, la 25. 'variabile Frase conterr: 26. ' "Il valore decimale : -0,0003705076" 27. Frase = "Il valore decimale : " & Decimale 28. End Sub 29. End Module Esistono poi degli speciali oper ator i di assegnamento, che velocizzano l'assegnazione di valor i, alcuni sono: 01. Module Module1 02. Sub Main() 03. Dim V, B As Int32 04. V += B 'Equivale a 05. 06. B -= V 'Equivale a 07. V *= B 'Equivale a 08. B /= V 'Equivale a 09. End Sub 10. End Module
V B V B
= = = =
V B V B
+ * /
B V B V
Le fr asi poste dopo un apice (') sono dette co m m enti e ser vono per spiegar e cosa viene scr itto nel codice. Il contenuto di un commento NON influisce in nessun modo su ci che scr itto nel sor gente, ma ha una funzione ESCLUSIVAMENTE esplicativa.
Le c ostanti
Abbiamo visto che il valor e delle var iabili pu esser e modificato a piacimento. Ebbene quello delle costanti, come il nome sugger isce, no. Esistono per semplificar e le oper azioni. Per esempio, invece di digitar e 3,1415926535897932 per il Pi g r eco , possibile dichiar ar e una costante di nome Pi che abbia quel valor e ed utilizzar la nelle espr essioni. La sintassi per dichiar ar e una costante la seguente: 1. Const [nome] As [tipo] = [valore] Ci sono due lampanti differ enze r ispetto al codice usato per dichiar ar e una var iabile. La pr ima , ovviamente, l'uso della keyw or d Con s t al posto di Dim; la seconda consiste nell'assegnazione posta subito dopo la dichiar azione. Infatti, una costante, per esser e tale, dev e contener e qualcosa: per questo motivo o bblig ato r io specificar e sempr e, dopo la dichiar azione, il valor e che la costante assumer . Questo valor e non potr mai esser e modificato. Esempio: 01. Module Module1 02. Sub Main() 03. Const Pi As Single = 3.1415926535897932 04. Dim Raggio, Area As Double 05. 06. 'Questa istruzione scrive sul monitor il messaggio posto tra 07. 'virgolette nelle parentesi 08. Console.WriteLine("Inserire il raggio di un cerchio:") 09. 10. 'Questa istruzione legg un valore immesso dalla tastiera e 11. 'lo deposita nella variabile Raggio 12. Raggio = Console.ReadLine 13. Area = Raggio * Raggio * Pi 14. 15. Console.WriteLine("L'Area : " & Area) 16. 17. 'Questa istruzione ferma il programma in attesa della pressione 18. 'di un pulsante 19. Console.ReadKey() 20. End Sub 21. End Module
N.B.: a causa della lor o stessa natur a, le costanti NON possono esser e inizializzate con un valor e che dipenda da una funzione. Scr ivo questo appunto per pur a utilit di consultazione: anche se or a potr non r isultar e chiar o, vi capiter pi avanti di imbatter vi in er r or i del gener e: 1. Const Sqrt2 As Single = Math.Sqrt(2) Sqr t2 dovr ebbe esser e una costante numer ica decimale che contiene la r adice quadr ata di due. Sebbene il codice sembr i cor r etto, il compilator e segnaler come er r or e l'espr essione Math.Sqr t(2), poich essa una funzione, mentr e dopo l'uguale r ichiesto un valor e sempr e costante. Il codice cor r etto 1. Const Sqrt2 As Single = 1.4142135
Le istruzioni
Tutti i comandi che abbiamo impar tito al computer e che abbiamo gener icamente chiamato con il nome di istr uzioni (come Console.Wr iteLine()) hanno dei nomi pi specifici: sono pr o cedur e o funzio ni, in sostanza sottopr ogr ammi gi scr itti. Pr ocedur e e funzioni possono esser e globalmente indicate con la par ola m eto do . I metodi accettano dei par am etr i passatigli tr a par entesi: se i par ametr i sono di pi di uno vengono separ ati da vir gole. I par ametr i ser vono per comunicar e al metodo i dati sui quali questo dovr lavor ar e. La differ enza tr a una pr ocedur a e una funzione r isiede nel fatto che la pr ima fa semplicemente eseguir e istr uzioni al computer , mentr e la seconda r estituise un valor e. Ad esempio: 01. Module Module1 02. Sub Main() 03. Dim F As Double 04. 05. 'Questa una funzione che restituisce la radice quadrata di 56 06. F = Math.Sqrt(56) 07. 08. 'Questa una procedura che scrive a video un messaggio 09. Console.WriteLine("La radice di 56 " & F) Console.ReadKey() 10. 11. End Sub 12. End Module Anche i metodi ver r anno tr attai successivamente in dettaglio.
Tipi Referenc e
Ogni cosa nel Fr amew or k un oggetto e la maggior par te di essi sono tipi r efer ence. Si dicono tipi r efer ence tutti quei tipi che der ivano dir ettamente dalla classe System.Object (la "der ivazione" appar tiene a un concetto che spiegher pi avanti): questa classe dichiar ata all'inter no di una libr er ia della Base Class Libr ar y, ossia l'ar chivio di classi del Fr amew or k. Nel capitolo pr ecedente si visto come sia possibile assegnar e un valor e ad una var iabile utilizzando l'oper ator e uguale "=". Con questo meccanismo, un deter minato valor e viene depositato nella casella di memor ia che la var iabile occupa. Ebbene, facendo uso dei tipi r efer ence, questo non avviene. Quando si utilizza l'uguale per assegnar e un valor e a tali var iabili, quello che effettivamente viene r iposto nella lor o par te di memor ia un puntator e inter o a 32bit (su sistemi oper ativi a 32bit). Per chi non lo sapesse, un puntator e una speciale var iabile che, invece di contener e un pr opr io valor e, contiene l'indir izzo di un'ar ea di memor ia contenente altr i dati. Il puntator e viene memor izzato come al solito sullo stack , mentr e il ver o oggetto viene cr eato e deposto in un'ar ea di memor ia differ ente, detta heap m anag ed, dove esiste sotto la super visione del CLR. Quando una var iabile di questo tipo viene impostata a Nothing (una costante che vedr emo tr a poco), la par te dell'heap managed che l'oggetto occupa viene r ilasciata dur ante il pr ocesso di g ar bag e co llectio n ("r accolta dei r ifiuti"). Tuttavia, ci non avviene subito, poich il meccanismo del Fr amew or k fa in modo di avviar e la gar bage collection solo quando necessar io, quindi quando la
memor ia comincia a scar seggiar e: supponendo che un pr ogr amma abbia r elativamente pochi oggetti, questi potr ebber o "viver e" indistur bati fino alla fine del pr ogr amma anche dopo esser e stati lo g icam ente distr utti, il che significa che stato eliminato manualmente qualsiasi r ifer imento ad essi (vedi par agr afo successivo). Data l'impossibilit di deter minar e a pr ior i quando un oggetto ver r distr utto, si ha un fenomeno che va sotto il nome di finalizzazio ne no n deter m inistica (il ter mine "finalizzazione" non casule: veder e il capitolo sui distr uttor i per maggior i infor mazioni).
Nothing
Nothing una costante di tipo r efer ence che r appr esenta l'assenza di un oggetto piuttosto che un oggetto nullo. Infatti, por r e una var iabile oggetto uguale a Nothing equivale a distr ugger la logicamente. 1. Dim O As New Object 'L'oggetto viene creato 2. O = Nothing 'L'oggetto viene logicamente distrutto La distr uzione logica non coincide con la distr uzione fisica dell'oggetto (ossia la sua r imzione dalla memor ia), poich, come detto pr ima, il pr ocesso di liber azione della memor ia viene avviato solo quando necessar io. Non possibile assegnar e Nothing a un tipo value, ma possibile usar e speciali tipi value che suppor tano tale valor e: per ulter ior i dettagli, veder e "Tipi Nullable".
Tipi V alue
Ogni tipo v alue der iva dalla classe System.ValueType, che der iva a sua volta da System.Object, ma ne r idefinisce i metodi. Ogni var iabile di questo tipo contiene effettivamente il pr opr io valor e e non un puntator e ad esso. Inoltr e, esse hanno dei vantaggi in ter mini di memor ia e velocit: occupano in gener e meno spazio; data la lor o posizione sullo stack non vi bisogno di r efer enziar e un puntator e per ottener e o impostar ne i valor i (r efer enziar e un puntator e significa r ecar si all'indir izzo di memor ia puntato e legger ne il contenuto); non c' necessit di occupar e spazio nello heap managed: se la var iabile viene distr utta, cessa di esister e all'istante e non si deve attuar e nessuna oper azione di r ilascio delle r isor se. Notar e che non possibile distr ugger e logicamente una var iabile value, fatta eccezione per cer ti tipi der ivati.
Is e =
Nel lavor ar e con tipi r efer ence e value bisogna pr estar e molta attenzione a quando si utilizzano gli oper ator i di assegnamento. Come gi detto, i r efer ence contengono un puntator e, per ci se si scr ive questo codice: 1. Dim O1, O2 As Object 2. '... 3. O1 = O2 quello che O2 conter r non sar un valor e identico a O1, ma un puntator e alla stessa ar ea di memor ia di O1. Questo pr ovoca un fatto str ano, poich sia O1 che O2 puntano alla stessa ar ea di memor ia: quindi O1 e O2 so no lo stesso o g g etto , soltanto r ifer ito con nomi difer si. In casi simili, si pu utilizzar e l'oper ator e Is per ver ificar e che due var iabili puntino allo stesso oggetto: 1. 'Scrive a schermo se vero oppure no che 2. 'O1 e O2 sono lo stesso oggetto 3. Console.WriteLine(O1 Is O2) La scr itta che appar ir sullo scher mo sar "Tr ue", ossia "Ver o". Utilizzar e Is per compar ar e un oggetto a Nothing equivale a ver ificar e che tale oggetto sia stato distr utto. Questo NON avviene per i tipi value: quando ad un tipo value si assegna un altr o valor e con l'oper ator e =, si passa
effettivamente una co pia del valor e. Non possibile utilizzar e Is con i tipi value poich Is definito solo per i r efer ence.
Boxing e Unboxing
Consider iamo il seguente codice: 1. Dim I As Int32 = 50 2. Dim O As Object 3. O = I I un tipo value, mentr e O un tipo r efer ence. Quello che succede dietr o le quinte semplice: il .NET cr ea un nuovo oggetto, per ci un tipo r efer ence, con il r ispettivo puntator e, e quindi gli assegna il valor e di I: quando il pr ocesso finito assegna il puntator e al nuovo oggetto a O. Questa conver sione spr eca tempo e spazio nello heap managed e viene definita come bo xing . L'oper azione inver sa l'unboxing e consiste nell'assegnar e un tipo r efer ence a un tipo value. Le oper azioni che si svolgono sono le stesse, ma al contr ar io: entr ambe spr ecano tempo e cpu, quindi sono da evitar e se non str ettamente necessar ie. Quando si pu sceglier e, quindi, sono meglio di tipi value.
Una pr ecisazione: in tutti i pr ossimi capitoli capiter fr equentemente che io dica cose del tipo "la var iabile X un oggetto di tipo Str ing" oppur e "le due var iabili sono lo stesso oggetto". Si tr atta solo di una via pi br eve per evitar e il for malismo tecnico, poich, se una var iabile dichiar ata di tipo r efer ence, essa pr opr iamente un riferimen to all'oggetto e non un oggetto. Gli oggetti "vivono" indistur bati nell'heap managed, quel magico posto che nessuno conosce: noi possiamo solo usar e r ifer imenti a tali oggetti, ossia possiamo solo indicar li ("eccolo l! guar da! l'hai visto? ma s, pr opr io l! non lo vedi?").
A7. Il costrutto If
Capita spessissimo di dover eseguir e un contr ollo per ver ificar e se vigono cer te condizioni. possibile attuar e tale oper azione tr amite un co str utto di co ntr o llo , la cui for ma pi comune e diffusa il costr utto If. Questo per mette di contr ollar e se una condizione ver a. Ad esempio: in un pr ogr amma che calcoli l'ar ea di un quadr ato si deve impor r e di visualizzar e un messaggio di er r or e nel caso l'utente immetta una misur a negativa, poich, come noto, non esistono lati la cui misur a un numer o negativo: 01. Module Module1 02. Sub Main() 03. Dim Lato As Single 04. Console.WriteLine("Inserire il lato di un quadrato:") 05. Lato = Console.ReadLine 06. 07. 08. If Lato < 0 Then 'Se Lato minore di 0... Console.WriteLine("Il lato non pu avere una misura negativa!") 09. Else 'Altrimenti, se non lo ... 10. Console.WriteLine("L'area del quadrato : " & Lato * Lato) 11. 12. End If 'Fine controllo 13. Console.ReadKey() 14. 15. End Sub 16. End Module Come sicur amente avr ete intuito, questo contr ollo si pu associar e al costr utto italiano "Se avviene qualcosa Allor a fai questo Altr imenti fai quell'altr o". Si pu eseguir e qualsiasi tipo di compar azione tr a If e Then utilizzando i seguenti oper ator i di confr onto: > : maggior e < : minor e = : uguaglianza <> : diver so >= : maggior e o uguale <= : minor e o uguale Is : identicit (solo per tipi r efer ence) IsNot : negazione di Is (solo per tipi r efer ence) Ma l'impor tante r icor dar si di attener si a questa sintassi: 1. If [Condizione] Then 2. [istruzioni] 3. Else 4. [istruzioni alternative] 5. End If
If nidific ati
Quando si tr ova un costr utto If all'inter no di un altr o costr utto If, si dice che si tr atta di un Co str utto If Nidificato . Questo avviene abbastanza spesso, specie se si ha bisogno di far e contr olli multipli: 01. Module Module1 02. Sub Main() 03. Dim Numero As Int16 04. 05.
Console.WriteLine("Inserisci un numero:") 06. Numero = Console.ReadLine 07. 08. If Numero > 0 Then 09. If Numero < 5 Then 10. Console.WriteLine("Hai indovnato il numero!") 11. End If 12. Else 13. Console.WriteLine("Numero errato!") 14. End If 15. 16. Console.ReadKey() 17. End Sub 18. End Module Se il numer o inser ito da tastier a compr eso fr a 0 e 5, estr emi esclusi, allor a l'utente ha indovinato il numer o, altr imenti no. Si pu tr ovar e un numer o illimitato di If nidificati, ma meglio limitar ne l'uso e, piuttosto, far e utilizzo di co nnettiv i lo g ici.
I c onnettivi logic i
I connettivi logici sono 4: And, Or , Xor e Not. Ser vono per costr uir e contr olli complessi. Di seguito un'illustr azione del lor o funzionamento: If A And B : la condizione r isulta ver ificata se sia A che B sono ver e co ntem po r aneam e nte If A Or B : la condizione r isulta ver ificata se ver a alm eno una delle due condizioni If A Xor B: la condizione r isulta ver a se una so la delle due condizioni ver a If Not A: la condizione r isulta ver ificata se falsa Un esempio pr atico: 01. Module Module1 02. Sub Main() Dim a, b As Double 03. 04. Console.WriteLine("Inserire i lati di un rettangolo:") 05. a = Console.ReadLine 06. 07. b = Console.ReadLine 08. 09. 'Se tutti e due i lati sono maggiori di 0 10. If a > 0 And b > 0 Then 11. Console.WriteLine("L'area : " & a * b) 12. Else Console.WriteLine("Non esistono lati con misure negative!") 13. 14. End If 15. Console.Readkey() 16. End Sub 17. End Module
Voto = Console.ReadLine 07. 08. If Voto < 3 Then 09. Console.WriteLine("Sei senza speranze!") 10. Else 11. If Voto < 5 Then 12. Console.WriteLine("Ancora un piccolo sforzo...") 13. Else 14. If Voto < 7 Then 15. Console.WriteLine("Stai andando discretamente") 16. Else 17. If Voto < 9 Then 18. Console.WriteLine("Molto bene, continua cos") 19. Else 20. Console.WriteLine("Sei praticamente perfetto!") 21. End If 22. End If 23. End If 24. End If 25. 26. Console.ReadKey() 27. End Sub 28. End Module E' abbastanza disor dinato... La var iante ElseIf molto utile per miglior e la leggibilit del codice: 01. Module Module1 02. Sub Main() 03. Dim Voto As Single 04. Console.WriteLine("Inserisci il tuo voto:") 05. 06. Voto = Console.ReadLine 07. 08. If Voto < 3 Then 09. Console.WriteLine("Sei senza speranze!") 10. ElseIf Voto < 5 Then 11. Console.WriteLine("Ancora un piccolo sforzo...") 12. ElseIf Voto < 7 Then 13. Console.WriteLine("Stai andando discretamente") 14. ElseIf Voto < 9 Then 15. Console.WriteLine("Molto bene, continua cos") 16. Else 17. Console.WriteLine("Sei praticamente perfetto!") 18. End If 19. Console.ReadKey() 20. 21. End Sub 22. End Module Notate che tutti gli ElseIf fanno par te dello s tes s o costr utto: mentr e nell'esempio ogni If nidificato er a un blocco a s stante, dotato infatti di un pr opr io End If, in questo caso ogni alter nativa-selettiva fa comunque par te dell'unico If iniziale, pr otr atto solamente un poco pi a lungo.
Bloc c hi di istruzioni
Fino a questo punto, gli esempi pr oposti non hanno mai dichiar ato una var iabile dentr o un costr utto If, ma solo all'inizio del pr ogr amma, dopo Sub Main(). possibile dichiar ar e var iabili in altr i punti del codice che non siano all'inizio della Sub? Cer tamente s. A differ enza di altr i, i linguaggi .NET per mettono di dichiar ar e var iabili in qualunque punto del sor gente, dove occor r e, evitando un gigantesco agglomer ato di dichiar azioni iniziali, for temente disper sive per chi legge. Questo un gr ande vantaggio, ma bisogna far e attenzione ai blocchi di codice. Con questo ter mine ci si r ifer isce a par ti del sor gente compr ese tr a due par ole r iser vate, che in VB di solito sono accoppiate in questo modo: 1. [Keyword] 2. 'Blocco di codice 3. End [Keyword]
Ad esempio, tutto il codice compr eso tr a Sub ed End Sub costituisce un blocco, cos come lo costituisce quello compr eso tr a If ed End If (se non vi un Else), tr a If ed Else o addir ttur a tr a Module ed End Module. Facendo questa distinzione sar facile intuir e che una var iabile dichiar ata in un blocco no n v isibile al di fuor i di esso. Con questo voglio dir e che la sua dichiar azione vale solo all'inter no di quel blocco. Ecco una dimostr azione: 01. Module Module1 02. Sub Main() 03. 'a, b e c fanno parte del blocco delimitato da Sub ... 'End Sub 04. Dim a, b, c As Single 05. 06. 07. 'Semplice esempio di risoluzione di equazione di 08. 'secondo grado Console.WriteLine("Equazione: ax2 + bx + c = 0") 09. Console.WriteLine("Inserisci, in ordine, a, b e c:") 10. a = Console.ReadLine 11. b = Console.ReadLine 12. 13. c = Console.ReadLine 14. 15. If a = 0 Then 16. Console.WriteLine("L'equazione si abbassa di grado") 17. Console.ReadKey() 18. 'Con Exit Sub si esce dalla Sub, che in questo caso 'coincide con il programma. Equivale a terminare 19. 'il programma stesso 20. Exit Sub 21. End If 22. 23. 'Anche delta fa parte del blocco delimitato da Sub ... 24. 'End Sub 25. Dim delta As Single = b ^ 2 - 4 * a * c 26. 27. 'Esistono due soluzioni distinte 28. If delta > 0 Then 29. 'Queste variabili fanno parte del blocco di If ... 30. 'ElseIf 31. Dim x1, x2 As Single 32. ' possibile accedere senza problemi alla variabile 33. 'delta, poich questo blocco a sua volta 34. 'all'interno del blocco in cui dichiarato delta 35. x1 = (-b + Math.Sqrt(delta)) / (2 * a) 36. x2 = (-b - Math.Sqrt(delta)) / (2 * a) 37. Console.WriteLine("Soluzioni: ") 38. Console.WriteLine("x1 = " & x1) 39. Console.WriteLine("x2 = " & x2) 40. 41. 'Esiste una soluzione doppia 42. ElseIf delta = 0 Then 43. 'Questa variabile fa parte del blocco ElseIf ... Else 44. Dim x As Single 45. x = -b / (2 * a) 46. Console.WriteLine("Soluzione doppia: ") 47. Console.WriteLine("x = " & x) 48. 49. 'Non esistono soluzioni in R 50. Else 51. Console.WriteLine("Non esistono soluzioni in R") 52. End If 53. 54. Console.ReadKey() 55. End Sub 56. 57. End Module Se in questo codice, pr ima del Console.ReadKey(), finale pr ovassimo a usar e una fr a le var iabili x , x 1 o x 2, otter r emmo un er r or e:
Questo succede per ch nessuna var iabile dichiar ata all'inter no di un blocco accessibile al di fuor i di esso. Con questo schemino r udimentale sar pi facile capir e:
Le fr ecce ver di indicano che un codice pu acceder e a cer te var iabili, mentr e quelle r osse indicano che non vi pu acceder e. Come salta subito agli occhi, sono per messe tutte le r ichieste che vanno dall'inter no di un blocco ver so l'ester no, mentr e sono pr oibite tutte quelle che vanno dall'ester no ver so l'inter no. Questa r egola vale sempr e, in qualsiasi cir costanza e per qualsiasi tipo di blocco: non ci sono eccezioni.
In questo caso il costr utto If diventa non solo noioso, ma anche ingombr ante e disor dinato. Per eseguir e questo tipo di contr olli multipli esiste un costr utto apposito, Select Case, che ha questa sintassi: 01. '... 02. Select Case [Nome variabile da analizzare] Case [valore1] 03. 04. 'istruzioni Case [valore2] 05. 06. 'istruzioni 07. Case [valore3] 'istruzioni 08. 09. End Select Questo tipo di contr ollo r ende molto pi linear e, semplice e veloce il codice sor gente. Un esempio: 01. Module Module 1 02. Sub Main() 03. Dim a, b As Double 04. Dim C As Byte 05. 06. Console.WriteLine("Inserire due numeri: ") 07. a = Console.ReadLine 08. b = Console.ReadLine 09. Console.WriteLine("Inserire 1 per calcolare la somma, 2 per la differenza, 3 per il prodotto, 4 per il quoziente:") 10. C = Console.ReadLine 11. 12. Select Case C 13. Case 1 14. Console.WriteLine(a + b) 15. Case 2 16. Console.WriteLine(a - b) 17. Case 3 Console.WriteLine(a * b) 18. Case 4 19. 20. Console.WriteLine(a / b) End Select 21. 22. Console.ReadKey() 23. End Sub 24. 25. End Module Molto semplice, ma anche molto efficace, specialmente utile nei pr ogr ammi in cui bisogna consider ar e par ecchi valor i. Anche se nell'esempio ho utilizzato solamente numer i, possibile consider ar e var iabili di qualsiasi tipo, sia base (str inghe, date), sia der ivato (str uttur e, classi). Ad esempio: 1.
Dim S As String 2. '... 3. Select Case S 4. Case "ciao" 5. '... 6. Case "buongiorno" 7. '... 8. End Select
solo se A 2 o 3 solo se A 6 o 9
Il codice sopr a pr oposto con Select equivale ad un If scr itto come segue: 1. If A = 1 Or A = 2 Or A = 3 Then 2. '... 3. ElseIf A = 4 Or A = 6 Or A = 9 Then 4. '... 5. End If Uso di To Al contr ar io, la keyw or d To per mette di definir e un ran ge di valor i, ossia un inter vallo di valor i, per il quale la condizione r isulta ver ificata se la var iabile in analisi r icade in tale inter vallo. 1. Select Case A 2. Case 67 To 90 3. 'Questo codice viene eseguito solo se 4. 'contiene un valore compreso tra 67 e 5. Case 91 To 191 6. 'Questo codice viene eseguito solo se 7. 'contiene un valore compreso tra 91 e 8. End Select Questo cor r isponde ad un If scr itto come segue: 1. If A >= 67 And A <= 90 Then 2. '... 3. ElseIf A >= 91 And A <= 191 Then 4. '... 5. End If Uso di Is Is usato in questo contesto per ver ificar e delle condizioni facendo uso di nor mali oper ator i di confr onto (meggior e, minor e, diver so, ecceter a...). L'Is usato nel costr utto Select Case non ha assolutamente niente a che veder e con quello usato per ver ificar e l'identicit di due oggetti: ha lo stesso nome, ma la funzione completamente differ ente. 01.
Select Case A 02. Case Is >= 6 03. 'Questo codice viene eseguito solo se A 04. 'contiene un valore maggiore o uguale di 6 05. Case Is > 1 06. 'Questo codice viene eseguito solo se A 07. 'contiene un valore maggiore di 1 (e minore di 6, 08. 'dato che, se si arrivati a questo Case, 09. 'significa che la condizione del Case precedente non 10. ' stata soddisfatta) 11. End Select Il suo equivalente If: 1. If A >= 6 Then 2. '... 3. ElseIf A > 1 Then 4. '... 5. End If Uso di Else Anche nel Select lecito usar e Else: il Case che include questa istr uzione solitamente l'ultimo di tutte le alter native possibili e pr escr ive di eseguir e il codice che segue solo se tutte le altr e condizioni non sono state soddisfatte: 01. Select Case A 02. Case 1, 4 03. 'Questo codice viene eseguito solo se A 04. 'contiene 1 o 4 05. Case 9 To 12 06. 'Questo codice viene eseguito solo se A 07. 'contiene un valore compreso tra 9 e 12 08. Case Else 09. 'Questo codice viene eseguito solo se A 10. 'contiene un valore minore di 9 o maggiore di 12, 11. 'ma diverso da 1 e 4 12. End Select Uso delle pr ecedenti alter nativ e in co m binazione Tutti i modi illustr ati fino ad or a possono esser e uniti in un solo Case per ottener e potenti condizioni di contr ollo: 1. Select Case A 2. Case 7, 9, 10 To 15, Is >= 90 3. 'Questo codice viene eseguito solo se A 4. 'contiene 7 o 9 o un valore compreso tra 10 e 15 5. 'oppure un valore maggiore o uguale di 90 6. Case Else 7. '... 8. End Select
01. Module Module1 02. Sub Main() 03. Dim a As Int32 = 0 04. Do 05. 06. a += 1 07. Loop While (a < 2) And (a > 0) 08. Console.WriteLine(a) 09. 10. Console.ReadKey() 11. End Sub 12. End Module Il codice scr iver a scher mo "2". 1. Do While [condizione] 2. 'istruzioni 3. Loop Esegue le istr uzioni specificate fintanto che una condizione r imane valida, ma se la condizione non valida all'inizio, non viene eseguita nessuna istr uzione nel blocco. Esempio: 01. Module Module1 02. Sub Main() Dim a As Int32 = 0 03. 04. Do While (a < 2) And (a > 0) 05. a += 1 06. 07. Loop Console.WriteLine(a) 08. 09. Console.ReadKey() 10. End Sub 11. 12. End Module Il codice scr iver a scher mo "0". Bisogna notar e come le stesse condizioni del caso pr ecedente, spostate da dopo Loop a dopo Do, cambino il r isultato di tutto l'algor itmo. In questo caso, il codice nel ciclo non viene neppur e eseguito per ch la condizione nel While diventa subito falsa (in quanto a = 0, e la pr oposizione "a < 0" r isulta falsa). Nel caso pr ecedente, invece, il blocco veniva eseguito almeno una volta poich la condizione di contr ollo si tr ovava dopo di esso: in quel caso, a er a or mai stato incr ementato di 1 e per ci soddisfaceva la condizione affinch il ciclo continuasse (fino ad ar r ivar e ad a = 2, che er a il r isultato visualizzato). 1. Do 2. 'istruzioni 3. Loop Until [condizione] Esegue le istr uzioni specificate fino a che non viene ver ificata la condizione, ma tutte le istr uzioni vengono eseguite almeno una volta, poich Until si tr ova dopo Do. Esempio: 01. Module Module1 02. Sub Main() 03. Dim a As Int32 = 0 04. 05. Do 06. a += 1 07. Loop Until (a <> 1) 08. Console.WriteLine(a) 09. 10. Console.ReadKey() 11. End Sub 12. End Module A scher mo appar ir "2". 1. Do Until [condizione] 2. 'istruzioni 3.
Loop Esegue le istr uzioni specificate fino a che non viene soddisfatta la condizione, ma se la condizione valida all'inizio, non viene eseguita nessuna istr uzione del blocco. Esempio: 01. Module Module1 02. Sub Main() 03. Dim a As Int32 = 0 04. 05. Do Until (a <> 1) 06. a += 1 07. Loop 08. Console.WriteLine(a) 09. 10. Console.ReadKey() 11. End Sub 12. End Module A scher mo appar ir "0". Un piccolo esempio finale: 01. Module Module1 02. Sub Main() 03. Dim a, b, c As Int32 04. Dim n As Int32 05. 06. Console.WriteLine("-- Successione di Fibonacci --") 07. Console.WriteLine("Inserire un numero oltre il quale terminare:") 08. n = Console.ReadLine 09. 10. If n = 0 Then 11. Console.WriteLine("Nessun numero della successione") 12. Console.ReadKey() 13. Exit Sub 14. End If 15. 16. a = 1 17. b = 1 18. Console.WriteLine(a) 19. Console.WriteLine(b) 20. Do While c < n 21. c = a + b 22. b = a a = c 23. 24. Console.WriteLine(c) 25. Loop 26. Console.ReadKey() 27. 28. End Sub 29. End Module
Suggerimen to Per impostar e il valor e di Default (ossia il valor e pr edefinito) di una var iabile si pu usar e questa sintassi: 1. Dim [nome] As [tipo] = [valore] Funziona solo per una var iabile alla volta. Questo tipo di istr uzione si chiama in izializzazion e in -lin e.
For a = b To b + 20 2. If a Mod 2 = 0 Then 3. Console.WriteLine(a) 4. End If 5. Next Il secondo, invece, pi elegante e usa una ver sione "ar r icchita" della str uttur a iter ativa For , nella quale viene specificato che l'incr emento del contator e non deve pi esser e 1, ma bens 2: 1. For a = b To b + 20 Step 2 2. Console.WriteLine(a) 3. Next Infatti, la par ola r iser vata Step posta dopo il numer o a cui ar r ivar e (in questo caso b+20) indica di quanto deve esser e aumentata la var iabile contator e del ciclo (in questo caso a) ad ogni step. L'incr emento pu esser e un valor e inter o, decimale, positivo o negativo, ma, cosa impor tante, deve sempr e appar tener e al r aggio d'azione del tipo del contator e: ed esempio, non si pu dichiar ar e una var iabile contator e di tipo Byte e un incr emento di -1, poich Byte compr ende solo numer i positivi (invece possibile far lo con SByte, che va da -127 a 128). Allo stesso modo non si dovr ebber o specificar e incr ementi decimali con contator i inter i. Suggerimen to Se non si vuole cr ear e una var iabile apposta per esser e contator e di un ciclo for , si pu inzializzar e dir ettamente una var iabile al suo inter no in questo modo: 1. 2. 3. 4. 5. 6. 7. For [variabile] As [tipo] = [valore] To [numero] 'istruzioni Next 'Che, se volessimo descrivere con un esempio, diverrebbe cos: For H As Int16 = 78 To 108 'istruzioni Next
Allo stesso modo, un ar r ay un insieme di scatole, tutte una vicina all'altr a (tanto nell'esempio quando nella posizione fisica all'inter no della memor ia), a for mar e un'unica fila che per comodit si indica con un solo nome. Per distinguer e ogni "scompar to" si fa uso di un numer o inter o (che per convenzione un inter o a 32 bit, ossia Integer ), detto indice. Tutti i linguaggi .NET utilizzano sempr e un indice a base 0: ci significa che si inizia a contar e da 0 anzich da 1:
La sintassi usata per dichiar ar e un ar r ay simile a quella usata per dichiar ar e una singola var iabile: 1. Dim [nome]([numero elementi - 1]) As [tipo] La differ enza tr a le due r isiede nelle par entesi tonde che vengono poste dopo il nome della var iabile. Tr a queste
par entesi pu anche esser e specificato un numer o (sempr e inter o, ovviamente) che indica l'indice massimo a cui si pu ar r ivar e: dato che, come abbiamo visto, gli indici sono sempr e a base 0, il numer o effettivo di elementi pr esenti nella collezione sar di un'unit super ior e r ispetto all'indice massimo. Ad esempio, con questo codice: 1. Dim A(5) As String il pr ogr ammator e indica al pr ogr amma che la var iabile A un ar r ay contenente questi elementi: 1. A(0), A(1), A(2), A(3), A(4), A(5) che sono per la pr ecisione 6 elementi. Ecco un listato che esemplifica i concetti fin'or a chiar iti: 01. Module Module1 02. Sub Main() 03. 'Array che contiene 10 valori decimali, rappresentanti voti 04. Dim Marks(9) As Single 05. 'Questa variabile terr traccia di quanti voti 06. 'l'utente avr immesso da tastiera e permetter di 07. 'calcolarne una media 08. Dim Index As Int32 = 0 09. 10. 'Mark conterr il valore temporaneo immesso 11. 'da tastiera dall'utente 12. Dim Mark As Single 13. Console.WriteLine("Inserisci un altro voto (0 per terminare):") 14. Mark = Console.ReadLine 15. 16. 'Il ciclo finisce quando l'utente immette 0 oppure quando 17. 'si raggiunto l'indice massimo che 18. 'possibile usare per identificare una cella dell'array Do While (Mark > 0) And (Index < 10) 19. 20. 'Se il voto immesso maggiore di 0, lo memorizza 21. 'nell'array e incrementa l'indice di 1, cos da 22. 'poter immagazzinare correttamente il prossimo voto nell'array Marks(Index) = Mark 23. Index += 1 24. 25. Console.WriteLine("Inserisci un altro voto (0 per terminare):") 26. Mark = Console.ReadLine 27. Loop 28. 'Decrementa l'indice di 1, poich anche se l'utente 29. 'ha immesso 0, nel ciclo precedente, l'indice era stato 30. 'incrementato prevedendo un'ulteriore immissione, che, 31. 'invece, non c' stata 32. Index -= 1 33. 34. 'Totale dei voti 35. Dim Total As Single = 0 36. 'Usa un ciclo For per scorrere tutte le celle dell'array 37. 'e sommarne i valori 38. For I As Int32 = 0 To Index 39. Total += Marks(I) 40. Next 41. 42. 'Mostra la media 43. Console.WriteLine("La tua media : " & (Total / (Index + 1))) 44. Console.ReadKey() 45. End Sub 46. 47. End Module Il codice potr ebbe non appar ir e subito chiar o a pr ima vista, ma attr aver so uno sguar do pi attento, tutto si far pi limpido. Di seguito scr itto il flusso di elabor azione del pr ogr amma ammettendo che l'utente immetta due voti: Richiede un voto da tastier a: l'utente immette 5 (Mar k = 5) Mar k maggior e di 0 Inser isce il voto nell'ar r ay: Mar ks(Index ) = Mar ks(0) = 5 Incr ementa Index di 1: Index = 1
Entr ambe le condizioni non sono ver ificate: Mar k <> 0 e Index < 9. Il ciclo continua Richiede un voto da tastier a: l'utente immette 10 (Mar k = 10) Mar k maggior e di 0 Inser isce il voto nell'ar r ay: Mar ks(Index ) = Mar ks(1) = 10 Incr ementa Index di 1: Index = 2 Entr ambe le condizioni non sono ver ificate: Mar k <> 0 e Index < 9. Il ciclo continua Richiede un voto da tastier a: l'utente immette 0 (Mar k = 0) Mar k uguale a 0: il codice dentr o if non viene eseguito Una delle condizioni di ar r esto ver ificata: Mar k = 0. Il ciclo ter mina Decr ementa Index di 1: Index = 1 Somma tutti i valor i in Mar ks da 0 a Index (=1): Total = Mar ks(0) + Mar ks(1) = 5 + 10 Visualizza la media: Total / (Index + 1) = 15 / (1 + 1) = 15 / 2 = 7.5 Attende la pr essione di un tasto per uscir e anche possibile dichiar ar e ed inizializzar e (ossia r iempir e) un ar r ay in una sola r iga di codice. La sintassi usata la seguente: 1. Dim [nome]() As [tipo] = {elementi dell'array separati da virgole} Ad esempio: 1. Dim Parole() As String = {"ciao", "mouse", "penna"} Questa sintassi br eve equivale a questo codice: 1. 2. 3. 4. Dim Parole(2) As String Parole(0) = "ciao" Parole(1) = "mouse" Parole(2) = "penna"
Un'ulter ior e sintassi usata per dichiar ar e un ar r ay la seguente: 1. Dim [nome] As [tipo]() Quest'ultima, come vedr emo, sar par ticolar mente utile nel gestir e il tipo r estituito da una funzione.
Array a pi dimensioni
Gli ar r ay a una dimensione sono contr addistinti da un singolo indice: se volessimo par agonar li ad un ente geometr ico, sar ebber o assimilabili ad una r etta, estesa in una sola dimensione, in cui ogni punto r appr esenta una cella dell'ar r ay. Gli ar r ay a pi dimensioni, invece, sono contr addistinti da pi di un indice: il numer o di indici che identifica univocamente un elemento dell'ar r ay di dice r ang o . Un ar r ay di r ango 2 (a 2 dimensioni) potr , quindi, esser e par agonato a un piano, o ad una gr iglia di scatole estesa in lunghezza e in lar ghezza. La sintassi usata : 1. Dim [nome]( , ) As [tipo] 'array di rango 2 2. Dim [nome]( , , ) As [tipo] 'array di rango 3 Ecco un esempio che consider a un ar r ay di r ango 2 come una matr ice quadr ata: 01. Module Module1 02. Sub Main() 03. 'Dichiara e inizializza un array di rango 2. Dato che 04. 'in questo caso abbiamo due dimensioni, e non una sola, 05. 'non si pu specificare una semplice lista di 06. 'valori, ma una specie di "tabella" a due entrate. 07. 'Nell'esempio che segue, ho creato una semplice 08. 'tabella a due righe e due colonne, in cui ogni cella 09. ' 0. 10.
Dim M(,) As Single = _ 11. {{0, 0}, _ 12. {0, 0}} 13. 'Bisogna notare il particolare uso delle graffe: si 14. 'considera l'array di rango 2 come un array i cui 15. 'elementi sono altri array 16. 17. Console.WriteLine("Inserire gli elementi della matrice:") 18. For I As Int32 = 0 To 1 19. For J As Int32 = 0 To 1 20. Console.Write("Inserire l'elemento (" & I & ", " & J & "): ") 21. M(I, J) = Console.ReadLine 22. Next 23. Next 24. 25. Dim Det As Single 26. Det = M(0, 0) * M(1, 1) - M(0, 1) * M(1, 0) 27. Console.WriteLine("Il determinante della matrice : " & Det) 28. Console.ReadKey() 29. 30. End Sub 31. End Module Rappr esentando gr aficamente l'ar r ay M, potr emmo disegnar lo cos:
Come si vede dal codice di inizializzazione, seppur concettualmente diver si, i due modi di veder e un ar r ay sono compatibili. Tuttavia, bisogna chiar ir e che so lo e so ltanto in questo caso, le due visioni sono conciliabili, poich un ar r ay di r ango 2 e un ar r ay di ar r ay sono, dopo tutto, due entit ben distinte. Infatti, esiste un modo per dichiar ar e ar r ay di ar r ay, come segue: 1. Dim [nome]()() As [tipo] 'array di array E se si pr ova a far e una cosa del gener e: 1. 2. 3. 4. Dim A(,) As Int32 Dim B()() As Int32 '... A = B
Ridimensionare un array
Pu capitar e di dover modificar e la lunghezza di un ar r ay r ispetto alla dichiar azione iniziale. Per far e questo, si usa la par ola r iser vata ReDim, da non confonder e con la keyw or d Dim: hanno due funzioni totalmente differ enti. Quando si r idimensiona un ar r ay, tutto il suo contenuto viene cancellato: per evitar e questo inconveniente, si deve usar e l'istr uzione ReDim Pres erve, che tuttavia ha pr estazioni molto scar se a causa dell'eccessiva lentezza. Entr ambe le istr uzioni der ivano dal Visual Basic classico e non fanno par te, per tanto, della sintassi .NET, sebbene continuino ad esser e molto usate, sia per comodit, sia per abitudine. Il metodo pi cor r etto da adottar e consiste nell'usar e la pr ocedur a Ar r ay.Resize. Eccone un esempio: 01. Module Module1 02. Sub Main() 03. Dim A() As Int32 04. Dim n As Int32 05. 06. Console.WriteLine("Inserisci un numero") 07. n = Console.ReadLine 08. 09. 'Reimposta la lunghezza di a ad n elementi Array.Resize(A, n) 10. 11. 12. 'Calcola e memorizza i primi n numeri pari (zero compreso) 13. For I As Int16 = 0 To n - 1 14. A(I) = I * 2 15. Next 16. 17. Console.ReadKey() 18. End Sub 19. End Module La r iga Ar r ay.Resize(A, n) equivale, usando ReDim a: 1. ReDim A(n - 1) Per r idimensionar e un ar r ay a pi dimensioni, la faccenda si fa abbastanza complessa. Per pr ima cosa, non si pu utilizzar e Ar r ay.Resize a meno che non si utilizzi un ar r ay di ar r ay, ma anche in quel caso le cose non sono semplici. Infatti, possibile stabilir e la lunghezza di una sola dimensione alla volta. Ad esempio, avendo un ar r ay M di r ango 2 con nove elementi, r aggr uppati in 3 r ighe e 3 colonne, non si pu semplicemente scr iver e: 1. ReDim M(2, 2)
per ch, cos facendo, solo la r iga 2 ver r r idimensionata a 3 elementi, mentr e la 0 e la 1 sar anno vuote. Il codice da usar e, quindi, : 1. ReDim M(0, 2) 2. ReDim M(1, 2) 3. ReDim M(2, 2) In questo modo, ogni "r iga" viene aggiustata alla lunghezza giusta.
'A e B sono due array di interi 04. Dim A() As Int32 = {1, 2, 3} 05. Dim B() As Int32 = {4, 5, 6} 06. 07. 'Ora A e B sono due oggetti diversi e contengono 08. 'numeri diversi. Questa riga stamper sullo 09. 'schermo "False", infatti A Is B = False 10. Console.WriteLine(A Is B) 11. 'Adesso poniamo A uguale a B. Dato che gli array 12. 'sono un tipo reference, da ora in poi, entrambi 13. 'saranno lo stesso oggetto 14. A = B 15. 'Infatti questa istruzione stamper a schermo 16. ''"True", poich A Is B = True 17. Console.WriteLine(A Is B) 18. 'Dato che A e B sono lo stesso oggetto, se modifichiamo 19. 'un valore dell'array riferendoci ad esso con il nome 20. 'B, anche richiamandolo con A, esso mostrer 21. 'che l'ultimo elemento lo stesso 22. B(2) = 90 23. 'Su schermo apparir 90 24. Console.WriteLine(A(2)) 25. 26. Dim C() As Int32 = {7, 8, 9} 27. B = C 'Ora cosa succede? 28. 29. Console.ReadKey() 30. End Sub 31. 32. End Module Ecco come appar e la memor ia dopo l'assegnazione A = B:
Come si vede, le var iabili contengono solo l'indir izzo degli oggetti effettivi, per ci ogni singola var iabile (A, B o C) pu puntar e allo stesso oggetto ma anche a oggetti diver si: se A = B e B = C, non ver o che A = C, come si vede dal gr afico. L'indir izzo di memor ia contenuto in A non cambia se non si usa esplicitamente un oper ator e di assegnamento. Se state leggendo la guida un capitolo alla volta, potete fer mar vi qui: il pr ossimo par agr afo utile solo per consultazione.
Manipolazione di array
La classe System.Ar r ay contiene molti metodi statici utili per la manipolazione degli ar r ay. I pi usati sono: Clear (A, I, L) : cancella L elementi a par tir e dalla posizione I nell'ar r ay A Clone() : cr ea una coppia esatta dell'ar r ay Constr ainedCopy(A1, I1, A2, I2, L) : copia L elementi dall'ar r ay A1 a par tir e dall'indice I1 nell'ar r ay A2, a par tir e dall'indice I2; se la copia non ha successo, ogni cambiamento sar annullato e l'ar r ay di destinazione non subir alcun danno Copy(A1, A2, L) / CopyTo(A1, A2) : il pr imo metodo copia L elementi da A1 a A2 a par tir e dal pr imo, mentr e il secondo fa una copia totale dell'ar r ay A1 e la deposita in A2 Find / FindLast (A, P(Of T)) As T : cer ca il pr imo elemento dell'ar r ay A per il quale la funzione gener ic Of T assegnata al delegate P r estituisce un valor e Tr ue, e ne r itor na il valor e Find(A, P(Of T)) As T() : cer ca tutti gli elementi dell'ar r ay A per i quali la funzione gener ic Of T assegnata al delegate P r estituisce un valor e Tr ue FindIndex / FindLastIndex (A, P(Of T)) As Int32 : cer ca il pr imo o l'ultimo elemento dell'ar r ay A per il quale la funzione gener ic Of T assegnata al delegate P r estituisce un valor e Tr ue, e ne r itor na l'indice For Each(A(Of T)) : esegue un'azione A deter minata da un delegate Sub per ogni elemento dell'ar r ay GetLength(A) : r estituisce la dimensione dell'ar r ay Index Of(A, T) / LastIndex Of(A, T) : r estituisce il pr imo o l'ultimo indice dell'oggetto T nell'ar r ay A Rever se(A) : inver te l'or dine di tutti gli elementi nell'ar r ay A Sor t(A) : or dina alfabeticamente l'ar r ay A. Esistono 16 ver sioni di questa pr ocedur a, tr a le quali una accetta
come secondo par ametr o un oggetto che implementa un'inter faccia ICompar er che per mette di decider e come or dinar e l'ar r ay Molti di questi metodi, come si visto, compr endono ar gomenti molto avanzati: quando sar ete in gr ado di compr ender e i Gener ics e i Delegate, r itor nate a far e un salto in questo capitolo: scopr ir ete la potenza di questi metodi.
Sub Esempio() 4. 'istruzioni 5. End Sub 6. 'istruzioni 7. End Sub 8. End Module Allo stesso modo, i metodi sono l'unica categor ia, oltr e alle pr opr iet e agli oper ator i, a poter contener e delle istr uzioni: sono str umenti "attivi" di pr ogr ammazione e solo lor o possono eseguir e istr uzioni. Quindi astenetevi dallo scr iver e un abominio del gener e: 1. Module Module1 2. Sub Main() 3. 'istruzioni 4. End Sub 5. Console.WriteLine() 6. End Sub E' totalmente e concettualmente sbagliato. Ma or a veniamo al dunque con un esempio: 01. Module Module1 02. 'Dichiarazione di una procedura: il suo nome "FindDay", il 03. 'suo elenco di parametri vuoto, e il suo corpo 04. 'rappresentato da tutto il codice compreso tra "Sub FindDay()" 05. 'ed "End Sub". 06. Sub FindDay() 07. Dim StrDate As String 08. Console.Write("Inserisci giorno (dd/mm/yyyy): ") 09. StrDate = Console.ReadLine 10. 11. Dim D As Date 12. 'La funzione Date.TryParse tenta di convertire la stringa 13. 'StrDate in una variabile di tipo Date (che un tipo 14. 'base). Se ci riesce, ossia non ci sono errori nella 15. 'data digitata, restituisce True e deposita il valore 16. 'ottenuto in D; se, al contrario, non ci riesce, 17. 'restituisce False e D resta vuota. 18. 'Quando una data non viene inizializzata, dato che un 'tipo value, contiene un valore predefinito, il primo 19. 'Gennaio dell'anno 1 d.C. a mezzogiorno in punto. 20. 21. If Date.TryParse(StrDate, D) Then 'D.DayOfWeek contiene il giorno della settimana di D 22. '(luned, marted, eccetera...), ma in un 23. 'formato speciale, l'Enumeratore, che vedremo nei 24. 'prossimi capitoli. 25. 'Il ".ToString()" converte questo valore in una 26. 'stringa, ossia in un testo leggibile: i giorni della 27. 'settimana, per, sono in inglese 28. Console.WriteLine(D.DayOfWeek.ToString()) 29. Else 30. Console.WriteLine(StrDate & " non una data valida!") 31. End If 32. End Sub 33. 34. 'Altra procedura, simile alla prima 35. Sub CalculateDaysDifference() 36. Dim StrDate1, StrDate2 As String 37. Console.Write("Inserisci il primo giorno (dd/mm/yyyy): ") 38. StrDate1 = Console.ReadLine 39. Console.Write("Inserisci il secondo giorno (dd/mm/yyyy): ") 40. StrDate2 = Console.ReadLine 41. 42. Dim Date1, Date2 As Date 43. 44. If Date.TryParse(StrDate1, Date1) And _ 45. Date.TryParse(StrDate2, Date2) Then 46. 'La differenza tra due date restituisce il tempo 47. 'trascorso tra l'una e l'altra. In questo caso noi 48. 'prendiamo solo i giorni 49. Console.WriteLine((Date2 - Date1).Days) 50. 51.
52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91. 92. 93. 94. 95. End
Console.WriteLine("Inserire due date valide!") End If End Sub Sub Main() 'Command una variabile di tipo char (carattere) che 'conterr una lettera indicante quale compito eseguire Dim Command As Char Do Console.Clear() Console.WriteLine("Qualche operazione con le date:") Console.WriteLine("- Premere F per sapere in che giorno " & _ "della settimana cade una certa data;") Console.WriteLine("- Premere D per calcolare la differenza tra due date;") Console.WriteLine("- Premere E per uscire.") 'Console.ReadKey() la funzione che abbiamo sempre 'usato fin'ora per fermare il programma in attesa della 'pressione di un pulsante. Come vedremo fra breve, non ' necessario usare il valore restituito da una 'funzione, ma in questo caso ci serve. Ci che 'ReadKey restituisce qualcosa che non ho ancora 'trattato. Per ora basti sapere che 'Console.ReadKey().KeyChar contiene l'ultimo carattere 'premuto sulla tastiera Command = Console.ReadKey().KeyChar 'Analizza il valore di Command Select Case Command Case "f" 'Invoca la procedura FindDay() FindDay() Case "d" 'Invoca la procedura CalculateDaysDifference() CalculateDaysDifference() Case "e" 'Esce dal ciclo Exit Do Case Else Console.WriteLine("Comando non riconosciuto!") End Select Console.ReadKey()
Else
In questo pr imo caso, le due pr ocedur e dichiar ate sono effettivamente sottopr ogr ammi a s stanti: non hanno nulla in comune con il modulo (eccetto il semplice fatto di esser ne membr i), n con Main, ossia non scambiano alcun tipo di infor mazione con essi; sono come degli ingr anaggi sigillati all'inter no di una scatola chiusa. A questo r iguar do, bisogna inser ir e una pr ecisazione sulle var iabili dichiar ate ed usate all'inter no di un metodo, qualsiasi esso sia. Esse si dicono lo cali o tem po r anee, poich esistono solo all'inter no del metodo e vengono distr utte quando il flusso di elabor azione ne r aggiunge la fine. Anche sotto questo aspetto, si pu notar e come le pr ocedur e appena stilate siano par ticolar mente chiuse e r estr ittive. Tuttavia, si pu benissimo far inter agir e un metodo con oggetti ed entit ester ne, e questo appr occio decisamente pi utile che non il semplice impacchettar e ed etichettar e blocchi di istr uzioni in locazioni distinte. Nel pr ossimo esempio, la pr ocedur a attinge dati dal modulo, poich in esso dichiar ata una var iabile a livello di classe. 01. Module Module1 02. 'Questa variabile dichiarata a livello di classe 03. '(o di modulo, in questo caso), perci accessibile 04. 'a tutti i membri del modulo, sempre seguendo il discorso 05. 'dei blocchi di codice fatto in precedenza 06. Dim Total As Single = 0 07. 08. 'Legge un numero da tastiera e lo somma al totale 09. Sub Sum() 10. Dim Number As Single = Console.ReadLine 11.
12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. End
Total += Number End Sub 'Legge un numero da tastiera e lo sottrae al totale Sub Subtract() Dim Number As Single = Console.ReadLine Total -= Number End Sub 'Legge un numero da tastiera e divide il totale per 'tale numero Sub Divide() Dim Number As Single = Console.ReadLine Total /= Number End Sub 'Legge un numero da tastiera e moltiplica il totale 'per tale numero Sub Multiply() Dim Number As Single = Console.ReadLine Total *= Number End Sub Sub Main() 'Questa variabile conterr il simbolo matematico 'dell'operazione da eseguire Dim Operation As Char Do Console.Clear() Console.WriteLine("Risultato attuale: " & Total) Operation = Console.ReadKey().KeyChar Select Case Operation Case "+" Sum() Case "-" Subtract() Case "*" Multiply() Case "/" Divide() Case "e" Exit Do Case Else Console.WriteLine("Operatore non riconosciuto") Console.ReadKey() End Select Loop End Sub Module
esempio, r iscr ivendo l'ultimo codice pr oposto nel par agr afo pr ecedente con l'aggiunta dei par ametr i: 01. Module Module1 02. Dim Total As Single = 0 03. Sub Sum(ByVal Number As Single) 04. Total += Number 05. 06. End Sub 07. 08. Sub Subtract(ByVal Number As Single) Total -= Number 09. End Sub 10. 11. 12. Sub Divide(ByVal Number As Single) 13. Total /= Number End Sub 14. 15. 16. Sub Multiply(ByVal Number As Single) 17. Total *= Number 18. End Sub 19. Sub Main() 20. 21. 'Questa variabile conterr il simbolo matematico 'dell'operazione da eseguire 22. Dim Operation As Char 23. Do 24. Console.Clear() 25. Console.WriteLine("Risultato attuale: " & Total) 26. Operation = Console.ReadKey().KeyChar 27. Select Case Operation 28. 'Se si tratta di simboli accettabili 29. Case "+", "-", "*", "/" 30. 'Legge un numero da tastiera 31. Dim N As Single = Console.ReadLine 32. 'E a seconda dell'operazione, utilizza una 33. 'procedura piuttosto che un'altra 34. Select Case Operation 35. Case "+" 36. Sum(N) 37. Case "-" 38. Subtract(N) 39. Case "*" 40. Multiply(N) 41. Case "/" 42. Divide(N) 43. End Select 44. Case "e" 45. Exit Do 46. Case Else 47. Console.WriteLine("Operatore non riconosciuto") 48. Console.ReadKey() 49. End Select 50. Loop 51. End Sub 52. 53. End Module Richiamando, ad esempio, Sum(N) si invoca la pr ocedur a Sum e si assegna al par ametr o Number il valor e di N: quindi, Number viene sommato a Total e il ciclo continua. Number , per ci, un "segnaposto", che r iceve solo dur ante l'esecuzione un valor e pr eciso, che pu anche esser e, come in questo caso, il contenuto di un'altr a var iabile. Nel ger go tecnico, Number - ossia, pi in gener ale, l'identificator e dichiar ato nell'elenco dei par ametr i - si dice par am etr o for m ale, mentr e N - ossia ci che viene concr etamente pas s ato al metodo - si dice par am etr o attuale. Non ho volutamente assegnato al par ametr o attuale lo stesso nome di quello for male, anche se del tutto lecito far lo: ho agito in questo modo per far capir e che non necessar io nessun legame par ticolar e tr a i due; l'unico vincolo che deve sussister e r isiede nel fatto che par ametr o for male ed attuale abbiano lo stesso tipo. Quest'ultima asser zione, del r esto, abbastanza ovvia: se r ichiamassimo Sum("ciao") come far ebbe il pr ogr amma a sommar e una str inga ("ciao") ad un numer o (Total)?
Or a pr oviamo a modificar e il codice pr ecedente r iassumendo tutte le oper azioni in una sola pr ocedur a, a cui, per , vengono passati due par ametr i: il numer o e l'oper ator e da usar e. 01. Module Module1 02. Dim Total As Single = 0 03. Sub DoOperation(ByVal Number As Single, ByVal Op As Char) 04. Select Case Op 05. 06. Case "+" 07. Total += Number 08. Case "-" Total -= Number 09. Case "*" 10. 11. Total *= Number 12. Case "/" 13. Total /= Number End Select 14. 15. End Sub 16. 17. Sub Main() 18. Dim Operation As Char Do 19. Console.Clear() 20. Console.WriteLine("Risultato attuale: " & Total) 21. Operation = Console.ReadKey().KeyChar 22. Select Case Operation 23. Case "+", "-", "*", "/" 24. Dim N As Single = Console.ReadLine 25. 'A questa procedura vengono passati due 26. 'parametri: il primo il numero da 27. 'aggiungere/sottrarre/moltiplicare/dividere 28. 'a Total; il secondo il simbolo 29. 'matematico che rappresenta l'operazione 30. 'da eseguire 31. DoOperation(N, Operation) 32. Case "e" 33. Exit Do 34. Case Else 35. Console.WriteLine("Operatore non riconosciuto") 36. Console.ReadKey() 37. End Select 38. Loop 39. End Sub 40. 41. End Module
End Module Per pr ovar lo, potete usar e cmd.ex e, il pr ompt dei comandi. Io ho digitato: 1. CD "C:\Users\Totem\Documents\Visual Studio 2008\Projects\ConsoleApplication2\bin\Debug" 2. ConsoleApplication2.exe Totem La pr ima istr uzione per cambiar e la dir ector y di lavor o, la seconda l'invocazione ver a e pr opr ia del pr ogr amma, dove "Totem" l'unico ar gomento passatogli: una volta pr emuto invio, appar ir il messaggio "Ciao Totem!". In alter nativa, possibile specificar e gli ar gomenti passati nella casella di testo "Command line ar guments" pr esente nella scheda Debug delle pr opr iet di pr ogetto. Per acceder e alle pr opr iet di pr ogetto, cliccate col pulsante destr o sul nome del pr ogetto nella finestr a a destr a, quindi scegliete Pr oper ties e r ecatevi alla tabella Debug:
End Sub 07. 08. Sub Main() 09. Test(A) Console.ReadKey() 10. 11. End Sub 12. End Module Se ByVal modificasse il compor tamento degli oggetti, allor a N conter r ebbe una copia di A, ossia un altr o oggetto semplicemente uguale, ma non identico. Invece, a scher mo appar e la scr itta "Tr ue", che significa "Ver o", per ci N e A sono lo stesso oggetto, anche se N er a pr eceduto da ByVal.
Le funzioni
Le funzioni sono simili alle pr ocedur e, ma possiedono qualche car atter istica in pi. La lor o sintassi la seguente: 1. Function [name]([elenco parametri]) As [tipo] 2. '... 3. Return [risultato] 4. End Function La pr ima differ enza che salta all'occhio l'As che segue l'elenco dei par ametr i, come a sugger ir e che la funzione sia di un cer to tipo. Ad esser e pr ecisi, quell'As non indica il tipo della funzione, ma piuttosto quello del suo r isultato. Infatti, le funzioni r estituiscono qualcosa alla fine del lor o ciclo di elabor azione. Per questo motivo, pr ima del ter mine del suo cor po, deve esser e posta almeno un'istr uzione Retur n, seguita da un qualsiasi dato, la quale for nisce al chiamante il ver o r isultato di tutte le oper azioni eseguite. Non un er r or e scr iver e funzioni pr ive dell'istr uzione Retur n, ma non avr ebbe comunque senso: si dovr ebbe usar e una pr ocedur a in quel caso. Ecco un esempio di funzione: 01. Module Module1 02. 'Questa funzione calcola la media di un insieme 03. 'di numeri decimali passati come array 04. Function Average(ByVal Values() As Single) As Single 05. 'Total conterr la somma totale di tutti 06. 'gli elementi di Values 07. Dim Total As Single = 0 08. 'Usa un For Each per ottenere direttamente i valori 09. 'presenti nell'array piuttosto che enumerarli 10. 'attraverso un indice mediante un For normale 11. For Each Value As Single In Values 12. Total += Value 13. Next 14. 'Restituisce la media aritmetica, ossia il rapporto 15. 'tra la somma totale e il numero di elementi 16. Return (Total / Values.Length) 17. End Function 18. 19. Sub Main(ByVal Args() As String) 20. Dim Values() As Single = {1.1, 5.2, 9, 4, 8.34} 21. 'Notare che in questo caso ho usato lo stesso nome 22. 'per il parametro formale e attuale Console.WriteLine("Media: " & Average(Values)) 23. Console.ReadKey() 24. 25. End Sub 26. End Module E un altr o esempio in cui ci sono pi Retur n: 01. Module Module1 02. Function Potenza(ByVal Base As Single, ByVal Esponente As Byte) As Double 03. Dim X As Double = 1 04. 05. If Esponente = 0 Then 06. Return 1 07. Else 08. If Esponente = 1 Then 09.
10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. End
For i As Byte = 1 To Esponente X *= Base Next Return X End If End If End Function Sub Main() Dim F As Double Dim b As Single Dim e As Byte Console.WriteLine("Inserire base ed esponente:") b = Console.ReadLine e = Console.ReadLine F = Potenza(b, e) Console.WriteLine(b & " elevato a " & e & " vale " & F) Console.ReadKey() End Sub Module
Else
Return Base
In quest'ultimo esempio, il cor po della funzione contiene ben tr e Retur n, ma ognuno appar tiene a un path di co dice differ ente. Path significa "per cor so" e la locuzione appena usata indica il flusso di elabor azione seguito dal pr ogr amma per deter minati valor i di Base ed Esponente. Disegnando un diagr amma di flusso della funzione, sar facile capir e come ci siano tr e per cor si differ enti, ossia quando l'esponente vale 0, quando vale 1 e quando maggior e di 1. sintatticamente lecito usar e due Retur n nello stesso path, o addir ittur a uno dopo l'altr o, ma non ha nessun senso logico: Retur n, infatti, non solo r estituisce un r isultato al chiamante, ma ter mina anche l'esecuzione della funzione. A questo pr oposito, bisogna dir e che esiste anche lo statement (=istr uzione) Exit Fun ction , che for za il pr ogr amma ad uscir e immediatamente dal cor po della funzione: inutile dir e che abbastanza per icoloso da usar e, poich si cor r e il r ischio di non r estituir e alcun r isultato al chiamante, il che pu tr adur si in un er r or e in fase di esecuzione. Come ultima postilla vor r ei aggiunger e che, come per le var ibili, non str ettamente necessar io specificar e il tipo del valor e r estituito dalla funzione, anche se for temente consigliato: in questo caso, il pr ogr amma suppor r che si tr atti del tipo Object.
Non obbligator io usar e il valor e r estituito da una funzione: nei casi in cui esso viene tr alasciato, la si tr atta come se fosse una pr ocedur a. Ne un esempio la funzione Console.ReadKey(). A noi ser ve per fer mar e il pr ogr amma in attesa della pr essione di un pulsante, ma essa non si limita a questo: r estituisce anche infor mazioni dettagliate sulle condizioni di pr essione e sul codice del car atter e inviato dalla tastier a. Tuttavia, a noi non inter essava usar e queste infor mazioni; cos, invece di scr iver e un codice come questo: 1. Dim b = Console.ReadKey() ci siamo limitati a: 1. Console.ReadKey() Questa ver satilit pu, in cer ti casi, cr ear e pr oblemi, poich si usa una funzione convinti che sia una pr ocedur a, mentr e il valor e r estituito impor tante per evitar e l'insor ger e di er r or i. Ne un esempio la funzione IO.File.Cr eate, che vedr emo molto pi in l, nella sezione E della guida.
V ariabili Static
Le var iabili Static sono una par ticolar e eccezione alle var iabili locali/tempor anee. Avevo chiar amente scr itto pochi par agr afi fa che queste ultime esistono solo nel cor po del metodo, vengono cr eate al momento dell'invocazione e distr utte al ter mine dell'esecuzione. Le Static, invece, possiedono soltanto le pr ime due car atter istiche: non vengono distr utte alla fine del cor po, ma il lor o valor e si conser va in memor ia e r imane tale anche quando il flusso entr a una seconda volta nel metodo. Ecco un esempio: 01. Module Module1 02. Sub Test() 03. Static B As Int32 = 0 04. B += 1 05. Console.WriteLine(B) 06. End Sub 07. 08. Sub Main(ByVal Args() As String) 09. For I As Int16 = 1 To 6 Test() 10. 11. Next 12. Console.ReadKey() 13. End Sub 14. End Module Il pr ogr amma stamper a scher mo, in successione, 1, 2, 3, 4, 5 e 6. Come volevasi dimostr ar e, nonostante B sia tempor anea, mantiene il suo valor e tr a una chiamata e la successiva.
Parametri indefiniti
Questo par ticolar e tipo di par ametr i non r appr esenta un solo elemento, ma bens una collezione di elementi: infatti, si specifica un par ametr o come indefinito quando non si sa a pr ior i quanti par ametr i il metodo r ichieder . A sostegno di questo fatto, i par ametr i indefiniti sono dichiar ati come ar r ay, usando la keyw or d Par amAr r ay inter posta tr a la clausola ByVal o ByRef e il nome del par ametr o. 01. Module Module1 02. 'Somma tutti i valori passati come parametri. 03. Function Sum(ByVal ParamArray Values() As Single) As Single Dim Result As Single = 0 04. 05. 06. For I As Int32 = 0 To Values.Length - 1 07. Result += Values(I) 08. Next 09. Return Result 10. 11. End Function 12. 13. Sub Main() Dim S As Single 14. 15. 16. 'Somma due valori 17. S = Sum(1, 2) 18. 'Somma quattro valori S = Sum(1.1, 5.6, 98.2, 23) 19. 'Somma un array di valori 20. Dim V() As Single = {1, 8, 3.4} 21. S = Sum(V) 22. End Sub 23. 24. End Module Come si vede, mediante Par amAr r ay, la funzione diventa capace si accettar e sia una lista di valor i specificata dal pr ogr ammator e si un ar r ay di valor i, dato che il par ametr o indefinito, in fondo, pur sempr e un ar r ay. N.B.: pu esister e uno e un solo par ametr o dichiar ato con Par amAr r ay per ciascun metodo, ed esso deve sempr e esser e posto alla fine dell'elenco dei par ametr i. Esempio: 01. Module Module1 02. 'Questa funzione calcola un prezzo includendovi anche 'il pagamento di alcune tasse (non sono un esperto di 03. 04. 'economia, perci mi mantengono piuttosto sul vago XD). 'Il primo parametro rappresenta il prezzo originale, mentre 05. 06. 'il secondo un parametro indefinito che 07. 'raggruppa tutte le varie tasse vigenti sul prodotto 'da acquistare che devono essere aggiunte all'importo 08. 09. 'iniziale (espresse come percentuali) 10. Function ApplyTaxes(ByVal OriginalPrice As Single, _ 11. ByVal ParamArray Taxes() As Single) As Single 12. Dim Result As Single = OriginalPrice 13. For Each Tax As Single In Taxes 14. Result += OriginalPrice * Tax / 100 15. Next 16. Return Result 17. End Function 18. 19. Sub Main() Dim Price As Single = 120 20. 21. 22. 'Aggiunge una tassa del 5% a Price Dim Price2 As Single = _ 23. ApplyTaxes(Price, 5) 24. 25. 'Aggiunge una tassa del 5%, una del 12.5% e una 26. 27. 'dell'1% a Price Dim Price3 As Single = _ 28. ApplyTaxes(Price, 5, 12.5, 1) 29. 30. Console.WriteLine("Prezzo originale: " & Price) 31. Console.WriteLine("Presso con tassa 1: " & Price2) 32. Console.WriteLine("Prezzo con tassa 1, 2 e 3: " & Price3) 33. 34.
Ric orsione
Si ha una situazione di r icor sione quando un metodo invoca se stesso: in questi casi, il metodo viene detto r icor sivo. Tale tecnica possiede pr egi e difetti: il pr egio pr incipale consiste nella r iduzione dr astica del codice scr itto, con un conseguente aumento della leggibilit; il difetto pi r ilevante l'uso spr opositato di memor ia, per evitar e il quale necessar io adottar e alcune tecniche di pr ogr ammazione dinamica. La r icor sione, se male usata, inoltr e, pu facilmente pr ovocar e il cr ash di un'applicazione a causa di un over flow dello stack. Infatti, se un metodo continua indiscr iminatamente a invocar e se stesso, senza alcun contr ollo per poter si fer mar e (o con costr utti di contr ollo contenenti er r or i logici), continua anche a r ichieder e nuova memor ia per il passaggio dei par ametr i e per le var iabili locali, oltr e che per l'invocazione stessa: tutte queste r ichieste finiscono per sovr accar icar e la memor ia tempor anea, che, non r iuscendo pi a soddisfar le, le deve r ifiutar e, pr ovocando il suddetto cr ash. Ma for se sono tr oppo pessimista: non vor r ei che r inunciaste ad usar e la r icor sione per paur a di incor r er e in tutti questi spaur acchi: ci sono cer ti casi in cui davver o utile. Come esempio non posso che pr esentar e il classico calcolo del fatto r iale: 01. Module Module1 02. 'Notare che il parametro di tipo Byte perch il 'fattoriale cresce in modo abnorme e gi a 170! Double non 03. 04. 'basta pi a contenere il risultato Function Factorial(ByVal N As Byte) As Double 05. If N <= 1 Then 06. 07. Return 1 Else 08. Return N * Factorial(N - 1) 09. 10. End If End Function 11. 12. Sub Main() 13. 14. Dim Number As Byte 15. 16. Console.WriteLine("Inserisci un numero (0 <= x < 256):") 17. Number = Console.ReadLine 18. Console.WriteLine(Number & "! = " & Factorial(Number)) 19. Console.ReadKey() 20. End Sub 21. 22. End Module
49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. End
Next 'Restituisce l'array modificato (un array di caratteri 'e una stringa sono equivalenti) Return Chars Case StringCase.Sentence 'Riduce tutta la stringa a Lower Case Str = Str.ToLower() 'Imposta il primo carattere come maiuscolo Dim Chars() As Char = Str Chars(0) = Char.ToUpper(Chars(0)) Str = Chars 'La chiude con un punto Str = Str & "." Return Str End Select End Function Sub Main() Dim Str As String = "QuEstA una stRingA DI prova" 'Per usare i valori di un enumeratore bisogna sempre scrivere 'il nome dell'enumeratore seguito dal punto Console.WriteLine(ToCase(Str, StringCase.Lower)) Console.WriteLine(ToCase(Str, StringCase.Upper)) Console.WriteLine(ToCase(Str, StringCase.Proper)) Console.WriteLine(ToCase(Str, StringCase.Sentence)) Console.ReadKey() End Sub Module
'parola e dovr essere maiuscolo. If I = 0 Then Chars(I) = Char.ToUpper(Chars(I)) End If If Chars(I) = " " And I < Str.Length - 1 Then 'Char.ToUpper rende maiuscolo un carattere 'passato come parametro e lo restituisce Chars(I + 1) = Char.ToUpper(Chars(I + 1)) End If
L'enumer ator e Str ingCase offr e quattr o possibilit: Low er , Upper , Pr oper e Sentence. Chi usa la funzione invitato a sceglier e una fr a queste costanti, ed in questo modo non si r ischia di dimenticar e il significato di un codice. Notar e che ho scr itto "invitato", ma non "obbligato", poich l'Enumer ator e soltanto un mezzo attr aver so il quale il pr ogr ammator e d nomi significativi a costanti, che sono pur sempr e dei numer i. A pr ima vista non si dir ebbe, vedendo la dichiar azione, ma ad ogni nome indicato come campo dell'enumer ator e viene associato un numer o (sempr e inter o e di solito a 32 bit). Per saper e quale valor e ciascun identificator e indica, basta scr iver e un codice di pr ova come questo: 1. 2. 3. 4. Console.WriteLine(StringCase.Lower) Console.WriteLine(StringCase.Upper) Console.WriteLine(StringCase.Sentence) Console.WriteLine(StringCase.Proper)
A scher mo appar ir 1. 2. 3. 4. 0 1 2 3
Come si vede, le costanti assegnate par tono da 0 per il pr imo campo e vengono incr ementate di 1 via via che si pr ocede a indicar e nuovi campi. anche possibile deter minar e esplicitamente il valor e di ogni identificator e: 1. Enum StringCase 2. Lower = 5 3. Upper = 10 4. Sentence = 20 5.
Proper = 40 6. End Enum Se ad un nome non viene assegnato valor e, esso assumer il valor e del suo pr ecedente, aumentato di 1: 1. Enum StringCase 2. Lower = 5 3. Upper '= 6 4. Sentence = 20 5. Proper '= 21 6. End Enum Gli enumer ator i possono assumer e solo valor i inter i, e sono, a dir la ver it, dir ettamente der ivati dai tipi numer ici di base. , infatti, per fettamente lecito usar e una costante numer ica al posto di un enumer ator e e vicever sa. Ecco un esempio lampante in cui utilizzo un enumer ator e indicante le note musicali da cui r icavo la fr equenza delle suddette: 01. Module Module1 02. 'Usa i nomi inglesi delle note. L'enumerazione inizia 03. 'da -9 poich il Do centrale si trova 9 semitoni 'sotto il La centrale 04. Enum Note 05. C = -9 06. CSharp 07. D 08. DSharp 09. E 10. F 11. FSharp 12. G 13. GSharp 14. A 15. ASharp 16. B 17. End Enum 18. 19. 'Restituisce la frequenza di una nota. N, in concreto, 20. 'rappresenta la differenza, in semitoni, di quella nota 21. 'dal La centrale. Ecco l'utilitt degli enumeratori, 22. 'che danno un nome reale a ci che un dato indica 23. 'indirettamente 24. Function GetFrequency(ByVal N As Note) As Single 25. Return 440 * 2 ^ (N / 12) 26. End Function 27. 28. 'Per ora prendete per buona questa funzione che restituisce 29. 'il nome della costante di un enumeratore a partire dal 30. 'suo valore. Avremo modo di approfondire nei capitoli 31. 'sulla Reflection 32. Function GetName(ByVal N As Note) As String 33. Return [Enum].GetName(GetType(Note), N) 34. End Function 35. 36. Sub Main() 37. 'Possiamo anche iterare usando gli enumeratori, poich 38. 'si tratta pur sempre di semplici numeri 39. For I As Int32 = Note.C To Note.B 40. Console.WriteLine("La nota " & GetName(I) & _ 41. " risuona a una frequenza di " & GetFrequency(I) & "Hz") 42. Next 43. 44. Console.ReadKey() 45. End Sub 46. 47. End Module anche possibile specificar e il tipo di inter o di un enumer ator e (se Byte, Int16, Int32, Int64 o SByte, UInt16, UInt32, UInt64) apponendo dopo il nome la clausola As seguita dal tipo: 1. Enum StringCase As Byte 2. Lower = 5 3.
Upper = 10 4. Sentence = 20 5. Proper = 40 6. End Enum Questa par ticolar it si r ivela molto utile quando bisogna scr iver e enumer ator i su file in modalit binar ia. In questi casi, essi r appr esentano solitamente un campo detto Flags, di cui mi occuper nel pr ossimo par agr afo.
'000000010 Or 41. '000000100 = 42. '000000110 43. 'Come si vede, ora ci sono due campi attivi: 4 e 2, che 44. 'corrispondono a Hidden e System. Abbiamo fuso insieme due 45. 'attributi con Or 46. 47. F = FileAttributes.Archive Or FileAttributes.System Or _ 48. FileAttributes.Hidden 49. 'La stessa cosa: 50. '00001000 Or 51. '00000100 Or 52. '00000010 = 53. '00001110 54. End Sub 55. End Module Or a sappiamo come immagazzinar e i campi, ma come si fa a legger li? Nel pr ocedimento inver so si una invece un And: 01. Module Module1 02. Sub Main() 03. Dim F As FileAttributes 04. 05. F = FileAttributes.Archive Or FileAttributes.System Or _ 06. FileAttributes.Hidden 07. 'Ora F 00001110 e bisogna eseguire un'operazione di And 08. 'sui bit, confrontando questo valore con Archive, che 8. 09. 'And restituisce Vero solo quando entrambe le condizioni 10. 'sono vere: 11. 12. '00001110 And 13. '00001000 = '00001000, ossia Archive! 14. 15. If F And FileAttributes.Archive = FileAttributes.Archive Then Console.WriteLine("Il file marcato come 'Archive'") 16. 17. End If 18. Console.ReadKey() End Sub 19. 20. End Module In definitiva, per immagazzinar e pi dati in poco spazio occor r e un enumer ator e contenente solo valor i che sono potenze di due; con Or si uniscono pi campi; con And si ver ifica che un campo sia attivo.
A17. Le Strutture
Nel capitolo pr ecedente ci siamo soffer mati ad analizzar e una par ticolar e categor ia di tipi di dato, gli enumer ator i, str umenti capaci di r appr esentar e tr amite costanti numer iche possibilit, scelte, opzioni, flags e in gener e valor i che si possano sceglier e in un insieme finito di elementi. Le str uttur e, invece, appar tengono ad un'altr a categor ia. Anch'esse r appr esentano un tipo di dato der ivato, o complesso, poich non r ientr a fr a i tipi base (di cui ho gi par lato) ma da essi composto. Le str uttur e ci per mettono di cr ear e nuovi tipi di dato che possano adattar si in modo miglior e alla logica dell'applicazione che si sta scr ivendo: in r ealt, quello che per mettono di far e una specie di "collage" di var iabili. Ad esempio, ammettiamo di voler scr iver e una r ubr ica, in gr ado di memor izzar e nome, cognome e numer o di telefono dei nostr i pr incipali amici e conoscenti. Ovviamente, dato che si tr atta di tan te per sone, avr emo bisogno di ar r ay per contener e tutti i dati, ma in che modo li potr emmo immagazzinar e? Per quello che ho illustr ato fino a questo punto, la soluzione pi lampante sar ebbe quella di dichiar ar e tr e ar r ay, uno per i nomi, uno per i cognomi e uno per i numer i telefonici. 1. Dim Names() As String 2. Dim Surnames() As String 3. Dim PhoneNumbers() As String Inutile dir e che seguendo questo appr occio il codice r isulter ebbe molto confusionar io e poco aggior nabile: se si volesse aggiunger e, ad esempio, un altr o dato, "data di nascita", si dovr ebbe dichiar ar e un altr o ar r ay e modificar e pr essoch tutte le par ti del listato. Usando una str uttur a, invece, potr emmo cr ear e un nuovo tipo di dato che contenga al suo inter no tutti i campi necessar i: 1. 2. 3. 4. 5. 6. 7. 8. Structure Contact Dim Name, Surname, PhoneNumber As String End Structure '... 'Un array di conttati, ognuno rappresentato dalla struttura Contact Dim Contacts() As Contact
Come si vede dall'esempio, la sintassi usata per dichiar ar e una str uttur a la seguente: 1. Structure [Nome] 2. Dim [Campo1] As [Tipo] Dim [Campo2] As [Tipo] 3. 4. '... 5. End Structure Una volta dichiar ata la str uttur a e una var iabile di quel tipo, per , come si fa ad acceder e ai campi in essa pr esenti? Si usa l'oper ator e punto ".", posto dopo il nome della var iabile: 01. Module Module1 02. Structure Contact 03. Dim Name, Surname, PhoneNumber As String 04. End Structure 05. 06. Sub Main() 07. Dim A As Contact 08. 09. A.Name = "Mario" 10. A.Surname = "Rossi" 11. A.PhoneNumber = "333 33 33 333" 12. End Sub 13. End Module [Ricor date che le dichiar azioni di nuovi tipi di dato (fino ad or a quelli che abbiamo analizzato sono enumer ator i e
str uttur e, e le classi solo come intr oduzione) possono esser e fatte solo a livello di classe o di namespace, e m ai dentr o ad un metodo.] Una str uttur a, volendo ben veder e, non altr o che un agglomer ato di pi var iabili di tipo base e, cosa molto impor tante, un tipo value, quindi si compor ta esattamente come Integer , Shor t, Date, ecceter a... e viene memor izzata dir ettamente sullo stack, senza uso di puntator i.
Ac robazie c on le strutture
Ma or a veniamo al codice ver o e pr opr io. Vogliamo scr iver e quella r ubr ica di cui avevo par lato pr ima, ecco un inizio: 01. Module Module1 02. Structure Contact 03. Dim Name, Surname, PhoneNumber As String End Structure 04. 05. 06. Sub Main() 07. 'Contacts(-1) inizializza un array vuoto, 08. 'ossia con 0 elementi Dim Contacts(-1) As Contact 09. Dim Command As Char 10. 11. 12. Do 13. Console.Clear() Console.WriteLine("Rubrica -----") 14. Console.WriteLine("Selezionare l'azione desiderata:") 15. Console.WriteLine("N - Nuovo contatto;") 16. Console.WriteLine("T - Trova contatto;") 17. 18. Console.WriteLine("E - Esci.") Command = Char.ToUpper(Console.ReadKey().KeyChar) 19. Console.Clear() 20. 21. Select Case Command 22. Case "N" 23. 'Usa ReDim Preserve per aumentare le dimensioni 24. 'dell'array mentenendo i dati gi presenti. 25. 'L'uso di array e di redim, in questo caso, 26. 'sconsigliato, a favore delle pi versatili 27. 'Liste, che per non ho ancora introdotto. 28. 'Ricordate che il valore specificato tra 29. 'parentesi indica l'indice massimo e non 30. 'il numero di elementi. 31. 'Se, all'inizio, Contacts.Length 0, 32. 'richiamando ReDim Contacts(0), si aumenta 33. 'la lunghezza dell'array a uno, poich 34. 'in questo caso l'indice massimo 0, 35. 'ossia quello che indica il primo e 36. 'l'unico elemento 37. ReDim Preserve Contacts(Contacts.Length) 38. 39. Dim N As Contact 40. Console.Write("Nome: ") 41. N.Name = Console.ReadLine 42. Console.Write("Cognome: ") 43. N.Surname = Console.ReadLine 44. Console.Write("Numero di telefono: ") 45. N.PhoneNumber = Console.ReadLine 46. 47. 'Inserisce nell'ultima cella dell'array 48. 'l'elemento appena creato 49. Contacts(Contacts.Length - 1) = N 50. 51. Case "T" 52. Dim Part As String 53. 54. Console.WriteLine("Inserire nome o cognome del " & _ 55. "contatto da trovare:") 56. Part = Console.ReadLine 57. 58. 59.
For Each C As Contact In Contacts 60. 'Il confronto avviene in modalit 61. 'case-insensitive: sia il nome/cognome 62. 'che la stringa immessa vengono 63. 'ridotti a Lower Case, cos da 64. 'ignorare la differenza tra 65. 'minuscole e maiuscole, qualora presente 66. If (C.Name.ToLower() = Part.ToLower()) Or _ 67. (C.Surname.ToLower() = Part.ToLower()) Then 68. Console.WriteLine("Nome: " & C.Name) 69. Console.WriteLine("Cognome: " & C.Surname) 70. Console.WriteLine("Numero di telefono: " & C.PhoneNumber) 71. Console.WriteLine() 72. End If 73. Next 74. 75. Case "E" 76. Exit Do 77. 78. Case Else 79. Console.WriteLine("Comando sconosciuto!") 80. End Select 81. Console.ReadKey() 82. Loop 83. End Sub 84. End Module Or a ammettiamo di voler modificar e il codice per per metter e l'inser imento di pi numer i di telefono: 01. Module Module1 02. Structure Contact 03. Dim Name, Surname As String 04. 'Importante: NON possibile specificare le dimensioni 05. 'di un array dentro la dichiarazione di una struttura. 06. 'Risulta chiaro il motivo se ci si pensa un attimo. 07. 'Noi stiamo dichiarando quali sono i campi della struttura 08. 'e quale il loro tipo. Quindi specifichiamo che 09. 'PhoneNumbers un array di stringhe, punto. Se scrivessimo 10. 'esplicitamente le sue dimensioni lo staremmo creando 11. 'fisicamente nella memoria, ma questa una 12. 'dichiarazione, come detto prima, e non una 13. 'inizializzazione. Vedremo in seguito che questa 14. 'differenza molto importante per i tipi reference 15. '(ricordate, infatti, che gli array sono tipi reference). 16. Dim PhoneNumbers() As String 17. End Structure 18. 19. Sub Main() 20. Dim Contacts(-1) As Contact Dim Command As Char 21. 22. Do 23. Console.Clear() 24. 25. Console.WriteLine("Rubrica -----") 26. Console.WriteLine("Selezionare l'azione desiderata:") 27. Console.WriteLine("N - Nuovo contatto;") Console.WriteLine("T - Trova contatto;") 28. Console.WriteLine("E - Esci.") 29. 30. Command = Char.ToUpper(Console.ReadKey().KeyChar) Console.Clear() 31. 32. Select Case Command 33. Case "N" 34. ReDim Preserve Contacts(Contacts.Length) 35. 36. Dim N As Contact 37. Console.Write("Nome: ") 38. N.Name = Console.ReadLine 39. Console.Write("Cognome: ") 40. N.Surname = Console.ReadLine 41. 42. 'Ricordate che le dimensioni dell'array non 43. 44.
'sono ancora state impostate: 45. ReDim N.PhoneNumbers(-1) 46. 47. 'Continua a chiedere numeri di telefono finch 48. 'non si introduce pi nulla 49. Do 50. ReDim Preserve N.PhoneNumbers(N.PhoneNumbers.Length) 51. Console.Write("Numero di telefono " & N.PhoneNumbers.Length & ": ") 52. N.PhoneNumbers(N.PhoneNumbers.Length - 1) = Console.ReadLine 53. Loop Until N.PhoneNumbers(N.PhoneNumbers.Length - 1) = "" 54. 'Ora l'ultimo elemento dell'array sicuramente 55. 'vuoto, lo si dovrebbe togliere. 56. 57. Contacts(Contacts.Length - 1) = N 58. 59. Case "T" 60. Dim Part As String 61. 62. Console.WriteLine("Inserire nome o cognome del " & _ 63. "contatto da trovare:") 64. Part = Console.ReadLine 65. 66. For Each C As Contact In Contacts 67. If (C.Name.ToLower() = Part.ToLower()) Or _ 68. (C.Surname.ToLower() = Part.ToLower()) Then Console.WriteLine("Nome: " & C.Name) 69. Console.WriteLine("Cognome: " & C.Surname) 70. Console.WriteLine("Numeri di telefono: ") 71. For Each N As String In C.PhoneNumbers 72. Console.WriteLine(" - " & N) 73. Next 74. Console.WriteLine() 75. End If 76. Next 77. 78. Case "E" 79. Exit Do 80. 81. Case Else 82. Console.WriteLine("Comando sconosciuto!") 83. End Select 84. Console.ReadKey() 85. Loop 86. End Sub 87. 88. End Module In questi esempi ho cer cato di pr opor r e i casi pi comuni di str uttur a, almeno per quanto si visto fino ad adesso: una str uttur a for mata da campi di tipo base e una composta dagli stessi campi, con l'aggiunta di un tipo a sua volta der ivato, l'ar r ay. Fino ad or a, infatti, ho sempr e detto che la str uttur a per mette di r aggr uppar e pi membr i di tipo base, ma sar ebbe r iduttivo r estr inger e il suo ambito di competenza solo a questo. In r ealt pu contener e var iabili di qualsiasi tipo, compr ese altr e str uttur e. Ad esempio, un contatto avr ebbe potuto anche contener e l'indir izzo di r esidenza, il quale avr ebbe potuto esser e stato r appr esentato a sua volta da un'ulter ior e str uttur a: 01. 02. 03. 04. 05. 06. 07. 08. 09. 10. 11. Structure Address Dim State, Town As String Dim Street, CivicNumber As String Dim Cap As String End Structure Structure Contact Dim Name, Surname As String Dim PhoneNumbers() As String Dim Home As Address End Structure
Per acceder e ai campi di Home si sar ebbe utilizzato un ulter ior e punto: 01. Dim A As Contact 02. 03.
A.Name = "Mario" A.Surname = "Rossi" ReDim A.PhoneNumbers(0) A.PhoneNumbers(0) = "124 90 87 111" A.Home.State = "Italy" A.Home.Town = "Pavia" A.Home.Street = "Corso Napoleone" A.Home.CivicNumber = "96/B" A.Home.Cap = "27010"
A18. Le Classi
Bene bene. Eccoci ar r ivati al sugo della questione. Le classi, entit alla base di tutto l'edificio del .NET. Gi nei pr imi capitoli di questa guida ho accennato alle classi, alla lor o sintassi e al modo di dichiar ar le. Per chi non si r icor dasse (o non avesse voglia di lasciar e questa magnifica pagina per r itor nar e indietr o nei capitoli), una classe si dichiar a semplicemente cos: 1. Class [Nome Classe] 2. '... 3. End Class Con l'atto della dichiar azione, la classe inizia ad esister e all'inter no del codice sor gente, cosicch il pr ogr ammator e la pu usar e in altr e par ti del listato per gli scopi a causa dei quali stata cr eata. Or a che ci stiamo avvicinando sempr e pi all'usar e le classi nei pr ossimi pr ogr ammi, tuttavia, dover oso r icor dar e ancor a una volta la sostanziale differ enza tr a dichiar azione e inizializzazione, tr a classe e oggetto, giusto per r infr escar e le memor ie pi fr agili e, lungi dal far vi odiar e questo concetto, per far e in modo che il messaggio penetr i: 01. Module Module1 02. 'Classe che rappresenta un cubo. 03. 'Segue la dichiarazione della classe. Da questo momento 04. 'in poi, potremo usare Cube come tipo per le nostre variabili. 05. 'Notare che una classe si dichiara e basta, non si 06. '"inizializza", perch non qualcosa di concreto, 07. ' un'astrazione, c', esiste in generale. 08. Class Cube 09. 'Variabile che contiene la lunghezza del lato 10. Dim SideLength As Single 11. 'Variabile che contiene la densit del cubo, e quindi 12. 'ci dice di che materiale composto 13. Dim Density As Single 14. 15. 'Questa procedura imposta i valori del lato e 16. 'della densit 17. Sub SetData(ByVal SideLengthValue As Single, ByVal DensityValue As Single) 18. SideLength = SideLengthValue 19. Density = DensityValue 20. End Sub 21. 22. 'Questa funzione restituisce l'area di una faccia 23. Function GetSurfaceArea() As Single Return (SideLength ^ 2) 24. 25. End Function 26. 'Questa funzione restituisce il volume del cubo 27. 28. Function GetVolume() As Single Return (SideLength ^ 3) 29. End Function 30. 31. 'Questa funzione restituisce la massa del cubo 32. Function GetMass() As Single 33. Return (Density * GetVolume()) 34. End Function 35. End Class 36. 37. Sub Main() 38. 'Variabile di tipo Cube, che rappresenta uno specifico cubo 39. 'La riga di codice che segue contiene la dichiarazione 40. 'della variabile A. La dichiarazione di una variabile 41. 'fa sapere al compilatore, ad esempio, di che tipo 42. 'sar, in quale blocco di codice sar 43. 'visibile, ma nulla di pi. 44. 'Non esiste ancora un oggetto Cube collegato ad A, ma 45. 'potrebbe essere creato in un immediato futuro. 46. 47.
'N.B.: quando si dichiara una variabile di tipo reference, 48. 'viene comunque allocata memoria sullo stack; viene 49. 'infatti creato un puntatore, che punta all'oggetto 50. 'Nothing, il cui valore simbolico stato 51. 'spiegato precedentemente. 52. Dim A As Cube 53. 54. 'Ed ecco l'immediato futuro: con la prossima linea di 55. 'codice, creiamo l'oggetto di tipo Cube che verr 56. 'posto nella variabile A. 57. A = New Cube 58. 'Quando New seguito dal nome di una classe, si crea un 59. 'oggetto di quel tipo. Nella fattispecie, in questo momento 60. 'il programma si preoccuper di richiedere della 61. 'memoria sull'heap managed per allocare i dati relativi 62. 'all'oggetto e di creare un puntatore sullo stack che 63. 'punti a tale oggetto. Esso, inoltre, eseguir 64. 'il codice contenuto nel costruttore. New, infatti, 65. ' uno speciale tipo di procedura, detta 'Costruttore, di cui parler approfonditamente 66. 67. 'in seguito 68. 69. 'Come per le strutture, i membri di classe sono accessibili 'tramite l'operatore punto ".". Ora imposto le variabili 70. 'contenute in A per rappresentare un cubo di alluminio 71. '(densit 2700 Kg/m<sup>3</sup>) di 1.5m di lato 72. A.SetData(1.5, 2700) 73. 74. Console.WriteLine("Superficie faccia: " & A.GetSurfaceArea() & " m2") 75. Console.WriteLine("Volume: " & A.GetVolume() & " m3") 76. Console.WriteLine("Massa: " & A.GetMass() & " Kg") 77. 'It's Over 9000!!!! 78. 79. Console.ReadKey() 80. End Sub 81. 82. End Module In questo esempio ho usato una semplice classe che r appr esenta un cubo di una cer ta dimensione e di un cer to mater iale. Tale classe espone quattr o funzioni, che ser vono per ottener e infor mazioni o impostar e valor i. C' un pr eciso motivo per cui non ho usato dir ettamente le due var iabili accedendovi con l'oper ator e punto, e lo spiegher a br eve nella pr ossima lezione. Quindi, tali funzioni sono membr i di classe e, sopr attutto, funzioni di istanza. Questo lemma non dovr ebbe suonar vi nuovo: gli oggetti, infatti, sono istanze (copie mater iali, concr ete) di classi (astr azioni). Anche questo concetto molto impor tante: il fatto che siano "di istanza" significa che possono esser e r ichiamate ed usate solo da un oggetto. Per far vi capir e, non si possono invocar e con questa sintassi: 1. Cube.GetVolume() ma solo passando attr aver so un'istanza: 1. Dim B As New Cube 2. '... 3. B.GetVolume() E questo, tr a l'altr o, abbastanza banale: infatti, come sar ebbe possibile calcolar e ar ea, volume e massa se non si disponesse della misur a della lunghezza del lato e quella della densit? ovvio che ogni cubo ha le sue pr opr ie misur e, e il concetto gener ale di "cubo" non ci dice niente su queste infor mazioni.
Un semplic e c ostruttore
Anche se entr er emo nel dettaglio solo pi in l, necessar io per i pr ossimi esempi che sappiate come funziona un costr uttor e, anche molto semplice. Esso viene dichiar ato come una nor male pr ocedur a, ma si deve sempr e usar e come nome "New ": 1.
Sub New([parametri]) 2. 'codice 3. End Sub Qualor a non si specificasse nessun costr uttor e, il compilator e ne cr eer uno nuovo senza par ametr i, che equivale al seguente: 1. Sub New() 2. End Sub Il codice pr esente nel cor po del costr uttor e viene eseguito in una delle pr ime fasi della cr eazione dell'oggetto, appena dopo che questo statao fisicamente collocato nella memor ia (ma, badate bene, non la pr ima istr uzione ad esser e eseguita dopo la cr eazione). Lo scopo di tale codice consiste nell'inizializzar e var iabili di tipo r efer ence pr ima solo dichiar ate, attr ibuir e valor i alle var iabili value, eseguir e oper azioni di pr epar azione all'uso di r isor se ester ne, ecceter a... Insomma, ser ve a spianar e la str ada all'uso della classe. In questo caso, l'uso che ne far emo molto r idotto e, non vor r ei dir lo, quasi mar ginale, ma l'unico compito possibile e utile in questo contesto: dar emo al costr uttor e il compito di inizializzar e SideLength e Density. 01. Module Module1 02. Class Cube Dim SideLength As Single 03. Dim Density As Single 04. 05. 'Quasi uguale a SetData 06. Sub New(ByVal SideLengthValue As Single, ByVal DensityValue As Single) 07. SideLength = SideLengthValue 08. Density = DensityValue 09. End Sub 10. 11. Function GetSurfaceArea() As Single 12. Return (SideLength ^ 2) 13. End Function 14. 15. Function GetVolume() As Single 16. Return (SideLength ^ 3) 17. 18. End Function 19. Function GetMass() As Single 20. Return (Density * GetVolume()) 21. End Function 22. End Class 23. 24. Sub Main() 25. 'Questa una sintassi pi concisa che equivale a: 26. 'Dim A As Cube 27. 'A = New Cube(2700, 1.5) 28. 'Tra parentesi vanno passati i parametri richiesti dal 29. 'costruttore 30. Dim A As New Cube(2700, 1.5) 31. 32. Console.WriteLine("Superficie faccia: " & A.GetSurfaceArea() & " m<sup>2</sup>") 33. Console.WriteLine("Volume: " & A.GetVolume() & " m<sup>3</sup>") 34. Console.WriteLine("Massa: " & A.GetMass() & " Kg") 35. 36. Console.ReadKey() 37. End Sub 38. 39. End Module
r isolve con una soluzione molto semplice: i costr uttor i dichiar ati nelle str uttur e possono esser e usati esattamente come per le classi, ma il lor o compito solo quello di inizializzar e campi e r ichiamar e r isor se, poich una var iabile di tipo str uttur ato viene cr eata sullo stack all'atto della sua dichiar azione. 01. Module Module1 02. Structure Cube 03. Dim SideLength As Single Dim Density As Single 04. 05. 06. Sub New(ByVal SideLengthValue As Single, ByVal DensityValue As Single) 07. SideLength = SideLengthValue 08. Density = DensityValue End Sub 09. 10. 11. Function GetSurfaceArea() As Single 12. Return (SideLength ^ 2) 13. End Function 14. 15. Function GetVolume() As Single Return (SideLength ^ 3) 16. 17. End Function 18. Function GetMass() As Single 19. Return (Density * GetVolume()) 20. End Function 21. End Structure 22. 23. Sub Main() 24. 'Questo codice 25. Dim A As New Cube(2700, 1.5) 26. 27. 'Equivale a questo 28. Dim B As Cube 29. B.SideLength = 1.5 30. B.Density = 2700 31. 32. 'A e B sono uguali 33. 34. Console.ReadKey() 35. End Sub 36. 37. End Module
Un esempio pratic o
Ripr endiamo il codice della classe Cube r ipr oposto nel capitolo pr ecedente. Pr oviamo a scr iver e nella Sub Main questo codice: 1. Sub Main() 2. Dim A As New Cube(2700, 1.5) 3. A.SideLength = 3 4. End Sub La r iga "A.SideLength = 3" ver r sottolineata e appar ir il seguente er r or e nel log degli er r or i: 1. ConsoleApplication2.Module1.Cube.SideLength' is not accessible in this 2. context because it is 'Private'. Questo il motivo per cui ho usato una pr ocedur a per impostar e i valor i: l'accesso al membr o (in questo caso "campo", in quanto si tr atta di una var iabile) SideLength ci pr ecluso se tentiamo di acceder vi da un codice ester no alla classe, poich, di default, nelle classi, Dim equivale a Pr ivate. Dichiar andolo esplicitamente, il codice di Cube sar ebbe stato cos: 01. Module Module1 02. Class Cube 03. 'Quando gli specificatori di accesso sono anteposti alla 04. 'dichiarazione di una variabile, si toglie il "Dim" 05. Private SideLength As Single 06. Private Density As Single 07. 08. Sub New(ByVal SideLengthValue As Single, ByVal DensityValue As Single) 09. SideLength = SideLengthValue 10. Density = DensityValue 11. End Sub 12. 13. Function GetSurfaceArea() As Single 14. Return (SideLength ^ 2) 15. End Function 16.
17. Function GetVolume() As Single 18. Return (SideLength ^ 3) 19. End Function 20. 21. Function GetMass() As Single 22. Return (Density * GetVolume()) 23. End Function 24. End Class 25. '... 26. End Module In questo specifico caso, sar ebbe stato meglio impostar e tali var iabili come Public, poich nel lor o scope (= livello di accesso) attuale non ser vono a molto e, anzi, r ichiedono molto pi codice di gestione. Ma immaginate una classe che compia oper azioni cr ittogr afiche sui dati che gli sono passati in input, usando var iabili d'istanza per i suoi calcoli: se tali var iabili fosser o accessibili al di fuor i della classe, lo sviluppator e che non sapesse esattamente cosa far ci potr ebbe compr ometter e ser iamente il r isultato di tali oper azioni, e quindi danneggiar e i pr otocolli di sicur ezza usati dall'applicazione. Etichettar e un membr o come pr ivate equivar r ebbe scher zosamente a por vi sopr a un gr ande car tello con scr itto "NON TOCCARE". Ma veniamo invece a Public, uno degli scope pi usati nella scr ittur a di una classe. Di solito, tutti i membr i che devono esser e r esi disponibili per altr e par ti del pr ogr amma o anche per altr i pr ogr ammator i (ad esempio, se si sta scr ivendo una libr er ia che sar usata successivamente da altr e per sone) sono dichiar ati come Public, ossia sempr e accessibili, senza nessun per messo di sor ta.
E che dir e, allor a, dei membr i senza specificator e di accesso? Non esistono, a dir la tutta. Anche quelli che nel codice non vengono esplicitamente mar cati dal pr ogr ammator e con una delle keyw or d sopr a elencate hanno uno scope pr edefinito: si tr atta di Fr iend. Esso ha un compito par ticolar e che potr ete capir e meglio quando affr onter emo la scr ittur a di una libr er ia di classi: per or a vi baster saper e che, all'inter no di uno stesso pr ogetto, equivale a Public. Un'altr a cosa impor tante: anche le classi (e i moduli) sono contr addistinte da un livello di accesso, che segue esattamente le stesse r egole sopr a esposte. Ecco un esempio: 01. Public Class Classe1 02. Private Class Classe2 03. '... 04. End Class 05. 06. Class Classe3 07. '... 08. End Class 09. End Class 10. 11. Class Classe4 12. Public Class Classe5 13. Private Class Classe6 14. '... 15.
End Class 16. End Class 17. End Class 18. 19. Module Module1 20. Sub Main() 21. '... 22. End Sub 23. End Module Il codice contenuto in Main pu acceder e a: Classe1, per ch Public Classe3, per ch Fr iend, ed possibile acceder e al suo contenitor e Classe1 Classe4, per ch Fr iend Classe5, per ch Public, ed possibile acceder e al suo contenitor e Classe4 mentr e non pu acceder e a: Classe2, per ch Pr ivate Classe6, per ch Pr ivate d'altr a par te, il codice di Classe2 pu acceder e a tutto tr anne a Classe6 e vicever sa. N.B.: Una classe pu esser e dichiar ata Pr ivate solo quando si tr ova all'inter no di un'altr a classe (altr imenti non sar ebbe mai accessibile, e quindi inutile).
Un esempio intelligente
Ecco un esempio di classe scr itta utilizzando gli specificator i di accesso per limitar e l'accesso ai membr i da par te del codice di Main (e quindi da chi usa la classe, poich l'utente finale pu anche esser e un altr o pr ogr ammator e). Oltr e a questo tr over ete anche un esempio di un diffuso e semplice algor itmo di or dinamento, 2 in 1! 001. Module Module1 002. 'Dato che usiamo la classe solo in questo programma, possiamo 003. 'evitare di dichiararla Public, cosa che sarebbe ideale in 004. 'una libreria 005. Class BubbleSorter 006. 'Enumeratore pubblico: sar accessibile da tutti. In questo 007. 'caso impossibile dichiararlo come Private, poich 008. 'uno dei prossimi metodi richiede come parametro una 009.
010. 011. 012. 013. 014. 015. 016. 017. 018. 019. 020. 021. 022. 023. 024. 025. 026. 027. 028. 029. 030. 031. 032. 033. 034. 035. 036. 037. 038. 039. 040. 041. 042. 043. 044. 045. 046. 047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081.
'variabile di tipo SortOrder e se questo fosse private, 'non si potrebbe usare al di fuori della classe, cosa 'che invece viene richiesta. Public Enum SortOrder Ascending 'Crescente Descending 'Decrescente None 'Nessun ordinamento End Enum 'Mantiene in memoria il senso di ordinamento della lista, 'per evitare di riordinarla nel caso fosse richiesto due 'volte lo stesso Private CurrentOrder As SortOrder = SortOrder.None 'Mantiene in memoria una copia dell'array, che 'accessibile ai soli membri della classe. In 'questo modo, possibile eseguire tutte 'le operazioni di ordinamento usando un solo metodo 'per l'inserimento dell'array Private Buffer() As Double 'Memorizza in Buffer l'array passato come parametro Public Sub PushArray(ByVal Array() As Double) 'Se Buffer diverso da Nothing, lo imposta 'esplicitamente a Nothing (equivale a distruggere 'l'oggetto) If Buffer IsNot Nothing Then Buffer = Nothing End If 'Copia l'array: ricordate come si comportano i tipi 'reference e pensate a quali ripercussioni tale 'comportamento potr avere sul codice Buffer = Array 'Annulla CurrentOrder CurrentOrder = SortOrder.None End Sub 'Procedura che ordina l'array secondo il senso specificato Public Sub Sort(ByVal Order As SortOrder) 'Se il senso None, oppure uguale a quello corrente, ' inutile proseguire, quindi si ferma ed esce If (Order = SortOrder.None) Or (Order = CurrentOrder) Then Exit Sub End If 'Questa variabile tiene conto di tutti gli scambi 'effettuati Dim Occurrences As Int32 = 0 'Il ciclo seguente ordina l'array in senso crescente: 'se l'elemento i maggiore dell'elemento i+1, 'ne inverte il posto, e aumenta il contatore di 1. 'Quando il contatore rimane 0 anche dopo il For, 'significa che non c' stato nessuno scambio 'e quindi l'array ordinato. Do Occurrences = 0 For I As Int32 = 0 To Buffer.Length - 2 If Buffer(I) > Buffer(I + 1) Then Dim Temp As Double = Buffer(I) Buffer(I) = Buffer(I + 1) Buffer(I + 1) = Temp Occurrences += 1 End If Next Loop Until Occurrences = 0 'Se l'ordine era discendente, inverte l'array If Order = SortOrder.Descending Then Array.Reverse(Buffer) End If 'Memorizza l'ordine
082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. End
CurrentOrder = Order End Sub 'Restituisce l'array ordinato Public Function PopArray() As Double() Return Buffer End Function End Class Sub Main() 'Crea un Dim a As 'Crea un Dim b As array temporaneo Double() = {1, 6, 2, 9, 3, 4, 8} nuovo oggetto BubbleSorter New BubbleSorter()
'Vi inserisce l'array b.PushArray(a) 'Invoca la procedura di ordinamento b.Sort(BubbleSorter.SortOrder.Descending) 'E per ogni elemento presente nell'array finale '(quello restituito dalla funzione PopArray), ne stampa 'il valore a schermo For Each n As Double In (b.PopArray()) Console.Write(n & " ") Next Console.ReadKey() End Sub Module
Ric apitolando...
Ricapitolando, quindi, davanti a ogni membr o si pu specificar e una keyw or d tr a Pr ivate, Public e Fr iend (per quello che abbiamo visto in questo capitolo), che ne limita l'accesso. Nel caso non si specifichi nulla, lo specificator e pr edefinito var ia a seconda dell'entit a cui stato applicato, secondo questa tabella: Pr ivate per var iabili contenute in una classe Public per var iabili contenute in una str uttur a Fr iend per tutte le altr e entit
26. Sub Main() 27. Dim A As New Example() 28. 29. 'Il codice di Main sta impostando il valore di A.Number. 30. 'Notare che una propriet si usa esattamente come una 31. 'comunissima variabile di istanza. 32. 'La propriet, quindi, richiama il suo blocco Set come 33. 'una procedura e assegna il valore 20 al campo A._Number 34. A.Number = 20 35. 36. 'Nella prossima riga, invece, viene richiesto il valore 37. 'di Number per poterlo scrivere a schermo. La propriet 38. 'esegue il blocco Get come una funzione e restituisce al 39. 'chiamante (ossia il metodo/oggetto che ha invocato Get, 40. 'in questo caso Console.WriteLine) il valore di A._Number 41. Console.WriteLine(A.Number) 42. 43. 'Per gli scettici, facciamo un controllo per vedere se 44. 'effettivamente il contenuto di A._Number cambiato. 45. 'Potrete constatare che uguale a 20. 46. Console.WriteLine(A._Number) 47. Console.ReadLine() 48. End Sub 49. 50. End Module Per pr ima cosa bisogna subito far e due impor tanti osser vazioni: Il nome della pr opr iet e quello del campo a cui essa sovr intende sono molto simili. Questa similar it viene mentenuta per l'appunto a causa dello str etto legame che lega pr opr iet e campo. una convenzione che il nome di un campo mediato da una pr opr iet inizi con il car atter e under scor e ("_"), oppur e con una di queste combinazioni alfanumer iche: "p_", "m_". Il nome usato per la pr opr iet sar , invece, identico, ma senza l'under scor e iniziale, come in questo esempio. Il tipo definito per la pr opr iet identico a quello usato per il campo. Abbastanza ovvio, d'altr onde: se essa deve mediar e l'uso di una var iabile, allor a anche tutti i valor i r icevuti e r estituiti dovr anno esser e compatibili.
14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. End
Return _DynamicFrictionCoefficient End Get Set(ByVal value As Single) _DynamicFrictionCoefficient = value End Set End Property 'Massa, m Public Property Mass() As Single Get Return _Mass End Get Set(ByVal value As Single) _Mass = value End Set End Property 'Accelerazione di gravit che vale nel sistema, g Public Property GravityAcceleration() As Single Get Return _GravityAcceleration End Get Set(ByVal value As Single) _GravityAcceleration = value End Set End Property 'Calcola e restituisce la forza di attrito che agisce 'quando la massa in moto Public Function CalculateFrictionForce() As Single Return (Mass * GravityAcceleration) * DynamicFrictionCoefficient End Function End Class Sub Main() Dim F As New InertialFrame() Console.WriteLine("Sistema inerziale formato da:") Console.WriteLine(" - Un piano orizzontale e scabro;") Console.WriteLine(" - Una massa variabile.") Console.WriteLine() Console.WriteLine("Inserire i dati:") Console.Write("Coefficiente di attrito dinamico = ") F.DynamicFrictionCoefficient = Console.ReadLine Console.Write("Massa (Kg) = ") F.Mass = Console.ReadLine Console.Write("Accelerazione di gravit (m/s<sup>2</sup>) = ") F.GravityAcceleration = Console.ReadLine Console.WriteLine() Console.Write("Attrito dinamico = ") Console.WriteLine(F.CalculateFrictionForce() & " N") Console.ReadLine() End Sub Module
I calcoli funzionano, le pr opr iet sono scr itte in modo cor r etto, tutto gir a alla per fezione, se non che... qualcuno tr ova il modo di metter e = 2 e m = -7, valor i assur di poich 0 < <= 1 ed m > 0. Modificando il codice delle pr opr iet possiamo impor r e questi vincoli ai valor i inser ibili: 01. Module Module1 02. Class InertialFrame 03. Private _DynamicFrictionCoefficient As Single 04. Private _Mass As Single 05. Private _GravityAcceleration As Single 06. 07. Public Property DynamicFrictionCoefficient() As Single 08. Get 09.
Return _DynamicFrictionCoefficient 10. End Get 11. Set(ByVal value As Single) 12. If (value > 0) And (value <= 1) Then 13. _DynamicFrictionCoefficient = value 14. Else 15. Console.WriteLine(value & " non un valore consentito!") 16. Console.WriteLine("Coefficiente attrito dinamico = 0.1") 17. _DynamicFrictionCoefficient = 0.1 18. End If 19. End Set 20. End Property 21. 22. Public Property Mass() As Single 23. Get 24. Return _Mass 25. End Get 26. Set(ByVal value As Single) 27. If value > 0 Then 28. _Mass = value 29. Else 30. Console.WriteLine(value & " non un valore consentito!") 31. Console.WriteLine("Massa = 1") _Mass = 1 32. End If 33. End Set 34. End Property 35. 36. Public Property GravityAcceleration() As Single 37. Get 38. Return _GravityAcceleration 39. End Get 40. Set(ByVal value As Single) 41. _GravityAcceleration = Math.Abs(value) 42. End Set 43. End Property 44. 45. Public Function CalculateFrictionForce() As Single 46. Return (Mass * GravityAcceleration) * DynamicFrictionCoefficient 47. End Function 48. 49. End Class 50. 51. '... 52. 53. End Module In gener e, ci sono due modi di agir e quando i valor i che la pr opr iet r iceve in input sono er r ati: Modificar e il campo r eimpostandolo su un valor e di default, ossia la str ategia che abbiamo adottato per questo esempio; Lanciar e un'eccezione. La soluzione for malmente pi cor r etta sar ebbe la seconda: il codice chiamante dovr ebbe poi cattur ar e e gestir e tale eccezione, lasciando all'utente la possibilit di decider e cosa far e. Tuttavia, per far vi fr onte, bisogner ebbe intr odur r e ancor a un po' di teor ia e di sintassi, r agion per cui il suo uso stato posto in secondo piano r ispetto alla pr ima. Inoltr e, bisogner ebbe anche evitar e di por r e il codice che comunica all'utente l'er r or e nel cor po della pr opr iet e, pi in gener ale, nella classe stessa, poich questo codice potr ebbe esser e r iutilizzato in un'altr a applicazione che magar i non usa la console (altr a r agione per sceglier e la seconda possibilit). Mettendo da par te tali osser vazioni di cir costanza, comunque, si nota come l'uso delle pr opr iet offr a molta pi gestibilit e flessibilit di un semplice campo. E non ancor a finita...
Come esempio user questa pr opr iet: 01. Property Number() As Single 02. Get 03. Return _Number End Get 04. Set(ByVal value As Single) 05. 06. If (value > 30) And (value < 100) Then 07. _Number = value 08. Else _Number = 31 09. End If 10. End Set 11. 12. End Property Quando una pr opr iet viene dichiar ata, ci sembr a che essa esista come un'entit unica nel codice, ed pi o meno ver o. Tuttavia, una volta che il sor gente passa nelle fauci del compilator e, succede una cosa abbastanza singolar e. La pr opr iet cessa di esister e e viene invece spezzata in due elementi distinti: Una funzione senza par ametr i, di nome "get_[Nome Pr opr iet]", il cui cor po viene cr eato copiando il codice contenuto nel blocco Get. Nel nostr o caso, get_Number : 1. Function get_Number() As Single 2. Return _Number 3. End Function Una pr ocedur a con un par ametr o, di nome "set_[Nome Pr opr iet]", il cui cor po viene cr eato copiando il codice contenuto nel blocco Set. Nel nostr o caso, set_Number : 1. Sub set_Number(ByVal value As Single) 2. If (value > 30) And (value < 100) Then 3. _Number = value 4. Else 5. _Number = 31 6. End If 7. End Sub Entr ambi i metodi hanno come specificator e di accesso lo stesso della pr opr iet. Inoltr e, ogni r iga di codice del tipo 1. [Propriet] = [Valore] oppur e 1. [Valore] = [Propriet] viene sostituita con la cor r ispondente r iga: 1. set_[Nome Propriet]([Valore]) oppur e: 1. [Valore] = get_[Nome Propriet] Ad esempio, il seguente codice: 1. Dim A As New Example 2. A.Number = 20 3. Console.WriteLine(A.Number) viene tr asfor mato, dur ante la compilazione, in: 1. Dim A As New Example 2. A.set_Number(20) 3. Console.WriteLine(A.get_Number())
Questo per dir e che una pr opr iet un costr utto di alto livello, uno str umento usato nella pr ogr ammazione astr atta: esso viene scomposto nelle sue par ti fondamentali quando il pr ogr amma passa al livello medio, ossia quando tr adotto in IL, lo pseudo-linguaggio macchina del Fr amew or k .NET.
07. 08. 09. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. End
Return _SideLength End Get Set(ByVal value As Single) If value > 0 Then _SideLength = value Else _SideLength = 1 End If End Set End Property Public Property Density() As Single Get Return _Density End Get Set(ByVal value As Single) If value > 0 Then _Density = value Else _Density = 1 End If End Set End Property Public ReadOnly Property SurfaceArea() As Single Get Return (SideLength ^ 2) End Get End Property Public ReadOnly Property Volume() As Single Get Return (SideLength ^ 3) End Get End Property Public ReadOnly Property Mass() As Single Get Return (Volume * Density) End Get End Property Class
Get
Vedendola dall'ester no, si pu pensar e che la classe Cube contenga come dati concr eti (var iabili) SideLength, Density, Sur faceAr ea, Volume e Mass, e che questi siano esposti tr amite una pr opr iet. In r ealt essa ne contiene solo i pr imi due e in base a questi calcola gli altr i. In questo esempio teor ico, le due pr opr iet esposte sono r eadonly per il pr imo e il secondo motivo: 01. Module Esempio3 02. Class LogFile 03. Private _FileName As String 04. Private _CreationTime As Date 05. 06. 'Niente deve modificare il nome del file, altrimenti 07. 'potrebbero verificarsi errori nella lettura o scrittura 08. 'dello stesso, oppure si potrebbe chiudere un file 09. 'che non esiste ancora 10. Public ReadOnly Property FileName() As String 11. Get 12. Return _FileName 13. End Get 14. End Property 15. 16. 'Allo stesso modo non si pu modificare la data di 17. 'creazione di un file: una volta creato, viene 18. 'prelevata l'ora e il giorno e impostata la 19. 'variabile. Se potesse essere modificata 20. 'non avrebbe pi alcun significato 21.
Public ReadOnly Property CreationTime() As Date 22. Get 23. Return _CreationTime 24. End Get 25. End Property 26. 27. Public Sub New(ByVal Path As String) 28. _FileName = Path 29. _CreationTime = Date.Now 30. End Sub 31. End Class 32. End Module
In questo modo, noi staimo effettivamente modificando l'oggetto S.Box , ma indir ettamente: non stiamo, invece, cambiando il valor e del campo S._Box , che effettivamente ci che ci viene impedito di far e. In sostanza, non stiamo as s egn an do un nuovo oggetto alla var iabile S._Box , ma stiamo solo manipolando i dati di un oggetto esistente, e questo consentito. Anzi, molto meglio dichiar ar e pr opr iet di tipo r efer ence come ReadOnly quando non necessar io assegnar e o impostar e nuovi oggetti.
Next 57. 58. Console.ReadKey() 59. End Sub 60. End Module Notar e che sar ebbe stato logicamente equivalente cr ear e una pr opr iet che r estituisse tutto l'ar r ay, in questo modo: 1. Public ReadOnly Property ExtractedNumbers() As Byte() 2. Get 3. Return _ExtractedNumbers 4. End Get 5. End Property Ma non si sar ebbe avuto alcun contr ollo sull'indice che l'utente avr ebbe potuto usar e: nel pr imo modo, invece, possibile contr ollar e l'indice usato e r estituir e 0 qualor a esso non sia coer ente con i limiti dell'ar r ay. La r estituzione di elementi di una lista, tuttavia, solo una delle possibilit che le pr opr iet par ametr iche offr ono, e non c' limite all'uso che se ne pu far e. Nonostante ci, bene sottolinear e che meglio utilizzar e una funzione o una pr ocedur a (poich le pr opr iet di questo tipo possono anche non esser e r eadonly, questo er a solo un caso) qualor a si debbano eseguir e calcoli o elabor azioni non immediati, diciamo oltr e le 20/30 r ighe di codice, ma anche di meno, a seconda della pesantezza delle oper azioni. Fate conto che le pr opr iet debbano sempr e esser e il pi legger e possibile, computazionalmente par lando: qualche costr utto di contr ollo come If o Select, qualche calcolo sul Retur n, ma nulla di pi.
Propriet di default
Con questo ter mine si indica la pr opr iet pr edefinita di una classe. Per esister e, essa deve soddisfar e questi due r equisiti: Deve posseder e almeno un par ametr o; Deve esser e unica. Anche se solitamente si usa in altr e cir costanze, ecco una pr opr iet di default applicata al pr ecedente esempio: 01. Module Module1 02. Class NumberExtractor 03. Private _ExtractedNumbers() As Byte 04. Private Rnd As Random 05. 06. 'Una propriet di default si dichiara come una 07. 'normalissima propriet, ma anteponendo allo specificatore 08. 'di accesso la keyword Default 09. Default Public ReadOnly Property ExtractedNumbers(ByVal Index As Int32) As Byte 10. Get 11. If (Index >= 0) And (Index < _ExtractedNumbers.Length) Then 12. Return _ExtractedNumbers(Index) 13. Else 14. Return 0 15. End If 16. End Get 17. End Property 18. 19. Public Sub New() 20. Rnd = New Random() 21. ReDim _ExtractedNumbers(5) 22. End Sub 23. Public Sub ExtractNumbers() 24. For I As Int32 = 0 To 5 25. 26. _ExtractedNumbers(I) = Rnd.Next(1, 91) Next 27. End Sub 28. End Class 29. 30. 31. Sub Main() 32.
Dim E As New NumberExtractor() 33. 34. E.ExtractNumbers() 35. Console.WriteLine("Numeri estratti: ") 36. For I As Int32 = 0 To 5 37. 'Ecco l'utilit delle propriet di default: si possono 38. 'richiamare anche omettendone il nome. In questo caso 39. 'E(I) equivalente a scrivere E.ExtractedNumbers(I), 40. 'ma poich ExtractedNumbers di default, 41. 'viene desunta automaticamente 42. Console.Write(E(I) & " ") 43. Next 44. 45. Console.ReadKey() 46. End Sub 47. End Module Dal codice salta subito all'occhio la motivazione dei due pr er equisiti specificati inizialmente: Se la pr opr iet non avesse almeno un par ametr o, sar ebbe impossibile per il compilator e saper e quando il pr ogr ammator e si sta r ifer endo all'oggetto e quando alla sua pr opr iet di default; Se non fosse unica, sar ebbe impossibile per il compilator e decider e quale pr ender e. Le pr opr iet di default sono molto diffuse, specialmente nell'ambito degli oggetti w indow s for m, ma spesso non le si sa r iconoscer e. Anche per quello che abbiamo impar ato fin'or a, per , possiamo scovar e un esempio di pr opr iet di default. Il tipo Str ing espone una pr opr iet par ametr izzata Char s(I), che per mette di saper e quale car atter e si tr ova alla posizione I nella str inga, ad esempio: 1. 2. 3. 4. Dim S As String = "Ciao" Dim C As Char = S.Chars(1) ' > C = "i", poich "i" il carattere alla posizione 1 ' nella stringa S
Ebbene, Char s una pr opr iet di default, ossia possibile scr iver e: 1. Dim S As String = "Ciao" 2. Dim C As Char = S(1) 3. ' > C = "i"
pr opr iet. Non avr ebbe senso, infatti, ad esempio, definir e una pr opr iet pubblica con un Get Fr iend e un Set Pr ivate, poich non sar ebbe pi pubblica (in quanto sia get che set non sono pubblici)! Ecco un esempio: 1. Public Property A() As Byte 2. Get 3. '... End Get 4. Private Set(ByVal value As Byte) 5. 6. '... 7. End Set 8. End Property La pr opr iet A sempr e leggibile, ma modificabile solo all'inter no della classe che la espone. In pr atica, come una nor male pr opr iet per il codice inter no alla classe, ma come una ReadOnly per quello ester no. pur ver o che in questo caso, si sar ebbe potuto r ender la dir ettamente ReadOnly e modificar e dir ettamente il campo da essa avvolto invece che espor r e un Set pr ivato, ma sono punti di vista. Ad ogni modo, l'uso di scope diver sificati per mette di far e di tutto e di pi ed solo un caso che non mi sia venuto in mente un esempio pi significativo.
N.B.: ovviamente, tutto quello che si detto fin'or a sulle pr opr iet nelle classi vale anche per le str uttur e!
51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91. 92. 93. 94. 95. End
End Get End Property Public ReadOnly Property Mass() As Single Get Return (Volume * Density) End Get End Property 'Allo stesso modo, la propriet che espone il membro 'shared deve essere anch'essa shared Public Shared ReadOnly Property CubesCount() As Int32 Get Return _CubesCount End Get End Property 'Ogni volta che un nuovo cubo viene creato, _CubesCount 'viene aumentato di uno, per rispecchiare il nuovo numero 'di istanze della classe Cube esistenti in memoria Sub New() _CubesCount += 1 End Sub End Class Sub Main() Dim Cube1 As New Cube() Cube1.SideLength = 1 Cube1.Density = 2700 Dim Cube2 As New Cube() Cube2.SideLength = 0.9 Cube2.Density = 3500 Console.Write("Cubi creati: ") 'Notate come si accede a un membro condiviso: poich 'appartiene alla classe e non alla singola istanza, vi si 'accede specificando prima il nome della classe, poi 'il comune operatore punto, e successivamente il nome 'del membro. Tutti i membri shared funzionano in questo 'modo Console.WriteLine(Cube.CubesCount) Console.ReadKey() End Sub Module
Facendo cor r er e l'applicazione, si vedr appar ir e a scher mo il numer o 2, poich abbiamo cr eato due oggetti di tipo Cube. Come si vede, il campo CubesCount non r iguar da un solo specifico oggetto, ma la totalit di tutti gli oggetti di tipo Cube, poich un dato globale. In gener ale, esso di dominio della classe Cube, ossia della r appr esentazione pi astr atta dell'essenza di ogni oggetto: per saper e quanti cubi sono stati cr eati, non si pu inter pellar e una singola istanza, per ch essa non "ha per cezione" di tutte le altr e istanze esistenti. Per questo motivo CubesCount un membr o condiviso. Anche in questo caso c' una r istr etta gamma di casi in cui oppor tuno sceglier e di definir e un membr o come condiviso: Quando contiene infor mazioni r iguar danti la totalit delle istanze di una classe, come in questo caso; Quando contiene infor mazioni accessibili e necessar ie a tutte le istanze della classe, come illustr er fr a qualche capitolo; Quando si tr atta di un metodo "di libr er ia". I cosiddetti metodi di libr er ia sono metodi sempr e shar ed che svolgono funzioni gener ali e sono utilizzabili da qualsiasi par te del codice. Un esempio potr ebbe esser e la funzione Math.Abs(x ), che r estituisce il valor e assoluto di x . Come si vede, shar ed poich vi si accede usando il nome della classe. Inoltr e, essa sempr e usabile, poich si tr atta di una semplice funzione matematica, che, quindi, for nisce ser vizi di or dine gener ale;
Quando si tr ova in un modulo, come spiegher nel pr ossimo par agr afo.
Classi Shared
Come!?!? Esistono classi shar ed? Ebbene s. Pu sembr ar e assur do, ma ci sono, ed lecito domandar si quale sia la lor o funzione. Se un membr o shar ed appar tiene a una classe... cosa possiamo dir e di una classe shar ed? A dir e il ver o, abbiamo sempr e usato classi shar ed senza saper lo: i moduli, infatti, non sono altr o che classi condivise (o statiche). Tuttavia, il significato della par ola shar ed, se applicato alle classi, cambia r adicalmente. Un modulo, quindi una classe shar ed, r ende implicitamente shar ed tutti i suoi membr i. Quindi, tutte le pr opr iet, i campi e i metodi appar tenenti ad un modulo - ivi compr esa la Sub Main - sono membr i shar ed. Che senso ha questo? I moduli sono consuetamente usati, al di fuor i delle applicazioni console, per r ender e disponibili a tutto il pr ogetto membr i di par ticolar e r ilevanza o utilit, ad esempio funzioni per il salvataggio dei dati, infor mazioni sulle opzioni salvate, ecceter a... Infatti impossibile definir e un membr o shar ed in un modulo, poich ogni membr o del modulo lo gi di per s: 1. Module Module1 2. Shared Sub Hello() 3. End Sub 4. 5. '... 6. 7. End Sub Il codice sopr a r ipor tato pr ovocher il seguente er r or e: 1. Methods in a Module cannot be declared 'Shared'. Inoltr e, anche possibile acceder e a membr i di un modulo senza specificar e il nome del modulo, ad esempio: 01. 02. 03. 04. 05. 06. 07. 08. 09. 10. 11. 12. Module Module2 Sub Hello() Console.WriteLine("Hello!") End Sub End Module Module Module1 Sub Main() Hello() ' = Module2.Hello() Console.ReadKey() End Sub End Module
Questo fenomeno anche noto col nome di Im po r ts statico . A dir la ver it esiste una piccola differ enza tr a classi statiche e moduli. Una classe pu esser e statica anche solo se tutti i suoi membr i lo sono, ma non gode dell'Impor ts Statico. Un modulo, al contr ar io, oltr e ad aver e tutti i membr i shar ed, gode sempr e dell'Impor ts Statico. Per far la br eve: 01. 02. 03. 04. 05. 06. 07. 08. 09. 10. 11. 12. 13. 14. 15. Module Module2 Sub Hello() Console.WriteLine("Hello Module2!") End Sub End Module Class Class2 Shared Sub Hello() Console.WriteLine("Hello Class2!") End Sub End Class Module Module1 Sub Main()
'Per richiamare l'Hello di Class2, sempre 16. 'necessaria questa sintassi: 17. Class2.Hello() 18. 'Per invocare l'Hello di Module2, invece, basta 19. 'questa, a causa dell'Imports Statico 20. Hello() 21. Console.ReadKey() 22. End Sub 23. End Module
Array List
Si tr atta di una classe per la gestione di liste di elementi. Essendo un tipo r efer ence, quindi, segue che ogni oggetto dichiar ato come di tipo Ar r ayList debba esser e inizializzato pr ima dell'uso con un adeguato costr uttor e. Una volta cr eata un'istanza, la si pu utilizzar e nor malmente. La differ enza con l'Ar r ay r isiede nel fatto che l'Ar r ayList, all'inizio della sua "vita", non contiene nessun elemento, e, di conseguenza occupa r elativamente meno memor ia. Infatti, quando noi inizializziamo un ar r ay, ad esempio cos: 1. Dim A(100) As Int32 nel momento in cui questo codice viene eseguito, il pr ogr amma r ichiede 101 celle di memor ia della gr andezza di 4 bytes ciascuna da r iser var e per i pr opr i dati: che esse siano impostate o meno (all'inizio sono tutti 0), non ha impor tanza, per ch A occuper sempr e la stessa quantit di memor ia. Al contr ar io l'Ar r ayList non "sa" nulla su quanti dati vor r emmo intr odur r e, quindi, ogni volta che un nuovo elemento viene intr odotto, esso si es pan de allocando dinamicamente nuova memor ia solo se ce n' bisogno. In questo r isiede la potenza delle liste. Per aggiunger e un nuovo elemento all'ar r aylist bisogna usar e il metodo d'istanza Add, passandogli come par ametr o il valor e da aggiunger e. Ecco un esempio: 01. Module Module1 02. Class Cube 03. 04. '... 05. End Class 06. 07. Sub Main() 'Crea un nuovo arraylist 08. 09. Dim Cubes As New ArrayList 10. 11. Console.WriteLine("Inserismento cubi:") 12. Console.WriteLine() 13. Dim Cmd As Char 14. Do 15. Console.WriteLine() 16. Dim C As New Cube 17. 'Scrive il numero del cubo 18. Console.Write((Cubes.Count + 1) & " - ") Console.Write("Lato (m): ") 19. 20. C.SideLength = Console.ReadLine 21. 22. Console.Write(" Densit (kg/m<sup>3</sup>): ") C.Density = Console.ReadLine 23. 24. 25. 'Aggiunge un nuovo cubo alla collezione Cubes.Add(C) 26. 27. Console.WriteLine("Termina inserimento? y/n") 28. 29.
Cmd = Console.ReadKey().KeyChar 30. Loop Until Char.ToLower(Cmd) = "y" 31. 32. 'Calcola la massa totale di tutti i cubi nella lista 33. Dim TotalMass As Single = 0 34. 'Notate che l'ArrayList si pu usare come un 35. 'normale array. L'unica differenza sta nel fatto che 36. 'esso espone la propriet Count al posto di Length. 37. 'In genere, tutte le liste espongono Count, che comunque 38. 'ha sempre lo stesso significato: restituisce il numero 39. 'di elementi nella lista 40. For I As Int32 = 0 To Cubes.Count - 1 41. TotalMass += Cubes(I).Mass 42. Next 43. 44. Console.WriteLine("Massa totale: " & TotalMass) 45. Console.ReadKey() 46. End Sub 47. End Module Allo stesso modo, possibile r imuover e o inser ir e elementi con altr i metodi: Remove(x ) : r imuove l'elemento x dall'ar r aylist RemoveAt(x ) : r imuove l'elemento che si tr ova nella posizione x dell'Ar r ayList Index Of(x ) : r estituisce l'indice dell'elemento x Contains(x ) : r estituisce Tr ue se x contenuto nell'Ar r ayList, altr imenti False Clear : pulisce l'ar r aylist eliminando ogni elemento Clone : r estituisce una copia esatta dell'Ar r ayList. Questo ar gomento ver r discusso pi in l nella guida.
Hashtable
L'Hashtable possiede un meccanismo di allocazione della memor ia simile a quello di un Ar r ayList, ma concettualmente differ ente in ter mini di utilizzo. L'Ar r ayList, infatti, non si discosta molto, par lando di pr atica, da un Ar r ay - e infatti vediamo questa somiglianza nel nome: ogni elemento pur sempr e contr addistinto da un indice, e mediante questo possibile ottener ne o modificar ne il valor e; inoltr e, gli indici sono sempr e su base 0 e sono sempr e numer i inter i, gener almente a 32 bit. Quest'ultima peculiar it ci per mette di dir e che in un Ar r ayList gli elementi sono logicamente or dinati. In un Hashtable, al contr ar io, tutto ci che ho esposto fin'or a non vale. Questa nuova classe si basa sull'associazione di una chiav e (key) con un v alo r e (value). Quando si aggiunge un nuovo elemento all'Hashtable, se ne deve specificar e la chiave, che pu esser e qualsiasi cosa: una str inga, un numer o, una data, un oggetto, ecceter a... Quando si vuole r ipescar e quello stesso elemento bisogna usar e la chiave che gli er a stata associata. Usando numer i inter i come chiavi si pu s imulare il compor tamento di un Ar r ayList, ma il meccanismo intr inseco di questo tipo di collezione r imane pur sempr e molto diver so. Ecco un esempio: 01. 02. 03. 04. 05. 06. 07. 08. 09. 10. 11. 12. 13. 'Hashtabel contenente alcuni materiali e le 'relative densit Dim H As New Hashtable 'Aggiunge un elemento, contraddistinto da una chiave stringa H.Add("Acqua", 1000) H.Add("Alluminio", 2700) H.Add("Argento", 10490) H.Add("Nichel", 8800) '... 'Possiamo usare l'hashtable per associare 'facilmente densit ai nostri cubi: Dim C As New Cube(1, H("Argento"))
Notar e che anche possibile far e il contr ar io, ossia: 1. Dim H As New Hashtable 2.
H.Add(1000, "Acqua") 3. H.Add(2700, "Alluminio") 4. H.Add(10490, "Argento") 5. H.Add(8800, "Nichel") In quest'ultimo esempio, l'Hashtable contiene quattr o chiavi costituite da valor i numer ici: non comunque possibile ciclar le usando un For . Infatti, negli Ar r ayList e negli Ar r ay, abbiamo la gar anzia che se la collezione contiene 8 elementi, ad esempio, ci sar anno sempr e degli indici inter i validi tr a 0 e 7; con gli Hashtable, al contr ar io, non possiamo desumer e n ulla sulle chiavi osser vando il semplice numer o di elementi. In gener e, per iter ar e attr aver so gli elementi di un Hashtable, si usano dei costr utti For Each: 1. For Each V As String In H.Values 2. 'Enumera tutti gli elementi di H 3. ' V = "Acqua", "Alluminio", "Argento", ... 4. Next
1. For Each K As Int32 In H.Keys 2. 'Enumera tutte le chiavi 3. 'K = 1000, 2700, 10490, ... 4. Next Per l'iter azione ci vengono in aiuto le pr opr iet Values e Keys, che contengono r ispettivamente tutti i valor i e tutte le chiavi dell'Hashtable: queste collezioni sono a sola lettur a, ossia non possibile modificar le in alcun modo. D'altr onde, abbastanza ovvio: se aggiungessimo una chiave l'Hashtable non sapr ebbe a quale elemento associar la. L'unico modo per modificar le indir etto e consiste nell'usar e metodi come Add, Remove, ecceter a... che sono poi gli stessi di Ar r ayList.
SortedList
Si compor ta esattamente come un Hashtable, solo che gli elementi vengono mantenuti sempr e in or dine secondo la chiave.
50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. End
Dim D As Document = Documents(ID) 'Se coincidono sia l'ID che il nome del file, 'allora restituisce l'oggetto gi esistente If D.FileName = Path Then Return D Else 'Altrimenti restituisce Nothing, dato che non 'possono esistere due documenti con uguale ID, 'o si farebbe confusione Return Nothing End If End If 'Se non esiste un documento con questo ID, lo crea Return New Document(ID, Path) End Function End Class Sub Main() Dim D As Dim E As Dim F As Dim G As Document Document Document Document = = = = Document.Create(0, Document.Create(0, Document.Create(0, Document.Create(1, "C:\testo.txt") "C:\testo.txt") "C:\file.txt") "C:\file.txt")
'Dimostra che se ID e Path coincidono, i due oggetti 'sono la stessa istanza Console.WriteLine(E Is D) 'Dimostra che se l'ID esiste gi, ma il Path differisce, 'l'oggetto restituito Nothing Console.WriteLine(F Is Nothing) Console.ReadKey() End Sub Module
Il codice sopr a r ipor tato cr ea volutamente tutte le situazioni contemplate all'inter no del metodo factor y statico: E ha gli stessi par ametr i di D, quindi nel metodo factor y usato per cr ear e E viene r estituita l'istanza D gi esistente; F ha lo stesso ID, quindi Nothing. A pr ova di ci, sullo scher mo appar ir il seguente output: 1. True 2. True
A26. Costruttori
Come si accennato nelle pr ecedenti lezioni, i costr uttor i ser vono a cr ear e un oggetto, un'istanza mater iale della classe. Ogni costr uttor e, poich ce ne pu esser e anche pi di uno, sempr e dichiar ato usando la keyw or d New e non pu esser e altr imenti. Si possono passar e par ametr i al costr uttor e allo stesso modo di come si passano alle nor mali pr ocedur e o funzioni, specificandoli tr a par entesi. Il codice scr itto nel costr uttor e viene eseguito pr ima di ogni altr o metodo nella classe, per ci pu anche modificar e le var iabili r ead-only (in sola lettur a), come vedr emo in seguito. Anche i moduli possono aver e un costr uttor e e questo viene eseguito pr ima della pr ocedur a Main. Una cosa da tener e bene a mente che, nonostante New sia eseguito pr ima di ogni altr a istr uzione, sia le costanti sia i campi con inizializzator e (ad esempio Dim I As Int32 = 50) sono gi stati inizializzati e contengono gi il lor o valor e. Esempio: 01. Module Module1 02. 'Classe 03. Class Esempio 'Costante pubblica 04. Public Const Costante As Byte = 56 05. 'Variabile pubblica che non pu essere modificata 06. 07. Public ReadOnly Nome As String 'Variabile privata 08. Private Variabile As Char 09. 10. 'Costruttore della classe: accetta un parametro 11. 12. Sub New(ByVal Nome As String) 13. Console.WriteLine("Sto inizializzando un oggetto Esempio...") 'Le variabili ReadOnly sono assegnabli solo nel 14. 'costruttore della classe 15. Me.Nome = Nome 16. 17. Me.Variabile = "c" 18. End Sub End Class 19. 20. 'Costruttore del Modulo 21. Sub New() 22. Console.WriteLine("Sto inizializzando il Modulo...") 23. End Sub 24. 25. Sub Main() 26. Dim E As New Esempio("Ciao") 27. E.Nome = "Io" ' Sbagliato: Nome ReadOnly 28. Console.ReadKey() 29. End Sub 30. 31. End Module Quando si fa cor r er e il pr ogr amma si ha questo output: 1. Sto inizializzando il Modulo... 2. Sto inizializzando un oggetto Esempio... L'esempio mostr a l'or dine in cui vengono eseguiti i costr uttor i: pr ima viene inizializzato il modulo, in seguito viene inizializzato l'oggetto E, che occupa la pr ima linea di codice della pr ocedur a Main. evidente che Main viene eseguita dopo New .
V ariabili ReadOnly
Ho par lato pr ima delle var iabili ReadOnly e ho detto che possono solamente esser e lette ma non modificate. La domanda che viene spontaneo por si : non sar ebbe meglio usar e una costante? La differ enza pi mar cata di quanto sembr i: le costanti devono esser e inizializzate con un valor e immutabile, ossia che definisce il pr ogr ammator e mentr e scr ive il codice (ad esempio, 1, 2, "Ciao" ecceter a); la var iabili ReadOnly possono esser e impostate nel costr uttor e, ma,
cosa pi impor tante, possono assumer e il valor e der ivante da un'espr essione o da una funzione. Ad esempio: 1. Public Const Data_Creazione_C As Date = Date.Now 'Sbagliato! 2. Public ReadOnly Data_Creazione_V As Date = Date.Now ' Giusto La pr ima istr uzione gener a un er r or e "Costant ex pr ession is r equir ed!" (" r ichiesta un'espr essione costante!"), der ivante dal fatto che Date.Now una funzione e, come tale, il suo valor e, pur pr eso una sola volta, non costante, ma pu var iar e. Non si pone nessun pr oblema, invece, per le var iabili ReadOnly, poich sono sempr e var iabili.
Costruttori Shared
I costr uttor i Shar ed sono detti co str utto r i statici e vengono eseguiti solamente quando cr eata la pr im a istanza di una data classe: per questo sono detti anche co str utto r i di classe o di tipo poich non appar tengono ad ogni singolo oggetto che da quella classe pr ende la str uttur a, ma piuttosto alla classe stessa (vedi differ enza tr a classe e oggetto). Un esempio di una possibile applicazione pu esser e questo: si sta scr ivendo un pr ogr amma che tiene tr accia di ogni er r or e r ipor tandolo su un file di log, e gli er r or i vengono gestiti da una classe Er r or s. Data la str uttur a dell'applicazione, possono esister e pi oggetti di tipo Er r or s, ma tutti devono condivider e un file comune... Come si fa? Costr uttor e statico! Questo fa in modo che si apr a il file di log solamente una volta, ossia quando viene istanziato il pr imo oggetto Er r or s. Esempio: 01. Module Esempio 02. Class Errors 03. 'Variabile statica che rappresenta un oggetto in grado 04. 'di scrivere su un file 05. Public Shared File As IO.StreamWriter 06. 07. 'Costruttore statico che inizializza l'oggetto StreamWriter 'Da notare che un costruttore statico NON pu avere 08. 09. 'parametri: il motivo semplice. Se li potesse avere 'e ci fossero pi costruttori normali il compilatore 10. 11. 'non saprebbe cosa fare, poich Shared Sub New 12. 'potrebbe avere parametri diversi dagli altri 13. Shared Sub New() 14. Console.WriteLine("Costruttore statico: sto creando il log...") 15. File = New IO.StreamWriter("Errors.log") 16. End Sub 17. 18. 'Questo il costruttore normale Sub New() 19. Console.WriteLine("Costruttore normale: sto creando un oggetto...") 20. 21. End Sub 22. Public Sub WriteLine(ByVal Text As String) 23. 24. File.WriteLine(Text) End Sub 25. End Class 26. 27. Sub Main() 28. 'Qui viene eseguito il costruttore statico e quello normale 29. Dim E1 As New Errors 30. 'Qui solo quello normale 31. Dim E2 As New Errors 32. 33. E1.WriteLine("Nessun errore") 34. 35. Console.ReadKey() 36. End Sub 37. 38. End Module L'ouput : 1. Costruttore statico: sto creando il log... 2. Costruttore normale: sto creando un oggetto... 3. Costruttore normale: sto creando un oggetto...
Questo esempio evidenzia bene come vengano eseguiti i costr uttor i: mentr e si cr ea il pr imo oggetto Er r or s in assoluto viene eseguito quello statico e in pi anche quello nor male, per i successivi, invece, solo quello nor male. Ovviamente non tr over e il file Er r or s.log con la scr itta "Nessun er r or e" poich nell'esempio il file non stato chiuso. Ripr ender emo lo stesso discor so con i distr uttor i.
< (minor e) >= (maggior e o uguale) <= (minor e o uguale) >> (shift destr o dei bit) << (shift sinistr o dei bit) And (inter sezione logica) Or (unione logica) Not (negazione logica) Xor (aut logico) Mod (r esto della divisione inter a) Like (r icer ca di un patter n: di solito il pr imo ar gomento indica dove cer car e e il secondo cosa cer car e) IsTr ue ( ver o) IsFalse ( falso) CType (conver sione da un tipo ad un altr o) Sintatticamente par lando, nulla vieta di usar e il simbolo And per far e una somma, ma sar ebbe meglio attener si alle nor mali nor me di utilizzo r ipor tate. Ed ecco un esempio: 001. Module Module1 002. 003. Public Structure Fraction 004. 'Numeratore e denominatore 005. Private _Numerator, _Denumerator As Int32 006. 007. Public Property Numerator() As Int32 008. Get 009. Return _Numerator 010. End Get 011. Set(ByVal value As Int32) 012. _Numerator = value 013. End Set 014. End Property 015. 016. Public Property Denumerator() As Int32 017. Get 018. Return _Denumerator 019. End Get Set(ByVal value As Int32) 020. 021. If value <> 0 Then 022. _Denumerator = value 023. Else 'Il denominatore non pu mai essere 0 024. 'Dovremmo lanciare un'eccezione, ma vedremo pi 025. 026. 'avanti come si fa. Per ora lo impostiamo a uno _Denumerator = 1 027. End If 028. End Set 029. End Property 030. 031. 'Costruttore con due parametri, che inizializza numeratore 032. 'e denominatore 033. Sub New(ByVal N As Int32, ByVal D As Int32) 034. Me.Numerator = N 035. Me.Denumerator = D 036. End Sub 037. 038. 'Restituisce la Fraction sottoforma di stringa 039. Function Show() As String 040. Return Me.Numerator & " / " & Me.Denumerator 041. End Function 042. 043. 'Semplifica la Fraction 044. Sub Semplify() 045. 046.
047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118.
Dim X As Int32 'Prende X come il valore meno alto in modulo 'e lo inserisce in X. X servir per un 'calcolo spicciolo del massimo comune divisore X = Math.Min(Math.Abs(Me.Numerator), Math.Abs(Me.Denumerator)) 'Prima di iniziare, per evitare errori, controlla 'se numeratore e denominatore sono entrambi negativi: 'in questo caso li divide per -1 If (Me.Numerator < 0) And (Me.Denumerator < 0) Then Me.Numerator /= -1 Me.Denumerator /= -1 End If 'E con un ciclo scova il valore pi alto di X 'per cui sono divisibili sia numeratore che denominatore '(massimo comune divisore) e li divide per quel numero. 'Continua a decrementare X finch non trova un 'valore per cui siano divisibili sia numeratore che 'denominatore: dato che era partito dall'alto, questo 'sar indubbiamente il MCD Do Until ((Me.Numerator Mod X = 0) And (Me.Denumerator Mod X = 0)) X -= 1 Loop 'Divide numeratore e denominatore per l'MCD Me.Numerator /= X Me.Denumerator /= X End Sub 'Somma due frazioni e restituisce la somma Shared Operator +(ByVal F1 As Fraction, ByVal F2 As Fraction) _ As Fraction Dim F3 As Fraction 'Se i denumeratori sono uguali, si limita a sommare 'i numeratori If F1.Denumerator = F2.Denumerator Then F3.Denumerator = F1.Denumerator F3.Numerator = F1.Numerator + F2.Numerator Else 'Altrimenti esegue tutta l'operazione 'x a x*b + a*y '- + - = --------'y b y*b F3.Denumerator = F1.Denumerator * F2.Denumerator F3.Numerator = F1.Numerator * F2.Denumerator + F2.Numerator * F1.Denumerator End If 'Semplifica la Fraction F3.Semplify() Return F3 End Operator 'Sottrae due Fraction e restituisce la differenza Shared Operator -(ByVal F1 As Fraction, ByVal F2 As Fraction) _ As Fraction 'Somma l'opposto del secondo membro F2.Numerator = -F2.Numerator Return F1 + F2 End Operator 'Moltiplica due frazioni e restituisce il prodotto Shared Operator *(ByVal F1 As Fraction, ByVal F2 As Fraction) _ As Fraction 'Inizializza F3 con il numeratore pari al prodotto 'dei numeratori e il denominatore pari al prodotto dei 'denominatori Dim F3 As Fraction = New Fraction(F1.Numerator * F2.Numerator, _ F1.Denumerator * F2.Denumerator)
119. 120. 121. 122. 123. 124. 125. 126. 127. 128. 129. 130. 131. 132. 133. 134. 135. 136. 137. 138. 139. 140. 141. 142. 143. 144. 145. 146. 147. 148. 149. 150. End
F3.Semplify() Return F3 End Operator 'Divide due frazioni e restituisce il quoziente Shared Operator /(ByVal F1 As Fraction, ByVal F2 As Fraction) _ As Fraction 'Inizializza F3 eseguendo l'operazione: 'a x a y '- / - = - * 'b y b x Dim F3 As Fraction = New Fraction(F1.Numerator * F2.Denumerator, _ F1.Denumerator * F2.Numerator) F3.Semplify() Return F3 End Operator End Structure Sub Main() Dim A As New Fraction(8, 112) Dim B As New Fraction(3, 15) A.Semplify() B.Semplify() Console.WriteLine(A.Show()) Console.WriteLine(B.Show()) Dim C As Fraction = A + B Console.WriteLine("A + B = " & C.Show()) Console.ReadKey() End Sub Module
CTy pe
CType un par ticolar e oper ator e che ser ve per conver tir e da un tipo di dato ad un altr o. Non ancor a stato intr odotto nei pr ecedenti capitoli, ma ne par ler pi ampiamente in uno dei successivi. Scr ivo comunque un par agr afo a questo r iguar do per amor di completezza e utilit di consultazione. Come noto, CType pu eseguir e conver sioni da e ver so tipi conosciuti: la sua sintassi, tuttavia, potr ebbe sviar e dalla cor r etta dichiar azione. Infatti, nonostante CType accetti due par ametr i, la sua dichiar azione ne implica uno solo, ossia il tipo che si desider a conver tir e, in questo caso Fr action. Il secondo par ametr o implicitamente indicato dal tipo di r itor no: se scr ivessimo "CType(ByVal F As Fr action) As Double", questa istr uzione gener er ebbe un CType in gr ado di conver tir e dal tipo Fr action al tipo Double nella manier a consueta in cui siamo abituati: 1. Dim F As Fraction 2. '... 3. Dim D As Double = CType(F, Double) La dichiar azione di una conver sione ver so Double gener a automaticamente anche l'oper ator e CDbl, che si pu usar e tr anquillamente al posto della ver sione completa di CType. Or a conviene por r e l'accento sul come CType viene dichiar ato: la sua sintassi non speciale solo per ch pu esser e confuso da unar io a binar io, ma anche per ch deve dichiar ar e sem pr e se una conver sione W idening (di espansione, ossia senza per dita di dati) o Nar r o w ing (di r iduzione, con possibile per dita di dati). Per questo motivo si deve specificar e una delle suddette keyw or d tr a Shar ed e Oper ator . Ad esempio: Fr action r appr esenta un numer o r azionale e, sebbene Double non r appr esenti tutte le cifr e di un possibile numer o per iodico, possiamo consider ar e che nel passaggio ver so i Double non ci sia per dita di dati n di pr ecisione in modo r ilevante. Possiamo quindi definir e la conver sione Widening: 1. Shared Widening Operator CType(ByVal F As Fraction) As Double 2. Return F.Numerator / F.Denumerator 3. End Operator
Invece, la conver sione ver so un numer o inter o implica non solo una per dita di pr ecisione r ilevante ma anche di dati, quindi la definir emo Nar r ow ing: 1. Shared Narrowing Operator CType(ByVal F As Fraction) As Int32 2. 'Notare l'operatore \ di divisione intera (per maggiori 3. 'informazioni sulla divisione intera, vedere capitolo A6) Return F.Numerator \ F.Denumerator 4. 5. End Operator
Operatori di c onfronto
Gli oper ator i di confr onto godono anch'essi di una car atter istica par ticolar e: devono sempr e esser e definiti in coppia, < con >, = con <>, <= con >=. Non pu infatti esister e un modo per ver ificar e se una var iabile minor e di un altr a e non se maggior e. Se manca uno degli oper ator i complementar i, il compilator e visualizzer un messaggio di er r or e. Ovviamente, il tipo r estituito dagli oper ator i di confr onto sar sempr e Boolean, poich una condizione pu esser e solo o ver a o falsa. 01. 02. 03. 04. 05. 06. 07. 08. 09. 10. 11. 12. 13. 14. 15. 16. 17. 18. Shared Operator <(ByVal F1 As Fraction, ByVal F2 As Fraction) As Boolean 'Converte le frazioni in double e confronta questi valori Return (CType(F1, Double) < CType(F2, Double)) End Operator Shared Operator >(ByVal F1 As Fraction, ByVal F2 As Fraction) As Boolean Return (CDbl(F1) > CDbl(F2)) End Operator Shared Operator =(ByVal F1 As Fraction, ByVal F2 As Fraction) As Boolean Return (CDbl(F1) = CDbl(F2)) End Operator Shared Operator <>(ByVal F1 As Fraction, ByVal F2 As Fraction) As Boolean 'L'operatore "diverso" restituisce sempre un valore opposto 'all'operatore "uguale" Return Not (F1 = F2) End Operator
da notar e che le espr essioni come (a=b) o (a-c>b) r estituiscano un valor e booleano. Possono anche esser e usate nelle espr essioni, ma sconsigliabile, in quanto il valor e di Tr ue spesse volte confuso: in VB.NET -1, ma a r untime 1, mentr e negli altr i linguaggi sempr e 1. Queste espr essioni possono tuttavia esser e assegnate con sicur ezza ad altr i valor i booleani: 1. 2. 3. 4. 5. '... a = 10 b = 20 Console.WriteLine("a maggiore di b: " & (a > b)) 'A schermo compare: "a maggiore di b: False"
Memorizzazione
Iniziamo col chiar ir e un aspetto gi noto. Le str uttur e sono tipi value, mentr e le classi sono tipi r efer ence. Ripetendo concetti gi spiegati pr ecedentemente, le pr ime vengono collocate dir ettamente sullo stack, ossia sulla memor ia pr incipale, nello spazio r iser vato alle var iabili del pr ogr amma, mentr e le seconde vengono collocate in un'altr a par te della memor ia (heap managed) e pongono sullo stack solo un puntator e alla lor o ver a locazione. Questo significa pr incipalmente due cose: L'accesso a una str uttur a e ai suoi membr i pi r apido di un accesso ad una classe; La classe occupa pi memor ia, a par it di membr i (almeno 6 bytes in pi). Inoltr e, una str uttur a si pr esta meglio alla memor izzazione "linear e", ed infatti gr andemente pr efer ita quando si esegue il mar shalling dei dati (ossia la lor o tr asfor mazione da entit alla pur a r appr esentazione in memor ia, costituita da una semplice ser ie di bits). In questo modo, per pr ima cosa molto pi facile legger e e scr iver e str uttur e in memor ia se si devono attuar e oper azioni di basso livello, ed anche possibile r ispar miar e spazio usando un'oppor tuna disposizione delle var iabili. Le classi, al contr ar io, non sono cos or dinate, ed meno facile manipolar le. Non mi addentr er oltr e in questo ambito, ma, per chi volesse, ci sono delle mie dispense che spiegano come funziona la memor izzazione delle str uttur e.
Identit
Un'altr a conseguenza del fatto che le classi siano tipi r efer ence consiste in questo: due oggetti, a par it di campi, sono sem pr e diver si, poich si tr atta di due istanze distinte, seppur contenti gli stessi dati. Due var iabili di tipo str uttur ato che contengono gli stessi dati, invece, sono uguali, per ch non esiste il concetto di istanza per i tipi value. I tipi value sono, per l'appunto, v alo r i, ossia semplici dati, infor mazione pur a, ammasso di bits, n pi n meno. Per questo motivo, ad esempio, impossibile modificar e una pr opr iet di una str uttur a tr amite l'oper ator e punto, poich sar ebbe come tentar e di modificar e la par te decimale di 1.23: 1.23 sempr e 1.23, si tr atta di un valor e e non lo si pu modificar e, ma al massimo si pu assegnar e un altr o valor e alla var iabile che lo contiene. Al contr ar io, gli oggetti sono entit pi complesse: non si tr atta di "infor mazione pur a" come i tipi str uttur ati. Un oggetto contiene molteplici campi e pr opr iet sempr e modificabili, per ch indicano solo un aspetto dell'oggetto: ad esempio, il color e di una par ete sempr e modificabile: basta tinteggiar e la par ete con un nuovo color e. Come dir e che "la par ete" non come un numer o, che sempr e quello e basta: essa un qualcosa di concr eto con diver se pr opr iet. Sono concetti molto astr atti e per cer ti ver si molto ar dui da capir e di pr imo acchito... io ho tentato di far e esempi convinceti, ma sper o che con il tempo impar er ete da soli a inter ior izzar e queste differ enze - differ enze che, pur
A29. L'Ereditariet
Eccoci ar r ivati a par lar e degli aspetti peculiar i di un linguaggio ad oggetti! Iniziamo con l'Eder editar iet.
L'ereditariet la possibilit di un linguaggio ad oggetti di far der ivar e una classe da un'altr a: in questo caso, la pr ima assume il nome di classe der iv ata, mentr e la seconda quello di classe base. La classe der ivata acquisisce tutti i membr i della classe base, ma pu r idefinir li o aggiunger ne di nuovi. Questa car atter istica di ogni linguaggio Object Or iented par ticolar mente efficace nello schematizzar e una r elazione "is-a" (ossia " un"). Per esempio, potr emmo definir e una classe Vegetale, quindi una nuova classe Fior e, che er edita Vegetale. Fior e un Vegetale, come mostr a la str uttur a ger ar chica dell'er editar iet. Se definissimo un'altr a classe Pr imula, der ivata da Fior e, dir emmo che Pr imula un Fior e, che a sua volta un Vegetale. Quest'ultimo tipo di r elazione, che cr ea classi der ivate che sar anno basi per er editar e altr e classi, si chiama er editar iet indir e tta. Passiamo or a a veder e come si dichiar a una classe der ivata: 1. Class [Nome] 2. Inherits [Classe base] 3. 'Membri della classe 4. End Class La keyw or d Inher its specifica quale classe base er editar e: si pu aver e solo UNA dir ettiva Inher its per classe, ossia non possibile er editar e pi classi base. In questo fr angente, si pu scopr ir e come le pr opr iet siano utili e flessibili: se una classe base definisce una var iabile pubblica, questa diver r par te anche della classe der ivata e su tale var iabile ver r anno basate tutte le oper azioni che la coinvolgono. Siccome possibile che la classe der ivata voglia r idefinir e tali oper azioni e molto pr obabilmente anche l'utilizzo della var iabile, sempr e consigliabile dichiar ar e campi Pr ivate avvolti da una pr opr iet, poich non c' mai alcun per icolo nel modificar e una pr opr iet in classi der ivate, ma non possibile modificar e i campi nella stessa classe. Un semplice esempio di er editar iet: 01. 02. 03. 04. 05. 06. 07. 08. 09. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. Class Person 'Per velocizzare la scrittura del codice, assumiamo che 'questi campi pubblici siano propriet Public FirstName, LastName As String Public ReadOnly Property CompleteName() As String Get Return FirstName & " " & LastName End Get End Property End Class 'Lo studente, ovviamente, una persona Class Student 'Student eredita da Person Inherits Person 'In pi, definisce anche questi campi pubblici 'La scuola frequentata Public School As String 'E l'anno di corso Public Grade As Byte End Class
In seguito, si pu utilizzar e la classe der ivata come si sempr e fatto con ogni altr a classe. Nel far ne uso, tuttavia, necessar io consider ar e che una classe der ivata possiede non solo i membr i che il pr ogr ammator e ha esplicitamente definito nel suo cor po, ma anche tutti quei membr i pr esenti nella classe base che si sono implicitamente acquisiti nell'atto stesso di scr iver e "Inher its". Se vogliamo, possiamo assimilar e una classe ad un insieme, i cui elementi sono i
suoi membr i: una classe base sottoinsieme della cor r ispondente classe der ivata. Di solito, l'ambiente di sviluppo aiuta molto in questo, poich, nei sugger imenti pr oposti dur ante la scr ittur a del codice, vengono automaticamente inser ite anche le voci er editate da altr e classi. Ci che abbiamo appena visto vale anche per er editar iet indir etta: se A er edita da B e B er edita da C, A dispor r dei membr i di B, alcuni dei quali sono anche membr i di C (semplice pr opr iet tr ansitiva). Or a, per , bisogna por r e un bel Nota Bene alla questione. Infatti, non tutto semplice come sembr a. For se nessuno si chiesto che fine fanno gli specificator i di accesso quando un membr o viene er editato da una classe der ivata. Ebbene, esistono delle pr ecise r egole che indicano come gli scope vengono tr attati quando si er edita: Un membr o Public o Fr iend della classe base diventa un membr o Public o Fr iend della classe der ivata (in pr atica, non cambia nulla; viene er editato esattamente com'); Un membr o Pr iv ate della classe base non accessibile dalla classe der ivata, poich il suo ambito di visibilit impedisce a ogni chiamante ester no alla classe base di far vi r ifer imento, come gi visto nelle lezioni pr ecedenti; Un membr o Pr o tected della classe base diventa un membr o Pr otected della classe der ivata, ma si compor ta come un membr o Pr ivate. Ed ecco che abbiamo intr odotto uno degli specificator i che ci er avamo lasciati indietr o. I membr i Pr otected sono par ticolar mente utili e costituiscono una sor ta di "scappatoia" al fatto che quelli pr ivati non subiscono l'er editar iet. Infatti, un memebr o Pr otected si compor ta esattamente come uno Pr ivate, con un'unica eccezione: er editabile, ed in questo caso diventa un membr o Pr otected della classe der ivata. Lo stesso discor so vale anche per Pr otected Fr iend. Ecco uno schema che esemplifica il compor tamento dei pr incipali Scope:
Esempio: 001. Module Esempio 002. Class Person 'Due campi protected 003. 004. Protected _FirstName, _LastName As String 005. 'Un campo private readonly: non c' ragione di rendere 'questo campo Protected poich la data di nascita non 006. 'cambia ed sempre accessibile tramite la propriet 007. 008. 'pubblica BirthDay 009. Private ReadOnly _BirthDay As Date 010. Public Property FirstName() As String 011. 012. Get 013. Return _FirstName 014. End Get 015. Set(ByVal Value As String) 016. If Value <> "" Then 017. _FirstName = Value 018. End If 019. End Set 020. End Property 021. Public Property LastName() As String 022. 023. Get Return _LastName 024. End Get 025. Set(ByVal Value As String) 026. If Value <> "" Then 027. 028.
029. 030. 031. 032. 033. 034. 035. 036. 037. 038. 039. 040. 041. 042. 043. 044. 045. 046. 047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099. 100.
_LastName = Value End If End Set End Property Public ReadOnly Property BirthDay() As Date Get Return _BirthDay End Get End Property Public ReadOnly Property CompleteName() As String Get Return _FirstName & " " & _LastName End Get End Property 'Costruttore che accetta tra parametri obbligatori Sub New(ByVal FirstName As String, ByVal LastName As String, _ ByVal BirthDay As Date) Me.FirstName = FirstName Me.LastName = LastName Me._BirthDay = BirthDay End Sub End Class 'Lo studente, ovviamente, una persona Class Student 'Student eredita da Person Inherits Person 'La scuola frequentata Private _School As String 'E l'anno di corso Private _Grade As Byte Public Property School() As String Get Return _School End Get Set(ByVal Value As String) If Value <> "" Then _School = Value End If End Set End Property Public Property Grade() As Byte Get Return _Grade End Get Set(ByVal Value As Byte) If Value > 0 Then _Grade = Value End If End Set End Property 'Questa nuova propriet si serve anche dei campi FirstName 'e LastName nel modo corretto, poich sono Protected anche 'nella classe derivata e fornisce un profilo completo 'dello studente Public ReadOnly Property Profile() As String Get 'Da notare l'accesso a BirthDay tramite la propriet 'Public: non possibile accedere al campo _BirthDay 'perch privato nella classe base Return _FirstName & " " & _LastName & ", nato il " & _ BirthDay.ToShortDateString & " frequenta l'anno " & _ _Grade & " alla scuola " & _School End Get End Property
101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121. 122. 123. 124. 125. 126. 127. 128. 129. 130. 131. 132. 133. 134. 135. End L'output: 1. 2. 3. 4. 5.
'Altra clausola importante: il costruttore della classe 'derivata deve sempre richiamare il costruttore della 'classe base Sub New(ByVal FirstName As String, ByVal LastName As String, _ ByVal BirthDay As Date, ByVal School As String, _ ByVal Grade As Byte) MyBase.New(FirstName, LastName, BirthDay) Me.School = School Me.Grade = Grade End Sub End Class Sub Main() Dim P As New Person("Pinco", "Pallino", Date.Parse("06/07/90")) Dim S As New Student("Tizio", "Caio", Date.Parse("23/05/92"), _ "Liceo Classico Ugo Foscolo", 2) Console.WriteLine(P.CompleteName) 'Come si vede, la classe derivata gode degli stessi membri 'di quella base, acquisiti secondo le regole 'dell'ereditariet appena spiegate Console.WriteLine(S.CompleteName) 'E in pi ha anche i suoi nuovi membri Console.WriteLine(S.Profile) 'Altra cosa interessante: dato che Student derivata da 'Person ed espone tutti i membri di Person, pi altri, 'non sbagliato assegnare un oggetto Student a una 'variabile Person P = S Console.WriteLine(P.CompleteName) Console.ReadKey() End Sub Module
Pinco Pallino Tizio Caio Tizio Caio, nato il 23/5/1992 frequenta l'anno 2 alla scuola Liceo Classico Ugo Foscolo Tizio Caio
(Per maggior i infor mazioni sulle oper azioni con le date, veder e il capitolo B13) Anche se il sor gente ampiamente commentato mi soffer mer ei su alcuni punti caldi. Il costr uttor e della classe der ivata deve sem pr e r ichiamar e il costr uttor e della classe base, e questo avviene tr amite la keyw or d MyBase che, usata in una classe der ivata, fa r ifer imento alla classe base cor r ente: attr aver so questa par ola r iser vata possibile anche r aggiunger e i membr i pr ivati della classe base, ma si fa r ar amente, poich il suo impiego pi fr equente quello di r ipr ender e le vecchie ver sioni di metodi modificati. Il secondo punto r iguar da la conver sione di classi: passar e da Student a Per son non , come potr ebbe sembr ar e, una conver sione di r iduzione, poich dur ante il pr ocesso, nulla va per duto nel ver o senso della par ola. Cer to, si per dono le infor mazioni supplementar i, ma alla classe base queste non ser vono: la sicur ezza di eseguir e la conver sione r isiede nel fatto che la classe der ivata gode degli stessi membr i di quella base e quindi non si cor r e il r ischio che ci sia r ifer imento a un membr o inesistente. Questo invece si ver ifica nel caso opposto: se una var iabile di tipo Student assumesse il valor e di un oggetto Per son, School e Gr ade sar ebber o pr ivi di valor e e ci gener ebbe un er r or e. Per eseguir e questo tipo di passaggi necessar io l'oper ator e Dir ectCast.
A30. Polimorfismo
Il polimor fismo la capacit di un linguaggio ad oggetti di r idefinir e i membr i della classe base in modo tale che si compor tino in manier a differ ente all'inter no delle classi der ivate. Questa possibilit quindi str ettamente legata all'er editar iet. Le keyw or ds che per mettono di attuar ne il funzionamento sono due: Over r idable e Over r ides. La pr ima deve mar car e il membr o della classe base che si dovr r idefinir e, mentr e la seconda contr assegna il membr o della classe der ivata che ne costituisce la nuova ver sione. da notar e che solo membr i della stessa categor ia con no m e ug uale e sig natur e identica (ossia con lo stesso numer o e lo stesso tipo di par ametr i) possono subir e questo pr ocesso: ad esempio non si pu r idefinir e la pr ocedur a Show Tex t() con la pr opr iet Tex t, per ch hanno nome differ ente e sono di diver sa categor ia (una una pr ocedur a e l'altr a una pr opr iet). La sintassi semplice: 1. 2. 3. 4. 5. 6. 7. 8. Class [Classe base] Overridable [Membro] End Class Class [Classe derivata] Inherits [Classe base] Overrides [Membro] End Class
Questo esempio pr ende come base la classe Per son definita nel capitolo pr ecedente e sviluppa da questa la classe Teacher (insegnante), modificandone le pr opr iet LastName e CompleteName: 001. Module Module1 002. Class Person 003. Protected _FirstName, _LastName As String 004. Private ReadOnly _BirthDay As Date 005. 006. Public Property FirstName() As String 007. Get 008. Return _FirstName 009. End Get 010. Set(ByVal Value As String) 011. If Value <> "" Then 012. _FirstName = Value 013. End If 014. End Set 015. End Property 016. 017. 'Questa propriet sar ridefinita nella classe Teacher 018. Public Overridable Property LastName() As String 019. Get 020. Return _LastName 021. End Get 022. Set(ByVal Value As String) 023. If Value <> "" Then 024. _LastName = Value 025. End If End Set 026. End Property 027. 028. Public ReadOnly Property BirthDay() As Date 029. Get 030. Return _BirthDay 031. End Get 032. End Property 033. 034. 'Questa propriet sar ridefinita nella classe Teacher 035. Public Overridable ReadOnly Property CompleteName() As String 036. Get 037. Return _FirstName & " " & _LastName 038. End Get 039. 040.
041. 042. 043. 044. 045. 046. 047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112.
End Property 'Costruttore che accetta tra parametri obbligatori Sub New(ByVal FirstName As String, ByVal LastName As String, _ ByVal BirthDay As Date) Me.FirstName = FirstName Me.LastName = LastName Me._BirthDay = BirthDay End Sub End Class Class Teacher Inherits Person Private _Subject As String Public Property Subject() As String Get Return _Subject End Get Set(ByVal Value As String) If Value <> "" Then _Subject = Value End If End Set End Property 'Ridefinisce la propriet LastName in modo da aggiungere 'anche il titolo di Professore al cognome Public Overrides Property LastName() As String Get Return "Prof. " & _LastName End Get Set(ByVal Value As String) 'Da notare l'uso di MyBase e LastName: in questo 'modo si richiama la vecchia versione della 'propriet LastName e se ne imposta il 'valore. Viene quindi richiamato il blocco Set 'vecchio: si risparmiano due righe di codice 'poich non si deve eseguire il controllo 'If su Value MyBase.LastName = Value End Set End Property 'Ridefinisce la propriet CompleteName in modo da 'aggiungere anche la materia insegnata e il titolo di 'Professore Public Overrides ReadOnly Property CompleteName() As String Get 'Anche qui viene richiamata la vecchia versione di 'CompleteName, che restituisce semplicemente il 'nome completo Return "Prof. " & MyBase.CompleteName & _ ", dottore in " & Subject End Get End Property Sub New(ByVal FirstName As String, ByVal LastName As String, _ ByVal BirthDay As Date, ByVal Subject As String) MyBase.New(FirstName, LastName, BirthDay) Me.Subject = Subject End Sub End Class Sub Main() Dim T As New Teacher("Mario", "Rossi", Date.Parse("01/01/1950"), _ "Letteratura italiana") 'Usiamo le nuove propriet, ridefinite nella classe 'derivata Console.WriteLine(T.LastName) '> "Prof. Rossi"
Console.WriteLine(T.CompleteName) 113. '> "Prof. Mario Rossi, dottore in Letteratura italiana" 114. 115. Console.ReadKey() 116. End Sub 117. End Module In questo modo si visto come r idefinir e le pr opr iet. Ma pr ima di pr oseguir e vor r ei far notar e un compor tamento par ticolar e: 1. Dim P As Person = T 2. Console.WriteLine(P.LastName) 3. Console.WriteLine(P.CompleteName) In questo caso ci si aspetter ebbe che le pr opr iet r ichiamate da P agiscano come specificato nella classe base (ossia senza includer e altr e infor mazioni se non il nome ed il cognome), poich P di quel tipo. Questo, invece, non accade. Infatti, P e T, dato che abbiamo usato l'oper ator e =, puntano or a allo stesso oggetto in memor ia, solo che P lo vede come di tipo Per son e T come di tipo Teacher . Tuttavia, l'oggetto r eale di tipo Teacher e per ci i suoi metodi sono a tutti gli effetti quelli r idefiniti nella classe der ivata. Quando P tenta di r ichiamar e le pr opr iet in questione, ar r iva all'indir izzo di memor ia dove sono conser vate le istr uzioni da eseguir e, solo che queste si tr ovano all'inter no di un oggetto Teacher e il lor o codice , di conseguenza, diver so da quello della classe base. Questo compor tamento, al contr ar io di quanto potr ebbe sembr ar e, utilissimo: ci per mette, ad esempio, di memor izzar e in un ar r ay di per sone sia studenti che insegnanti, e ci per mette di scr iver e a scher mo i lor o nomi differ entemente senza eseguir e una conver sione. Ecco un esempio: 01. 02. 03. 04. 05. 06. 07. 08. 09. 10. Dim Ps(2) As Person Ps(0) = New Person("Luigi", "Ciferri", Date.Parse("7/7/1982")) Ps(1) = New Student("Mario", "Bianchi", Date.Parse("19/10/1991"), _ "Liceo Scientifico Tecnologico Cardano", 5) Ps(2) = New Teacher("Ubaldo", "Nicola", Date.Parse("11/2/1980"), "Filosofia") For Each P As Person In Ps Console.WriteLine(P.CompleteName) Next
lecito assegnar e oggetti Student e Teacher a una cella di un ar r ay di Per son in quanto classi der ivate da Per son. I metodi r idefiniti, tuttavia, r imangono e modificano il compor tamento di ogni oggetto anche se r ichiamato da una "mascher a" di classe base. Pr oviamo or a con un piccolo esempio sul polimor fismo dei metodi: 01. 02. 03. 04. 05. 06. 07. 08. 09. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. Class A Public Overridable Sub ShowText() Console.WriteLine("A: Testo di prova") End Sub End Class Class B Inherits A 'Come si vede il metodo ha: '- lo stesso nome: ShowText '- lo stesso tipo: una procedura '- gli stessi parametri: senza parametri 'Qualunque tentativo di cambiare una di queste caratteristiche 'produrr un errore del compilatore, che comunica di non poter 'ridefinire il metodo perch non ne esistono di uguali nella 'classe base Public Overrides Sub ShowText() Console.WriteLine("B: Testo di prova") End Sub End Class
Ultime due pr ecisazioni: le var iabili non possono subir e polimor fismo, cos come i membr i statici.
Shadow ing
Se il polimor fismo per mette di r idefinir e accur atamente membr i che pr esentano le stesse car atter istiche, ed quindi pi pr eciso, lo shadow ing per mette letter almente di oscur ar e qualsiasi membr o che abbia lo stesso nome, indipendentemente dalla categor ia, dalla signatur e e dalla qauntit di ver sioni alter native pr esenti. La keyw or d da usar e Shadow s, e si applica solo sul membr o della classe der ivata che intendiamo r idefinir e, oscur ando l'omonimo nella classe base. Ad esempio: 01. Module Esempio 02. Class Base 03. Friend Control As Byte End Class 04. 05. 06. Class Deriv 07. Inherits Base 08. Public Shadows Sub Control(ByVal Msg As String) Console.WriteLine("Control, seconda versione: " & Msg) 09. End Sub 10. End Class 11. 12. 13. Sub Main() Dim B As New Base 14. 15. Dim D As New Deriv 16. 17. 'Entrambe le classe hanno lo stesso membro di nome 18. '"Control", ma nella prima un campo friend, 'mentre nella seconda una procedura pubblica 19. Console.WriteLine(B.Control) 20. D.Control("Ciao") 21. 22. Console.ReadKey() 23. End Sub 24. 25. End Module Come si vede, la sintassi come quella di Over r ides: Shadow s viene specificato tr a lo specificator e di accesso (se c'e') e la tipologia del membr o (in questo caso Sub, pr ocedur a). Entr ambe le classi pr esentano Contr ol, ma la seconda ne fa un uso totalmente diver so. Ad ogni modo l'uso dello shadow ing in casi come questo for tememente sconsigliabile: pi che altr o lo si usa per assicur ar si che, se mai dovesse uscir e una nuova ver sione della classe base con dei nuovi metodi che pr esentano lo stesso nome di quelli della classe der ivata da noi definita, non ci siano pr oblemi di compatibilit. Se una var iabile dichiar ata Shadow s, viene omessa la keyw or d Dim.
CTy pe
CType l'oper ator e di conver sione univer sale e per mette la conver sione di qualsiasi tipo in qualsiasi altr o tipo, almeno quando questa possibile. La sintassi molto semplice: [Variabile] = CType([Valore da convertire], [Tipo in cui convertire]) Ad esempio: 1. Dim I As Int32 = 50 2. 'Converte I in un valore Byte 3. Dim B As Byte = CType(I, Byte) Questa lista r ipor ta alcuni casi in cui bene usar e esplicitamente l'oper ator e di conver sione CType: Per conver tir e un valor e inter o o decimale in un valor e booleano; Per conver tir e un valor e Single o Double in Decimal; Per conver tir e un valor e inter o con segno in uno senza segno; Per conver tir e un valor e inter o senza segno in uno con segno della stessa ampiezza (ad esempio da UInt32 a Int32). Oltr e a CType, esistono moltissime ver sioni pi cor te di quest'ultimo che conver tono in un solo tipo: CInt conver te sempr e in Int32, CBool sempr e in booleano, CByte in byte, CShor t Int16, CLong, CUShor t, CULong, CUInt, CSng, CDbl, CDec, CStr , CDate, CObj. inoppor tuno utilizzar e CStr poich ci si pu sevir e della funzione ToStr ing er editata da ogni classe da System.Object; allo stesso modo, meglio evitar e CDate, a favor e di Date.Par se, come si vedr nella lezione "DateTimePicker : Lavor ar e con le date". CType pu comunque esser e usato per qualsiasi altr a conver sione contemplabile, anche e sopr attutto con i tipi Refer ence.
Direc tCast
Dir ectCast lavor a in un modo legger mente di diver so: CType tenta sempr e di conver tir e l'ar gomento di or gine nel tipo specificato, mentr e Dir ectCast lo fa solo se tale valor e pu esser e sottoposto al casting (al "passaggio" da un tipo all'altr o, piuttosto che alla conver sione) ver so il tipo indicato. Per ci non , ad esempio, in gr ado di conver tir e una str inga in inter o, e neanche un valor e shor t in un integer , sebbene questa sia una conver sione di espansione. Questi ultimi esempi non sono validi anche per ch questo par ticolar e oper ator e pu accettar e come ar gomenti solo oggetti, e quindi tipi Refer ence. In gener ale, quindi, dato il legger o r ispar mio di tempo di Dir ectCast in confr onto a CType, conveniente usar e Dir ectCast:
Per eseguir e l'unbox ing di tipi value; Per eseguir e il casting di una classe base in una classe der ivata (vedi "Er editar ieta'"); Per eseguir e il casting di un oggetto in qualsiasi altr o tipo r efer ence; Per eseguir e il casting di un oggetto in un'inter faccia. N.B.: notar e che tutti i casi sopr a menzionati hanno come tipo di par tenza un oggetto, pr opr io come detto pr ecedentemente.
Try Cast
Tr yCast ha la stessa sintassi di Dir ectCast, e quindi anche di CType, ma nasconde un piccolo pr egio. Spesso, quando si esegue una conver sione si deve pr ima contr ollar e che la var iabile in questione sia di un deter minato tipo base o implementi una deter minata inter faccia e solo successivamente si esegue la conver sione ver a e pr opr ia. Con ci si contr olla due volte la stessa var iabile, pr ima con l'If e poi con Dir ectCast. Tr yCast, invece, per mette di eseguir e il tutto in un unico passaggio e r estituisce semplicemente Nothing se il cast fallisce. Questo appr occio r ende tale oper ator e cir ca 0,2 volte pi veloce di Dir ectCast.
Convert
Esiste, poi, una classe statica definita del namespace System - il namespace pi impor tante di tutto il Fr amew or k. Questa classe, essendo statica (e qui facciamo un po' di r ipasso), espone solo metodi statici e non pu esser e istanziata (non espone costr uttor i e comunque sar ebbe inutile far lo). Essa contiene molte funzioni per eseguir e la conver sione ver so i tipi di base ed espone anche un impor tante valor e che vedr emo molto pi in l par lando dei database. Essenzialmente, tutti i suoi metodi hanno un nome del tipo "ToXXXX", dove XXXX uno qualsiasi tr a i tipi base: ad esempio, c', ToInt32, ToDouble, ToByte, ToStr ing, ecceter a... Un esempio: 01. 02. 03. 04. 05. 06. 07. 08. 09. Dim Dim ' D Dim ' S Dim ' N Dim Dim I D = S = N = K A As Int32 As Double 34.0 As String "34" As Single 34.0 As String As Date = = 34 = Convert.ToDouble(I) = Convert.ToString(D) = Convert.ToSingle(S) = "31/12/2008" Convert.ToDate(K)
All'inter no di Conver t sono definiti anche alcuni metodi per conver tir e una str inga da e ver so il for mato Base64, una par ticolar e codifica che utilizza solo 64 car atter i, al contr ar io dell'ASCII standar d che ne utilizza 128 o di quello esteso che ne utilizza 256. Tale codifica viene usata ad esempio nell'invio delle e-mail e pr oduce output un ter zo pi voluminosi degli input, ma in compenso tutti i car atter i contemplati sono sempr e leggibili (non ci sono, quindi, car atter i "speciali"). Per appr ofondir e l'ar gomento, cliccate su w ik ipedia. Per r ipr ender e il discor so conver sioni, sar ebbe lecito pensar e che la definizione di una classe del gener e, quando esistono gi altr i oper ator i come CType e Dir ectCast - altr ettanto qualificati e per for manti - sia abbastanza r idondante. Pi o meno cos. Utilizzar e la classe Conver t al posto degli altr i oper ator i di casting non gar antisce alcun vantaggio di sor ta, e pu anche esser e r icondotta ad una questione di gusti (io per sonalmente pr efer isco CType). Ad ogni modo, c' da dir e un'altr a cosa al r iguar do: i metodi di Conver t sono piuttosto r igor osi e for niscono dei ser vizi molto mir ati. Per questo motivo, in casi molto vantaggiosi, ossia quando il cast pu esser e ottimizzato, essi eseguono pur sempr e le stesse istr uzioni: al contr ar io, CType pu "ingegnar si" e for nir e una conver sione pi efficiente. Quest'ultimo, quindi, legger mente pi elastico ed adattabile alle situazioni.
Parse
Un'oper azione di par sing legge una str inga, la elabor a, e la conver te in un valor e di altr o tipo. Abbiamo gi visto un utilizzo di Par se nell'uso delle date, poich il tipo Date espone il metodo Par se, che ci per mette di conver tir e la r appr esentazione testuale di una data in un valor e date appr opr iato. Quasi tutti i tipi base del Fr amew or k espongono un metodo Par se, che per mette di passar e da una str inga a quel tipo: possiamo dir e che Par se l'inver sa di ToStr ing. Ad esempio: 01. 02. 03. 04. 05. 06. 07. 08. 09. 10. Dim I As Int32 I = Int32.Parse("27") ' I = 27 I = Int32.Parse("78.000") ' Errore di conversione! I = Int32.Parse("123,67") ' Errore di conversione!
Come vedete, Par se ha pur sempr e dei limiti: ad esempio non contempla i punti e le vir gole, sebbene la conver sione, vista da noi "umani", sia del tutto lecita (78.000 settantottomila con il separ ator e delle migliaia e 123,67 un numer o decimale, quindi conver tibile in inter o con un ar r otondamento). Inoltr e, Par se viene anche automaticamente chiamato dai metodi di Conver t quando il valor e passato una str inga. Ad esempio, Conver t.ToInt32("27") r ichiama a sua volta Int32.Par se("27"). Per far vi veder e in che modo CType pi flessibile, r ipetiamo l'esper imento di pr ima usando appunto CType: 01. 02. 03. 04. 05. 06. 07. 08. 09. 10. Dim I As Int32 I = CType("27", Int32) ' I = 27 I = CType("78.000", Int32) ' I = 78000 I = CType("123,67", Int32) ' I = 124
Try Parse
Una var iante di Par se Tr yPar se, anch'essa definita da molti tipi base. La sostanziale differ enza r isiede nel fatto che, mentr e la pr ima pu gener ar e er r or i nel caso la str inga non possa esser e conver tita, la seconda non lo fa, ma non r estituisce neppur e il r isultato. Infatti, Tr yPar se accetta due ar gomenti, come nella seguente signatur e: 1. TryParse(ByVal s As String, ByRef result As [Tipo]) As Boolean Dove [Tipo] dipende da quale tipo base la stiamo r ichiamando: Int32.Tr yPar se avr il secondo ar gomento di tipo Int32, Date.Tr yPar se ce l'avr di tipo Date, e cos via. In sostanza Tr yPar se tenta di eseguir e la funzione Par se sulla str inga s: se ci r iesce, r estituisce Tr ue e pone il r isultato in r esult (notar e che il par ametr o passato per indir izzo); se non ci r iesce, r estituisce False. Ecco un esempio: 01. 02. 03. 04. 05. 06. 07. 08. 09. Dim S As String = "56/0/1000" 'S contiene una data non valida Dim D As Date If Date.TryParse(S, D) Then Console.WriteLine(D.ToLongDateString()) Else Console.WriteLine("Data non valida!") End If
Ty peOf
TypeOf ser ve per contr ollar e se una var iabile di un cer to tipo, der iva da un cer to tipo o implementa una cer ta inter faccia, ad esempio: 1. Dim I As Int32 2. If TypeOf I Is Int32 Then 3. 'Questo blocco viene eseguito poich I di tipo Int32 4. End If Oppur e: 1. Dim T As Student 2. If TypeOf T Is Person Then 3. 'Questo blocco viene eseguito perch T, essendo Student, 4. 'anche di tipo Person, in quanto Student una sua classe 5. 'derivata 6. End If Ed infine un esempio sulle inter facce, che potr ete tor nar e a guar dar e da qui a qualche capitolo: 1. Dim K(9) As Int32 2. If TypeOf Is IEnumerable Then 3. 'Questo blocco viene eseguito poich gli array implementano 4. 'sempre l'interfaccia IEnumerable 5. End If
A31. L'Overloading
L'Over loading la capacit di un linguaggio ad oggetti di poter definir e, nella stessa classe, pi var ianti dello stesso metodo. Per poter eseguir e cor r ettamente l'over loading, che ogni var iante del metodo abbia queste car atter istiche: Sia della stessa categor ia (pr ocedur a O funzione, anzi, per dir la in modo pi esplicito: pr ocedur a Xor funzione); Abbia lo stesso nome; Abbia signatur e diver sa da tutte le altr e var ianti. Per color o che non se lo r icor dasser o, la signatur e di un metodo indica il tipo e la quantit dei suoi par ametr i. Questo il tr atto essenziale che per mette di differ enziar e concr etamente una var iante dall'altr a. Per far e un esempio, il metodo Console.Wr iteLine espone ben 18 ver sioni diver se, che ci consentono di stampar e pr essoch ogni dato sullo scher mo. Fr a quelle che non abbiamo mai usato, ce n' una in par ticolar e che vale la pena di intr odur r e or a, poich molto utile e flessibile. Essa pr evede un pr imo par ametr o di tipo str inga e un secondo Par amAr r ay di oggetti: 1. Console.WriteLine("stringa", arg0, arg1, arg2, arg3, ...) Il pr imo par ametr o pr ende il nome di str ing a di fo r m ato , poich specifica il for mato in cui i dati costituiti dagli ar gomenti addizionali dovr anno esser e visualizzati. All'inter no di questa str inga, si possono specificar e, oltr e ai nor mali car atter i, dei codici speciali, nella for ma "{I}", dove I un numer o compr eso tr a 0 e il numer o di par amtr i meno uno: "{I}" viene detto segnaposto e ver r sostituito dal par ametr o I nella str inga. Ad esempio: 1. 2. 3. 4. A = 1 B = 3 Console.WriteLine("La somma di {0} e {1} {2}.", A, B, A + B) '> "La somma di 1 e 3 4."
Ulter ior i infor mazioni sulle str inghe di for mato sono disponibili nel capitolo "Magie con le str inghe". Ma or a passiamo alla dichiar azione dei metodi in over load. La par ola chiave da usar e, ovviamente, Over loads, specificata poco dopo lo scope, e dopo gli eventuali Over r idable od Over r ides. Le entit che possono esser e sottoposte ad over load, oltr e ai metodi, sono: Metodi statici Oper ator i Pr opr iet Costr uttor i Distr uttor i Anche se gli ultimi due sono sempr e metodi - per or a tr alasciamo i distr uttor i, che non abbiamo ancor a analizzato - bene specificar e con pr ecisione, per ch a compiti speciali spesso cor r ispondono compor tamenti altr ettanto speciali. Ecco un semplicissimo esempio di over load: 01. Module Module1 02. 03. 'Restituisce il numero di secondi passati dalla data D a oggi 04. Private Function GetElapsed(ByVal D As Date) As Single 05. Return (Date.Now - D).TotalSeconds 06. End Function 07. 08. 'Come sopra, ma il parametro di tipo intero e indica 09. 'un anno qualsiasi 10. Private Function GetElapsed(ByVal Year As Int32) As Single 11. 'Utilizza Year per costruire un nuovo valore Date 12.
13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. End
'e usa la precedente variante del metodo per 'ottenere il risultato Return GetElapsed(New Date(Year, 1, 1)) End Function 'Come le due sopra, ma il parametro di tipo stringa 'e indica la data Private Function GetElapsed(ByVal D As String) As Single Return GetElapsed(Date.Parse(D)) End Function Sub Main() 'GetElapsed viene 'diversi, ma sono Dim El1 As Single Dim El2 As Single Dim El3 As Single Console.ReadKey() End Sub Module chiamata con tre tipi di parametri tutti leciti = GetElapsed(New Date(1987, 12, 4)) = GetElapsed(1879) = GetElapsed("12/12/1991")
Come avr ete notato, nell'esempio pr ecedente non ho usato la keyw or d Over loads: anche se le r egole dicono che i membr i in over load vanno segnati, non sempr e necessar io far lo. Anzi, molte volte si evita di dichiar ar e esplicitamente i membr i di cui esistono var ianti come Over loads. Ci sono var ie r agioni per questa pr atica: l'over load scontato se i metodi pr esentano lo stesso nome, e il compilator e r iesce comunque a distinguer e tutto nitidamente; inoltr e, capita spesso di definir e var ianti e per r ender e il codice pi leggibile e meno pesante (anche se i sor genti in VB tendono ad esser e un poco pr olissi), si omette Over loads. Tuttavia, esistono casi in cui assolutamente necessar io usar e la keyw or d; eccone un esempio: 01. Module Module1 02. Class Person 03. Protected _FirstName, _LastName As String 04. Private ReadOnly _BirthDay As Date 05. 06. Public Property FirstName() As String 07. Get 08. Return _FirstName 09. End Get Set(ByVal Value As String) 10. 11. If Value <> "" Then 12. _FirstName = Value 13. End If 14. End Set 15. End Property 16. 17. Public Overridable Property LastName() As String 18. Get 19. Return _LastName 20. End Get 21. Set(ByVal Value As String) If Value <> "" Then 22. _LastName = Value 23. 24. End If End Set 25. End Property 26. 27. Public ReadOnly Property BirthDay() As Date 28. Get 29. Return _BirthDay 30. End Get 31. End Property 32. 33. Public Overridable ReadOnly Property CompleteName() As String 34. Get 35. Return _FirstName & " " & _LastName 36. End Get 37. End Property 38. 39.
40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91. 92. 93. End
'ToString una funzione definita nella classe 'System.Object e poich ogni cosa in .NET 'deriva da questa classe, &egrae; sempre possibile 'ridefinire tramite polimorfismo il metodo ToString. 'In questo caso ne scriveremo non una, ma due versioni, 'quindi deve essere dichiarato sia Overrides, perch 'sovrascrive System.Object.ToString, sia Overloads, 'perch una versione alternativa di 'quella che andremo a scrivere tra poco Public Overloads Overrides Function ToString() As String Return CompleteName End Function 'Questa versione accetta un parametro stringa che assume 'la funzione di stringa di formato: il metodo restituir 'la frase immessa, sostituendo {F} con FirstName e {L} con 'LastName. In questa versione sufficiente 'Overloads, dato che non esiste un metodo ToString che 'accetti un parametro stringa in System.Object e perci 'non lo potremmo modificare Public Overloads Function ToString(ByVal FormatString As String) _ As String Dim Temp As String = FormatString 'Sostituisce {F} con FirstName Temp = Temp.Replace("{F}", _FirstName) 'Sostituisce {L} con LastName Temp = Temp.Replace("{L}", _LastName) Return Temp End Function Sub New(ByVal FirstName As String, ByVal LastName As String, _ ByVal BirthDay As Date) Me.FirstName = FirstName Me.LastName = LastName Me._BirthDay = BirthDay End Sub End Class Sub Main() Dim P As New Person("Mario", "Rossi", Date.Parse("17/07/67")) Console.WriteLine(P.ToString) '> Mario Rossi 'vbCrLf una costante che rappresenta il carattere '"a capo" Console.WriteLine(P.ToString("Nome: {F}" & vbCrLf & "Cognome: {L}")) '> Nome: Mario '> Cognome: Rossi Console.ReadKey() End Sub Module
Come mostr ato dall'esempio, quando il membr o di cui si vogliono definir e var ianti sottoposto anche a polimor fismo, necessar io specificar e la keyw or d Over loads, poich, in caso contr ar io, il compilator e r intr accer ebbe quello stesso membr o come diver so e, non potendo esister e membr i con lo stesso nome, pr odur r ebbe un er r or e.
Catch Ex As InvalidCastException 17. 'Se, invece, il programma arriva in questo blocco, 18. 'vuol dire che abbiamo "preso" (catch) un'eccezione 19. 'di tipo InvalidCastException, che stata 20. '"lanciata" dal codice precedente. Tutti i dati 21. 'relativi a quella eccezione sono ora conservati 22. 'nella variabile Ex. 23. 'Possiamo accedervi oppure no, come in questo caso, 24. 'ma sono in ogni caso informazioni utili, come 25. 'vedremo fra poco 26. Console.WriteLine("I dati inseriti non sono numeri!") 27. 'I dati non sono coerenti, quindi ok = False 28. ok = False 29. End Try 30. 'Richiede gli stessi dati fino a che non si tratta 31. 'di due numeri 32. Loop Until ok 33. 34. 'Esegue il controllo su b e poi effettua la divisione 35. If b <> 0 Then 36. Console.WriteLine("{0} / {1} = {2}", a, b, a / b) 37. Else 38. Console.WriteLine("Divisione impossibile!") 39. End If 40. Console.ReadKey() 41. End Sub 42. 43. End Module Or a potr este anche chieder vi "Come faccio a saper e quale classe r appr esenta quale eccezione?". Beh, in gener e, si mette un blocco Tr y dopo aver notato il ver ificar si dell'er r or e e quindi dopo aver letto il messaggio di er r or e che contiene anche il nome dell'eccezione. In alter nativa si pu specificar e come tipo semplicemente Ex ception, ed in quel caso ver r anno cattur ate tutte le eccezioni gener ate, di qualsiasi tipo. Ecco una var iante dell'esempio pr ecedente: 01. Module Module1 02. Sub Main() 03. 'a e b sono interi short, ossia possono assumere 04. 'valori da -32768 a +32767 05. Dim a, b As Int16 06. Dim ok As Boolean = False 07. 08. Do 09. Try 10. Console.WriteLine("Inserire due numeri non nulli: ") 11. a = Console.ReadLine 12. b = Console.ReadLine 13. ok = True 14. Catch Ex As Exception 15. 'Catturiamo una qualsiasi eccezione e stampiamo il 16. 'messaggio 17. 'ad essa relativo. Il messaggio contenuto nella 18. 'propriet Message dell'oggetto Ex. 19. Console.WriteLine(Ex.Message) 20. ok = False 21. End Try 22. Loop Until ok 23. 24. If b <> 0 Then 25. Console.WriteLine("{0} / {1} = {2}", a, b, a / b) Else 26. Console.WriteLine("Divisione impossibile!") 27. 28. End If 29. Console.ReadKey() 30. End Sub 31. 32. End Module Pr ovando ad inser ir e un numer o tr oppo gr ande o tr oppo piccolo si otter r "Over flow di un'oper azione ar itmetica."; inser endo una str inga non conver tibile in numer o si otter r "Cast non valido dalla str inga [str inga] al tipo 'Shor t'".
Questa ver sione, quindi, cattur a e gestisce ogni possibile eccezione. Ricor date che possibile usar e anche pi clausole Catch in un unico blocco Tr y, ad esempio una per ogni eccezione diver sa.
Clausola Finally
Il costr utto Tr y costituito da un blocco Tr y e da una o pi clausole Catch. Tuttavia, opzionalmente, possibile specificar e anche un'ulter ior e clausola, che deve esser e posta dopo tutti i Catch: Finally. Finally d inizio ad un altr o blocco di codice che viene s empre eseguito, sia che si gener i un'eccezione, sia che non se ne gener i alcuna. Il codice ivi contenuto viene eseguito comunque dopo il tr y e il catch. Ad esempio, assumiamo di aver e questo blocco di codice, con alcune istr uzioni di cui non ci inter essa la natur a: mar chiamo le istr uzioni con delle letter e e ipotizziamo che la D gener i un'eccezione: 01. 02. 03. 04. 05. 06. 07. 08. 09. 10. 11. 12. 13. 14. Try A B C D E F Catch Ex As Exception G H Finally I L End Try
Le istr uzioni eseguite sar anno: 01. 02. 03. 04. 05. 06. 07. 08. 09. A B C 'Eccezione: salta nel blocco Catch G H 'Alla fine esegue comunque il Finally I L
17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. End
Return "La password inserita sbagliata!" End Get End Property 'Modifica il link di aiuto Public Overrides Property HelpLink() As String Get Return "http://totem.altervista.org" End Get Set(ByVal Value As String) MyBase.HelpLink = value End Set End Property 'Il resto dei membri di Exception sono molto importanti 'e vengono inizializzati con dati prelevati tramite 'Reflection (ultimo argomento di questa sezione), perci ' conveniente non modificare altro. Potete 'semmai aggiungere qualche membro End Class Sub Main() Dim Pass As String = "b7dha90" Dim NewPass As String Console.WriteLine("Inserire la password:") NewPass = Console.ReadLine If NewPass <> Pass Then 'Lancia l'eccezione usando la keyword Throw Throw New IncorrectPasswordException End If Catch IPE As IncorrectPasswordException 'Visualizza il messaggio Console.WriteLine(IPE.Message) 'E il link d'aiuto Console.WriteLine("Help: " & IPE.HelpLink) End Try Console.ReadKey() End Sub Module Try
Come si visto nell'esempio, lanciar e un'eccezione molto semplice: basta scr iver e Thr ow , seguito da un oggetto Ex ception valido. In questo caso abbiamo cr eato l'oggetto Incor r ectPassw or dEx ception nello stessa linea di codice in cui l'abbiamo lanciato.
A33. Distruttori
Avver tenza: questo un capitolo molto tecnico. For se vi sar pi utile in futur o.
Gli oggetti COM (Component Object Model) utilizzati dal vecchio VB6 possedevano una car atter istica peculiar e che per metteva di deter minar e quando non vi fosse pi bisogno di lor o e la memor ia associata potesse esser e r ilasciata: er ano dotati di un r efer ence counter , ossia di un "contator e di r ifer imenti". Ogni volta che una var iabile veniva impostata su un oggetto COM, il contator e veniva aumentato di 1, mentr e quando quella var iabile veniva distr utta o se ne cambiava il valor e, il contator e scendeva di un'unit. Quando tale valor e r aggiungeva lo zer o, gli oggetti venivano distr utti. Er ano pr esenti alcuni pr oblemi di cor r uzione della memor ia, per : ad esempio se due oggetti si puntavano vicendevolmente ma non er ano utilizzati dall'applicazione, essi non venivano distr utti (r ifer imento cir colar e). Il meccanismo di gestione della memor ia con il .NET Fr amew or k molto diver so, e or a vediamo come oper a.
Finalize
Il metodo Finalize di un oggetto speciale, poich viene r ichiamato dal Gar bage Collector "in per sona" dur ante la r accolta della memor ia. Come gi detto, non possibile saper e quando un oggetto logicamente distr utto lo sar anche fisicamente, quindi Finalize potr ebbe esser e eseguito anche diver si secondi, o minuti, o addir ittur a or e, dopo che sia stato annullato ogni r ifer imento all'oggetto. Come seconda clausola impor tante, necessar io non acceder e m ai ad oggetti ester ni in una pr ocedur a Finalize: dato che il GC (acr onimo di gar bage collector ) pu distr ugger e gli oggetti in qualsiasi or dine, non si pu esser e sicur i che l'oggetto a cui si sta facendo r ifer imento esista ancor a o sia gi stato distr utto. Questo vale anche per oggetti singleton come Console o Application, o addir ittur a per i tipi Str ing, Byte, Date e tutti gli altr i (dato che, essendo anch'essi istanze di System.Type, che definisce le car atter istiche di ciascun tipo,
sono soggetti alla GC alla fine del pr ogr amma). Per saper e se il pr ocesso di distr uzione stato avviato dalla chiusur a del pr ogr amma si pu r ichiamar e una semplice pr opr iet booleana, Envir onment.HasShutdow nStar ted. Per esemplificar e i concetti, in questo par agr afo far uso dell'oggetto singleton GC, che r appr esenta il Gar bage Collector , per mettendo di avviar e for zatamente la r accolta della memor ia e altr e cose: questo no n deve mai esser e fatto in un'applicazione r eale, poich potr ebbe compr ometter ne le pr estazioni. 01. Module Module1 02. Class Oggetto 03. Sub New() Console.WriteLine("Un oggetto sta per essere creato.") 04. End Sub 05. 06. 'La procedura Finalize definita in System.Object, quindi, 07. 'per ridefinirla dobbiamo usare il polimorfismo. Inoltre 08. 'deve essere dichiarata Protected, poich non pu 09. 'essere richiamata da altro ente se non dal GC e allo 10. 'stesso tempo ereditabile 11. Protected Overrides Sub Finalize() 12. Console.WriteLine("Un oggetto sta per essere distrutto.") 13. 'Blocca il programma per 4 secondi circa, consentendoci 'di vedere cosa viene scritto a schermo 14. 15. System.Threading.Thread.CurrentThread.Sleep(4000) 16. End Sub 17. End Class 18. Sub Main() 19. Dim O As New Oggetto 20. Console.WriteLine("Oggetto = Nothing") 21. Console.WriteLine("L'applicazione sta per terminare.") 22. End Sub 23. 24. End Module L'output sar : 1. 2. 3. 4. Un oggetto sta per essere creato. Oggetto = Nothing L'applicazione sta per terminare. Un oggetto sta per essere distrutto.
Come si vede, l'oggetto viene distr utto do po il ter mine dell'applicazione (siamo for tunati che Console ancor a "in vita" pr ima della distr uzione): questo significa che c'er a abbastanza spazio disponibile da non avviar e la GC, che quindi stata r imandata fino alla fine del pr ogr amma. Ripr oviamo invece in questo modo: 01. Sub Main() 02. Dim O As New Oggetto 03. O = Nothing 04. Console.WriteLine("Oggetto = Nothing") 05. 06. 'NON PROVATECI A CASA! 07. 'Forza una garbage collection 08. GC.Collect() 09. 'Attende che tutti i metodi Finalize siano stati eseguiti 10. GC.WaitForPendingFinalizers() 11. 12. Console.WriteLine("L'applicazione sta per terminare.") 13. Console.ReadKey() 14. End Sub Ci che appar ir sullo scher mo : 1. 2. 3. 4. Un oggetto sta per essere creato. Oggetto = Nothing Un oggetto sta per essere distrutto. L'applicazione sta per terminare.
Si vede che l'or dine delle ultime due azioni stato cambiato a causa delle GC avviata anzi tempo pr ima del ter mine del pr ogr amma.
Anche se ci siamo diver titi con Finalize, questo metodo deve esser e definito solo se str ettamente necessar io, per alcune r agioni. La pr ima che il GC impiega non uno, ma due cicli per finalizzar e un oggetto in cui stata definita Finalize dal pr ogr ammator e. Il motivo consiste nella possibilit che venga usata la cosiddetta r esur r ezio ne dell'o g g etto : in questa tecnica, ad una var iabile globale viene assegnato il r ifer imento alla classe stessa usando Me; dato che in questo modo c' ancor a un r ifer imento valido all'oggetto, questo non deve venir e distr utto. Tuttavia, per r ilevar e questo fenomeno, il GC impiega due cicli e si r ischia di occupar e memor ia inutile. Inoltr e, sempr e per questa causa, si impiega pi tempo macchina che potr ebbe esser e speso in altr o modo.
Dispose
Si potr ebbe definir e Dispose come un Finalize manuale: esso per metto di r ilasciar e qualsiasi r isor sa che non sia la memor ia (ossia connessioni a database, files, immagini, pennelli, oggetti di sistema, ecceter a...) manualmente, appena pr ima di impostar e il r ifer imento a Nothing. In questo modo non si dovr aspettar e una successiva GC affinch sia r ilasciato tutto cor r ettamente. Dispose non un metodo definito da tutti gli oggetti, e per ci ogni classe che intende definir lo deve implementar e l'inter faccia IDisposable (per ulter ior i infor mazioni sulle inter facce, veder e capitolo 36): per or a pr endete per buono il codice che for nisco, vedr emo in seguito pi appr ofonditamente l'agor mento delle inter facce. 01. Class Oggetto 02. 'Implementa l'interfaccia IDisposable 03. Implements IDisposable 04. 'File da scrivere: 05. Dim W As IO.StreamWriter 06. 07. Sub New() 08. 'Inizializza l'oggetto 09. W = New IO.StreamWriter("C:\test.txt") 10. End Sub 11. 12. Public Sub Dispose() Implements IDisposable.Dispose 13. 'Chiude il file 14. W.Close() 15. End Sub 16. End Class Invocando il metodo Dispose di Oggetto, possibile chiuder e il file ed evitar e che venga lasciato aper to. Il Vb.NET for nisce un costr utto, valido per tutti gli oggetti che implementano l'inter faccia IDisposable, che si assicur a di r ichiamar e il metodo Dispose e impostar e il r ifer imento a Nothing automaticamente dopo l'uso. La sintassi questa: 1. 2. 3. 4. 5. 6. 7. 8. Using [Oggetto] 'Codice da eseguire End Using 'Che corrisponde a scrivere: 'Codice da eseguire [Oggetto].Dispose() [Oggetto] = Nothing
Per convenzione, se una classe implementa un'inter faccia IDisposable e contiene altr e classi nidificate o altr i oggetti, il suo metodo Dispose deve r ichiamar e il Dispose di tutti gli oggetti inter ni, almeno per quelli che ce l'hanno. Altr a convenzione che se viene r ichiamata Dispose da un oggetto gi distr utto logicamente, deve gener ar si l'eccezione ObjectDisposedEx ception.
meccanismi: N Dispose, n Finalize: la classe impiega solo la memor ia come unica r isor sa o, se ne impiegate altr e, le r ilascia pr ima di ter minar e le pr opr ie oper azioni. Solo Dispose: la classe impiega r isor se facendo r ifer imento ad altr i oggetti .NET e si vuole for nir e al chiamante la possibilit di r ilasciar e tali r isor se il pr ima possibile. Dispose e Finalize: la classe impiega dir ettamente una r isor sa, ad esempio invocando un metodo di una libr er ia unmanaged, che r ichiede un r ilascio esplicito; in pi si vuole for nir e al client la possibilit di deallocar e manualmente gli oggetti. Solo Finalize: si deve eseguir e un cer to codice pr ima della distr uzione. A questo punto ci si deve pr eoccupar e di due pr oblemi che possono pr esentar si: Finalize pu esser e chiamato anche dopo che l'oggetto stato distr utto e le sue r isor se deallocate con Dispose, quindi potr ebbe tantar e di distr ugger e un oggetto inesistente; il codice che viene eseguito in Finalize potr ebbe far r ifer imento a oggetti inesistenti. Le convenzioni per mettono di aggir ar e il pr oblema facendo uso di ver sioni in over load di Dispose e di una var iabile pr ivata a livello di classe. La var iabile booleana Disposed ha il compito di memor izzar e se l'oggetto stato distr utto: in questo modo eviter emo di r ipeter e il codice in Finalize. Il metodo in over load di Dispose accetta un par ametr o di tipo booleano, di solito chiamato Disposing, che indica se l'oggetto sta subendo un pr ocesso di distr uzione manuale o di finalizzazione: pr ocedendo con questo metodo si cer ti di r ichiamar e eventuali altr i oggetti nel caso non ci sia finalizzazione. Il codice seguente implementa una semplicissima classe FileWr iter e, tr amite messaggi a scher mo, visualizza quando e come l'oggetto viene r imosso dalla memor ia: 001. Module Module1 002. Class FileWriter 003. Implements IDisposable 004. 005. Private Writer As IO.StreamWriter 006. 'Indica se l'oggetto gi stato distrutto con Dispose 007. Private Disposed As Boolean 008. 'Indica se il file aperto 009. Private Opened As Boolean 010. 011. Sub New() 012. Disposed = False 013. Opened = False 014. Console.WriteLine("FileWriter sta per essere creato.") 015. 'Questa procedura comunica al GC di non richiamare pi 016. 'il metodo Finalize per questo oggetto. Scriviamo ci 017. 'perch se file non viene esplicitamente aperto con 018. 'Open non c' alcun bisogno di chiuderlo 019. GC.SuppressFinalize(Me) 020. End Sub 021. 022. 'Apre il file 023. Public Sub Open(ByVal FileName As String) Writer = New IO.StreamWriter(FileName) 024. Opened = True 025. 026. Console.WriteLine("FileWriter sta per essere aperto.") 'Registra l'oggetto per eseguire Finalize: ora il file 027. ' aperto e pu quindi essere chiuso 028. GC.ReRegisterForFinalize(Me) 029. End Sub 030. 031. 'Scrive del testo nel file 032. Public Sub Write(ByVal Text As String) 033. If Opened Then 034. Writer.Write(Text) 035. End If 036. End Sub 037. 038. 'Una procedura analoga a Open aiuta a impostare meglio 039. 'l'oggetto e non fa altro che richiamare Dispose: 040. 'pi una questione di completezza 041. 042.
043. 044. 045. 046. 047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. End L'output:
Public Sub Close() Dispose() End Sub 'Questa versione in overload perch l'altra viene 'chiamata solo dall'utente ( Public), mentre questa 'implementa tutto il codice che necessario eseguire 'per rilasciare le risorse. 'Il parametro Disposing indica se l'oggetto sta per 'essere distrutto, quindi manualmente, o finalizzato, 'quindi nel processo di GC: nel secondo caso altri oggetti 'che questa classe utilizza potrebbero non esistere pi, 'perci si deve controllare se possibile 'invocarli correttamente Protected Overridable Overloads Sub Dispose(ByVal Disposing _ As Boolean) 'Esegue il codice solo se l'oggetto esiste ancora If Disposed Then 'Se distrutto, esce dalla procedura Exit Sub End If If Disposing Then 'Qui possiamo chiamare altri oggetti con la 'sicurezza che esistano ancora Console.WriteLine("FileWriter sta per essere distrutto.") Else Console.WriteLine("FileWriter sta per essere finalizzato.") End If 'Chiude il file Writer.Close() Disposed = True Opened = False End Sub Public Overloads Sub Dispose() Implements IDisposable.Dispose 'L'oggetto stato distrutto Dispose(True) 'Quindi non deve pi essere finalizzato GC.SuppressFinalize(Me) End Sub Protected Overrides Sub Finalize() 'Processo di finalizzazione: Dispose(False) End Sub End Class Sub Main() Dim F As New FileWriter 'Questo blocco mostra l'esecuzione di Dispose F.Open("C:\test.txt") F.Write("Ciao") F.Close() 'Questo mostra l'esecuzione di Finalize F = New FileWriter F.Open("C:\test2.txt") F = Nothing GC.Collect() GC.WaitForPendingFinalizers() Console.ReadKey() End Sub Module
3. 4. 5. 6.
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.
viene automaticamente cr eato all'atto stesso della dichiar azione, anche se noi non r iusciamo a veder lo. Questo costr uttor e accetta sempr e e solo un par ametr o: un oggetto di tipo indeter minato r estituito da uno speciale oper ator e, Addr essOf. Questo un oper ator e unar io che accetta come oper ando il metodo di cui ottener e l'"indir izzo": 1. AddressOf [NomeMetodo] Ci che Addr essOf r estituisce non molto chiar o: la sua descr izione dice espr essamente che viene r estituito un oggetto delegate (il che gi abbastanza str ano di per s, dato che per cr ear e un delegate ci vuole un altr o delegate). Tuttavia, se si utilizza come par ametr o del costr uttor e un oggetto System.Delegate viene r estituito un er r or e. Ma lasciamo queste disquisizioni a chi ha tempo da per der e e pr ocediamo con le cose impor tanti. N.B.: Dalla ver sione 2008, i costr uttor i degli oggetti delegate accettano anche espr essioni lambda! Una volta dichiar ata ed inizializzata una var iabile di tipo delegate, possibile usar la esattamente come se fosse un metodo con la signatur e specificata. Ecco un esempio: 01. Module Module1 02. 'Dichiarazione di un tipo delegate Sub che accetta un parametro 03. 'di tipo stringa. Delegate Sub Display(ByVal Message As String) 04. 05. 06. 'Una procedura dimostrativa 07. Sub Write1(ByVal S As String) Console.WriteLine("1: " & S) 08. 09. End Sub 10. 11. 'Un'altra procedura dimostrativa 12. Sub Write2(ByVal S As String) 13. Console.WriteLine("2: " & S) 14. End Sub 15. 16. Sub Main() 17. 'Variabile D di tipo Display, ossia il nuovo tipo 18. 'delegate appena definito all'inizio del modulo Dim D As Display 19. 20. 21. 'Inizializa D con un nuovo oggetto delegate contenente 'un riferimento al metodo Console.WriteLine 22. D = New Display(AddressOf Console.WriteLine) 23. 24. 'Invoca il metodo referenziato da D: in questo caso 25. 'equivarrebbe a scrivere Console.WriteLine("Ciao") 26. D("Ciao") 27. 28. 'Reinizializza D, assegnandogli l'indirizzo di Write1 29. D = New Display(AddressOf Write1) 30. ' come chiamare Write1("Ciao") 31. D("Ciao") 32. 33. 'Modo alternativo per inizializzare un delegate: si omette 34. 'New e si usa solo AddressOf. Questo genera una conversione 35. 'implicita che d errore di cast nel caso in cui Write1 36. 'non sia compatibile con la signature del delegate 37. D = AddressOf Write2 38. D("Ciao") 39. 40. 'Notare che D pu contenere metodi di istanza 41. '(come Console.WriteLine) e metodi statici (come Write1 42. 'e Write2) 43. 44. Console.ReadKey() 45. End Sub 46. 47. End Module La signatur e di un delegate no n pu contener e par ametr i indefiniti (Par amAr r ay) od opzionali (Optional), tuttavia i metodi memor izzati in un oggetto di tipo delegate possono aver e par ametr i di questo tipo. Eccone un esempio: 001. Module Module1 002.
003. 004. 005. 006. 007. 008. 009. 010. 011. 012. 013. 014. 015. 016. 017. 018. 019. 020. 021. 022. 023. 024. 025. 026. 027. 028. 029. 030. 031. 032. 033. 034. 035. 036. 037. 038. 039. 040. 041. 042. 043. 044. 045. 046. 047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074.
'Tipo delegate che pu contenere riferimenti a funzioni Single 'che accettino un parametro di tipo array di Single Delegate Function ProcessData(ByVal Data() As Single) As Single 'Tipo delegate che pu contenere riferimenti a procedure 'che accettino due parametri, un array di Single e un Boolean Delegate Sub PrintData(ByVal Data() As Single, ByVal ReverseOrder As Boolean) 'Funzione che calcola la media di alcuni valori. Notare che 'l'unico parametro indefinito, in quanto 'dichiarato come ParamArray Function CalculateAverage(ByVal ParamArray Data() As Single) As Single Dim Total As Single = 0 For I As Int32 = 0 To Data.Length - 1 Total += Data(I) Next Return (Total / Data.Length) End Function 'Funzione che calcola la varianza di alcuni valori. Notare che 'anche in questo caso il parametro indefinito Function CalculateVariance(ByVal ParamArray Data() As Single) As Single Dim Average As Single = CalculateAverage(Data) Dim Result As Single = 0 For I As Int32 = 0 To Data.Length - 1 Result += (Data(I) - Average) ^ 2 Next Return (Result / Data.Length) End Function 'Procedura che stampa i valori di un array in ordine normale 'o inverso. Notare che il secondo parametro opzionale Sub PrintNormal(ByVal Data() As Single, _ Optional ByVal ReverseOrder As Boolean = False) If ReverseOrder Then For I As Int32 = Data.Length - 1 To 0 Step -1 Console.WriteLine(Data(I)) Next Else For I As Int32 = 0 To Data.Length - 1 Console.WriteLine(Data(I)) Next End If End Sub 'Procedura che stampa i valori di un array nella forma: '"I+1) Data(I)" 'Notare che anche in questo caso il secondo parametro ' opzionale Sub PrintIndexed(ByVal Data() As Single, _ Optional ByVal ReverseOrder As Boolean = False) If ReverseOrder Then For I As Int32 = Data.Length - 1 To 0 Step -1 Console.WriteLine("{0}) {1}", Data.Length - I, Data(I)) Next Else For I As Int32 = 0 To Data.Length - 1 Console.WriteLine("{0}) {1}", (I + 1), Data(I)) Next End If End Sub Sub Main() Dim Process As ProcessData Dim Print As PrintData Dim Data() As Single Dim Len As Int32 Dim Cmd As Char
Console.WriteLine("Quanti valori inserire?") 075. Len = Console.ReadLine 076. 077. ReDim Data(Len - 1) 078. For I As Int32 = 1 To Len 079. Console.Write("Inserire il valore " & I & ": ") 080. Data(I - 1) = Console.ReadLine 081. Next 082. 083. Console.Clear() 084. 085. Console.WriteLine("Scegliere l'operazione da eseguire: ") 086. Console.WriteLine("m - Calcola la media dei valori;") 087. Console.WriteLine("v - Calcola la varianza dei valori;") 088. Cmd = Console.ReadKey().KeyChar 089. Select Case Cmd 090. Case "m" 091. Process = New ProcessData(AddressOf CalculateAverage) 092. Case "v" 093. Process = New ProcessData(AddressOf CalculateVariance) 094. Case Else 095. Console.WriteLine("Comando non valido!") 096. Exit Sub End Select 097. Console.WriteLine() 098. Console.WriteLine("Scegliere il metodo di stampa: ") 099. Console.WriteLine("s - Stampa i valori;") 100. Console.WriteLine("i - Stampa i valori con il numero ordinale a fianco.") 101. Cmd = Console.ReadKey().KeyChar 102. Select Case Cmd 103. Case "s" 104. Print = New PrintData(AddressOf PrintNormal) 105. Case "i" 106. Print = New PrintData(AddressOf PrintIndexed) 107. Case Else 108. Console.WriteLine("Comando non valido!") 109. Exit Sub 110. End Select 111. 112. Console.Clear() 113. 114. Console.WriteLine("Valori:") 115. 'Eccoci arrivati al punto. Come detto prima, i delegate 116. 'non possono definire una signature che comprenda parametri 117. 'opzionali o indefiniti, ma si 118. 'pu aggirare questa limitazione semplicemente dichiarando 119. 'un array di valori al posto del ParamArray (in quanto si 120. 'tratta comunque di due vettori) e lo stesso parametro 121. 'non opzionale al posto del parametro opzionale. 122. 'L'inconveniente, in questo ultimo caso, che il 123. 'parametro, pur essendo opzionale va sempre specificato 124. 'quando il metodo viene richiamato attraverso un oggetto 125. 'delegate. Questo escamotage permette di aumentare la 126. 'portata dei delegate, includendo anche metodi che 127. 'possono essere stati scritti tempo prima in un'altra 128. 'parte inaccessibile del codice: cos 129. 'non necessario riscriverli! 130. Print(Data, False) 131. Console.WriteLine("Risultato:") 132. Console.WriteLine(Process(Data)) 133. 134. Console.ReadKey() 135. End Sub 136. 137. 138. End Module
stesso metodo per eseguir e pi compiti differ enti. Dato che una var iabile delegate contiene un r ifr iento ad un metodo qualsiasi, semplicemente cambiando questo r ifer imento possiamo eseguir e codici diver si r ichiamando la stessa var iabile. E' come se potessimo "innestar e" del codice sempr e diver so su un substr ato costante. Ecco un esempio piccolo, ma significativo: 01. Module Module2 02. 'Nome del file da cercare 03. Dim File As String 04. 'Questo delegate referenzia una funzione che accetta un 05. 'parametro stringa e restituisce un valore booleano 06. 07. Delegate Function IsMyFile(ByVal FileName As String) As Boolean 08. 'Funzione 1, stampa il contenuto del file a schermo 09. Function PrintFile(ByVal FileName As String) As Boolean 10. 11. 'Io.Path.GetFileName(F) restituisce solo il nome del 12. 'singolo file F, togliendo il percorso delle cartelle 13. If IO.Path.GetFileName(FileName) = File Then 'IO.File.ReadAllText(F) restituisce il testo contenuto 14. 15. 'nel file F in una sola operazione 16. Console.WriteLine(IO.File.ReadAllText(FileName)) 17. Return True 18. End If Return False 19. End Function 20. 21. 'Funzione 2, copia il file sul desktop 22. Function CopyFile(ByVal FileName As String) As Boolean 23. If IO.Path.GetFileName(FileName) = File Then 24. 'IO.File.Copy(S, D) copia il file S nel file D: 25. 'se D non esiste viene creato, se esiste viene 26. 'sovrascritto 27. IO.File.Copy(FileName, _ 28. My.Computer.FileSystem.SpecialDirectories.Desktop & _ 29. "\" & File) 30. Return True 31. End If 32. Return False 33. End Function 34. 35. 'Procedura ricorsiva che cerca il file 36. Function SearchFile(ByVal Dir As String, ByVal IsOK As IsMyFile) _ 37. As Boolean 38. 'Ottiene tutte le sottodirectory 39. Dim Dirs() As String = IO.Directory.GetDirectories(Dir) 40. 'Ottiene tutti i files 41. Dim Files() As String = IO.Directory.GetFiles(Dir) 42. 43. 'Analizza ogni file per vedere se quello cercato 44. For Each F As String In Files 45. ' il file cercato, basta cercare 46. If IsOK(F) Then 47. 'Termina la funzione e restituisce Vero, cosicch 48. 'anche nel for sulle cartelle si termini 49. 'la ricerca 50. Return True 51. End If 52. Next 53. 54. 'Analizza tutte le sottocartelle 55. For Each D As String In Dirs 56. If SearchFile(D, IsOK) Then 57. 'Termina ricorsivamente la ricerca 58. Return True 59. End If 60. Next 61. End Function 62. 63. Sub Main() 64. Dim Dir As String 65. 66.
67. Console.WriteLine("Inserire il nome file da cercare:") 68. File = Console.ReadLine 69. 70. Console.WriteLine("Inserire la cartella in cui cercare:") 71. Dir = Console.ReadLine 72. 73. 'Cerca il file e lo scrive a schermo 74. SearchFile(Dir, AddressOf PrintFile) 75. 76. 'Cerca il file e lo copia sul desktop 77. SearchFile(Dir, AddressOf CopyFile) 78. 79. Console.ReadKey() 80. End Sub 81. End Module Nel sor gente si vede che si usano pochissime r ighe per far compier e due oper azioni molto differ enti alla stessa pr ocedur a. In altr e condizioni, un aspir ante pr ogr ammator e che non conoscesse i delegate avr ebbe scr itto due pr ocedur e inter e, spr ecando pi spazio, e condannandosi, inoltr e, a r iscr iver e la stessa cosa per ogni futur a var iante.
funzione memor izzata. Ecco or a un altr o esempio molto ar ticolato sui delegate multicast: 001. 002. 003. 004. 005. 006. 007. 008. 009. 010. 011. 012. 013. 014. 015. 016. 017. 018. 019. 020. 021. 022. 023. 024. 025. 026. 027. 028. 029. 030. 031. 032. 033. 034. 035. 036. 037. 038. 039. 040. 041. 042. 043. 044. 045. 046. 047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 'Questo esempio si basa completamente sulla manipolazione 'di file e cartelle, argomento non ancora affrontato. Se volete, 'potete dare uno sguardo ai capitoli relativi nelle parti 'successive della guida, oppure potete anche limitarvi a leggere 'i commenti, che spiegano tutto ci che accade. Module Module1 'In questo esempio eseguiremo delle operazioni su file con i delegate. 'Nel men sar possibile scegliere quali operazioni 'eseguire (una o tutte insieme) e sotto quali condizioni modificare 'un file. 'Il delegate FileFilter rappresenta una funzione che restituisce 'True se la condizione soddisfatta. Le condizioni 'sono racchiuse in un delegate multicast che contiene pi 'funzioni di questo tipo Delegate Function FileFilter(ByVal FileName As String) As Boolean 'Il prossimo delegate rappresenta un'operazione su un file Delegate Sub MassFileOperation(ByVal FileName As String) 'AskForData un delegate del tipo pi semplice. 'Servir per reperire le informazioni necessarie ad 'eseguire le operazioni (ad esempio, se si sceglie di copiare 'tutti i file di una cartella, si dovr anche scegliere 'dove copiare questi file). Delegate Sub AskForData() 'Queste variabili globali rappresentano le informazioni necesarie 'per lo svolgimento delle operazioni o la verifica delle condizioni. 'Stringa di formato per rinominare i file Dim RenameFormat As String 'Posizione di un file nella cartella Dim FileIndex As Int32 'Directory in cui copiare i file Dim CopyDirectory As String 'File in cui scrivere. Il tipo StreamWriter permette di scrivere 'facilmente stringhe su un file usando WriteLine come in Console Dim LogFile As IO.StreamWriter 'Limitazioni sulla data di creazione del file Dim CreationDateFrom, CreationDateTo As Date 'Limitazioni sulla data di ultimo accesso al file Dim LastAccessDateFrom, LastAccessDateTo As Date 'Limitazioni sulla dimensione Dim SizeFrom, SizeTo As Int64 'Rinomina un file Sub Rename(ByVal Path As String) 'Ne prende il nome semplice, senza estensione Dim Name As String = IO.Path.GetFileNameWithoutExtension(Path) 'Apre un oggetto contenente le informazioni sul file 'di percorso Path Dim Info As New IO.FileInfo(Path) 'Formatta il nome secondo la stringa di formato RenameFormat Name = String.Format(RenameFormat, _ Name, FileIndex, Info.Length, Info.LastAccessTime, Info.CreationTime) 'E aggiunge ancora l'estensione al nome modificato Name &= IO.Path.GetExtension(Path) 'Copia il vecchio file nella stessa cartella, ma con il nuovo nome IO.File.Copy(Path, IO.Path.GetDirectoryName(Path) & "\" & Name) 'Elimina il vecchio file IO.File.Delete(Path) 'Aumenta l'indice di uno FileIndex += 1 End Sub 'Funzione che richiede i dati necessari per far funzionare 'il metodo Rename Sub InputRenameFormat()
070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121. 122. 123. 124. 125. 126. 127. 128. 129. 130. 131. 132. 133. 134. 135. 136. 137. 138. 139.
Console.WriteLine("Immettere una stringa di formato valida per rinominare i file.") Console.WriteLine("I parametri sono:") Console.WriteLine("0 = Nome originale del file;") Console.WriteLine("1 = Posizione del file nella cartella, in base 0;") Console.WriteLine("2 = Dimensione del file, in bytes;") Console.WriteLine("3 = Data dell'ultimo accesso;") Console.WriteLine("4 = Data di creazione.") RenameFormat = Console.ReadLine End Sub 'Elimina un file di percorso Path Sub Delete(ByVal Path As String) IO.File.Delete(Path) End Sub 'Copia il file da Path alla nuova cartella Sub Copy(ByVal Path As String) IO.File.Copy(Path, CopyDirectory & "\" & IO.Path.GetFileName(Path)) End Sub 'Richiede una cartella valida in cui copiare i file. Se non esiste, la crea Sub InputCopyDirectory() Console.WriteLine("Inserire una cartella valida in cui copiare i file:") CopyDirectory = Console.ReadLine If Not IO.Directory.Exists(CopyDirectory) Then IO.Directory.CreateDirectory(CopyDirectory) End If End Sub 'Scrive il nome del file sul file aperto Sub Archive(ByVal Path As String) LogFile.WriteLine(IO.Path.GetFileName(Path)) End Sub 'Chiede il nome di un file su cui scrivere tutte le informazioni Sub InputLogFile() Console.WriteLine("Inserire il percorso del file su cui scrivere:") LogFile = New IO.StreamWriter(Console.ReadLine) End Sub 'Verifica che la data di creazione del file cada tra i limiti fissati Function IsCreationDateValid(ByVal Path As String) As Boolean Dim Info As New IO.FileInfo(Path) Return (Info.CreationTime >= CreationDateFrom) And (Info.CreationTime >= CreationDateTo) End Function 'Richiede di immettere una limitazione temporale per considerare 'solo certi file Sub InputCreationDates() Console.WriteLine("Verranno considerati solo i file con data di creazione:") Console.Write("Da: ") CreationDateFrom = Date.Parse(Console.ReadLine) Console.Write("A: ") CreationDateTo = Date.Parse(Console.ReadLine) End Sub 'Verifica che la data di ultimo accesso al file cada tra i limiti fissati Function IsLastAccessDateValid(ByVal Path As String) As Boolean Dim Info As New IO.FileInfo(Path) Return (Info.LastAccessTime >= LastAccessDateFrom) And (Info.LastAccessTime >= LastAccessDateTo) End Function 'Richiede di immettere una limitazione temporale per considerare 'solo certi file Sub InputLastAccessDates() Console.WriteLine("Verranno considerati solo i file con data di creazione:") Console.Write("Da: ") LastAccessDateFrom = Date.Parse(Console.ReadLine) Console.Write("A: ")
140. 141. 142. 143. 144. 145. 146. 147. 148. 149. 150. 151. 152. 153. 154. 155. 156. 157. 158. 159. 160. 161. 162. 163. 164. 165. 166. 167. 168. 169. 170. 171. 172. 173. 174. 175. 176. 177. 178. 179. 180. 181. 182. 183. 184. 185. 186. 187. 188. 189. 190. 191. 192. 193. 194. 195. 196. 197. 198. 199. 200. 201. 202. 203. 204. 205. 206. 207. 208. 209. 210. 211.
LastAccessDateTo = Date.Parse(Console.ReadLine) End Sub 'Verifica che la dimensione del file sia coerente coi limiti fissati Function IsSizeValid(ByVal Path As String) As Boolean Dim Info As New IO.FileInfo(Path) Return (Info.Length >= SizeFrom) And (Info.Length >= SizeTo) End Function 'Richiede di specificare dei limiti dimensionali per i file Sub InputSizeLimit() Console.WriteLine("Verranno considerati solo i file con dimensione compresa:") Console.Write("Tra (bytes):") SizeFrom = Console.ReadLine Console.Write("E (bytes):") SizeTo = Console.ReadLine End Sub 'Classe che rappresenta un'operazione eseguibile su file Class Operation Private _Description As String Private _Execute As MassFileOperation Private _RequireData As AskForData Private _Enabled As Boolean 'Descrizione Public Property Description() As String Get Return _Description End Get Set(ByVal value As String) _Description = value End Set End Property 'Variabile che contiene l'oggetto delegate associato 'a questa operazione, ossia un riferimento a una delle Sub 'definite poco sopra Public Property Execute() As MassFileOperation Get Return _Execute End Get Set(ByVal value As MassFileOperation) _Execute = value End Set End Property 'Variabile che contiene l'oggetto delegate che serve 'per reperire informazioni necessarie ad eseguire 'l'operazione, ossia un riferimento a una delle sub 'di Input definite poco sopra. E' Nothing quando 'non serve nessun dato ausiliario (come nel caso 'di Delete) Public Property RequireData() As AskForData Get Return _RequireData End Get Set(ByVal value As AskForData) _RequireData = value End Set End Property 'Determina se l'operazione va eseguita oppure no Public Property Enabled() As Boolean Get Return _Enabled End Get Set(ByVal value As Boolean) _Enabled = value End Set End Property
212. 213. 214. 215. 216. 217. 218. 219. 220. 221. 222. 223. 224. 225. 226. 227. 228. 229. 230. 231. 232. 233. 234. 235. 236. 237. 238. 239. 240. 241. 242. 243. 244. 245. 246. 247. 248. 249. 250. 251. 252. 253. 254. 255. 256. 257. 258. 259. 260. 261. 262. 263. 264. 265. 266. 267. 268. 269. 270. 271. 272. 273. 274. 275. 276. 277. 278. 279. 280. 281. 282. 283.
Sub New(ByVal Description As String, _ ByVal ExecuteMethod As MassFileOperation, _ ByVal RequireDataMethod As AskForData) Me.Description = Description Me.Execute = ExecuteMethod Me.RequireData = RequireDataMethod Me.Enabled = False End Sub End Class 'Classe che rappresenta una condizione a cui sottoporre 'i file nella cartella: verranno elaborati solo quelli che 'soddisfano tutte le condizioni Class Condition Private _Description As String Private _Verify As FileFilter Private _RequireData As AskForData Private _Enabled As Boolean Public Property Description() As String Get Return _Description End Get Set(ByVal value As String) _Description = value End Set End Property 'Contiene un oggetto delegate associato a una delle 'precedenti funzioni Public Property Verify() As FileFilter Get Return _Verify End Get Set(ByVal value As FileFilter) _Verify = value End Set End Property Public Property RequireData() As AskForData Get Return _RequireData End Get Set(ByVal value As AskForData) _RequireData = value End Set End Property Public Property Enabled() As Boolean Get Return _Enabled End Get Set(ByVal value As Boolean) _Enabled = value End Set End Property Sub New(ByVal Description As String, _ ByVal VerifyMethod As FileFilter, _ ByVal RequireDataMethod As AskForData) Me.Description = Description Me.Verify = VerifyMethod Me.RequireData = RequireDataMethod End Sub End Class Sub Main() 'Contiene tutte le operazioni da eseguire: sar, quindi, un 'delegate multicast Dim DoOperations As MassFileOperation 'Contiene tutte le condizioni da verificare Dim VerifyConditions As FileFilter
284. 285. 286. 287. 288. 289. 290. 291. 292. 293. 294. 295. 296. 297. 298. 299. 300. 301. 302. 303. 304. 305. 306. 307. 308. 309. 310. 311. 312. 313. 314. 315. 316. 317. 318. 319. 320. 321. 322. 323. 324. 325. 326. 327. 328. 329. 330. 331. 332. 333. 334. 335. 336. 337. 338. 339. 340. 341. 342. 343. 344. 345. 346. 347. 348. 349. 350.
'Indica la cartella di cui analizzare i file Dim Folder As String 'Hashtable di caratteri-Operation o carattri-Condition. Il 'carattere indica quale tasto necessario 'premere per attivare/disattivare l'operazione/condizione Dim Operations As New Hashtable Dim Conditions As New Hashtable Dim Cmd As Char 'Aggiunge le operazioni esistenti. La 'c' messa dopo la stringa 'indica che la costante digitata un carattere e non una 'stringa. Il sistema non riesce a distinguere tra stringhe di lunghezza 1 e caratteri, al contrario di come accade in C With Operations .Add("r"c, New Operation("Rinomina tutti i file nella cartella;", _ New MassFileOperation(AddressOf Rename), _ New AskForData(AddressOf InputRenameFormat))) .Add("c"c, New Operation("Copia tutti i file nella cartella in un'altra cartella;", _ New MassFileOperation(AddressOf Copy), _ New AskForData(AddressOf InputCopyDirectory))) .Add("a"c, New Operation("Scrive il nome di tutti i file nella cartella su un file;", _ New MassFileOperation(AddressOf Archive), _ New AskForData(AddressOf InputLogFile))) .Add("d"c, New Operation("Cancella tutti i file nella cartella;", _ New MassFileOperation(AddressOf Delete), _ Nothing)) End With 'Aggiunge le condizioni esistenti With Conditions .Add("r"c, New Condition("Seleziona i file da elaborare in base alla data di creazione;", _ New FileFilter(AddressOf IsCreationDateValid), _ New AskForData(AddressOf InputCreationDates))) .Add("l"c, New Condition("Seleziona i file da elaborare in base all'ultimo accesso;", _ New FileFilter(AddressOf IsLastAccessDateValid), _ New AskForData(AddressOf InputLastAccessDates))) .Add("s"c, New Condition("Seleziona i file da elaborare in base alla dimensione;", _ New FileFilter(AddressOf IsSizeValid), _ New AskForData(AddressOf InputSizeLimit))) End With Console.WriteLine("Modifica in massa di file ---") Console.WriteLine() Do Console.WriteLine("Immetti il percorso della cartella su cui operare:") Folder = Console.ReadLine Loop Until IO.Directory.Exists(Folder) Do Console.Clear() Console.WriteLine("Premere la lettera corrispondente per selezionare la voce.") Console.WriteLine("Premere 'e' per procedere.") Console.WriteLine() For Each Key As Char In Operations.Keys 'Disegna sullo schermo una casella di spunta, piena: ' [X] 'se l'operazione attivata, altrimenti vuota: ' [ ] Console.Write("[") If Operations(Key).Enabled = True Then Console.Write("X") Else Console.Write(" ") End If Console.Write("] ") 'Scrive quindi il carattere da premere e vi associa la descrizione
351. 352. 353. 354. 355. 356. 357. 358. 359. 360. 361. 362. 363. 364. 365. 366. 367. 368. 369. 370. 371. 372. 373. 374. 375. 376. 377. 378. 379. 380. 381. 382. 383. 384. 385. 386. 387. 388. 389. 390. 391. 392. 393. 394. 395. 396. 397. 398. 399. 400. 401. 402. 403. 404. 405. 406. 407. 408. 409. 410. 411. 412. 413. 414. 415. 416. 417. 418. 419. 420. 421. 422.
Next Cmd = Console.ReadKey().KeyChar If Operations.ContainsKey(Cmd) Then Operations(Cmd).Enabled = Not Operations(Cmd).Enabled End If Loop Until Cmd = "e"c Console.Clear() Console.WriteLine("Premere la lettera corrispondente per selezionare la voce.") Console.WriteLine("Premere 'e' per procedere.") Console.WriteLine() For Each Key As Char In Conditions.Keys Console.Write("[") If Conditions(Key).Enabled = True Then Console.Write("X") Else Console.Write(" ") End If Console.Write("] ") Console.Write(Key) Console.Write(" - ") Console.WriteLine(Conditions(Key).Description) Next Cmd = Console.ReadKey().KeyChar If Conditions.ContainsKey(Cmd) Then Conditions(Cmd).Enabled = Not Conditions(Cmd).Enabled End If Loop Until Cmd = "e"c Console.Clear() Console.WriteLine("Acquisizione informazioni") Console.WriteLine() 'Cicla su tutte le operazioni presenti nell'Hashtable. For Each Op As Operation In Operations.Values 'Se l'operazione attivata... If (Op.Enabled) Then 'Se richiede dati ausiliari, invoca il delegate memorizzato 'nella propriet RequireData. Invoke un metodo 'di istanza che invoca i metodi contenuti nel delegate. 'Si pu anche scrivere: ' Op.RequireData()() 'Dove la prima coppia di parentesi indica che la propriet 'non indicizzata e la seconda, in questo caso, specifica 'che il metodo sotteso dal delegate non richiede parametri. ' pi comprensibile la prima forma If Op.RequireData IsNot Nothing Then Op.RequireData.Invoke() End If 'Se DoOperations non contiene ancora nulla, vi inserisce Op.Execute If DoOperations Is Nothing Then DoOperations = Op.Execute Else 'Altrimenti, combina gli oggetti delegate gi memorizzati 'con il nuovo DoOperations = System.Delegate.Combine(DoOperations, Op.Execute) End If End If Next For Each C As Condition In Conditions.Values If C.Enabled Then If C.RequireData IsNot Nothing Then C.RequireData.Invoke() End If If VerifyConditions Is Nothing Then VerifyConditions = C.Verify Else Do
VerifyConditions = System.Delegate.Combine(VerifyConditions, C.Verify) 423. End If 424. End If 425. Next 426. 427. FileIndex = 0 428. For Each File As String In IO.Directory.GetFiles(Folder) 429. 'Ok indica se il file ha passato le condizioni 430. Dim Ok As Boolean = True 431. 'Se ci sono condizioni da applicare, le verifica 432. If VerifyConditions IsNot Nothing Then 433. 'Dato che nel caso di delegate multicast contenenti 434. 'rifermenti a funzione, il valore restituito 435. 'solo quello della prima funzione e a noi interessano 436. '<b>tutti</b> i valori restituiti, dobbiamo enumerare 437. 'ogni singolo oggetto delegate presente nel 438. 'delegate multicast e invocarlo singolarmente. 439. 'Ci viene in aiuto il metodo di istanza GetInvocationList, 440. 'che restituisce un array di delegate singoli. 441. For Each C As FileFilter In VerifyConditions.GetInvocationList() 442. 'Tutte le condizioni attive devono essere verificate, 443. 'quindi bisogna usare un And 444. Ok = Ok And C(File) 445. Next 446. End If 'Se le condizioni sono verificate, esegue le operazioni 447. If Ok Then 448. Try 449. DoOperations(File) 450. Catch Ex As Exception 451. Console.WriteLine("Impossibile eseguire l'operazione: " & Ex.Message) 452. End Try 453. End If 454. Next 455. 'Chiude il file di log se era aperto 456. If LogFile IsNot Nothing Then 457. LogFile.Close() 458. End If 459. 460. Console.WriteLine("Operazioni eseguite con successo!") 461. Console.ReadKey() 462. End Sub 463. 464. 465. End Module Questo esempio molto ar tificioso solo un assaggio delle potenzialit dei delegate (noter ete che ci sono anche molti conflitti, ad esempio se si seleziona sia copia che elimina, i file potr ebber o esser e cancellati pr ima della copia a seconda dell'or dine di invocazione). Vedr emo fr a poco come utilizzar e alcuni delegate piuttosto comuni messi a disposizione dal Fr amew or k, e scopr ir emo nella sezione B che i delegate sono il meccanismo fondamentale alla base di tutto il sistema degli ev enti.
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
adottar e: eccone una dimostr azione nel pr ossimo esempio: 001. Module Module1 002. 003. 'Classe astratta che rappresenta un risolutore di equazioni. 'Dato che di equazioni ce ne possono essere molte tipologie 004. 'differenti, non ha senso rendere questa classe istanziabile. 005. 006. 'Provando a scrivere qualcosa come: 007. ' Dim Eq As New EquationSolver() 008. 'Vi verr comunicato un errore, in quanto le classi 'astratte sono per loro natura non istanziabili 009. MustInherit Class EquationSolver 010. 011. 'Per lo stesso discorso fatto prima, se non conosciamo come 012. ' fatta l'equazione che questo tipo contiene non 013. 'possiamo neppure tentare di risolverla. Perci 014. 'ci limitiamo a dichiarare una funzione Solve come MustOverride. 015. 'Notate che il tipo restituito un array di Single, 016. 'in quanto le soluzioni saranno spesso pi di una. 017. Public MustOverride Function Solve() As Single() 018. End Class 019. 'La prossima classe rappresenta un risolutore di equazioni 020. 'polinomiali. Dato che la tipologia ben definita, 021. 'avremmo potuto anche <i>non</i> rendere astratta la classe 022. 'e, nella funzione Solve, utilizzare un Select Case per 023. 'controllare il grado dell'equazione. Ad ogni modo, 024. 'utile vedere come si comporta l'erediteriet attraverso 025. 'pi classi astratte. 026. 'Inoltre, ci ritorner molto utile in seguito disporre 027. 'di questa classe astratta intermedia 028. MustInherit Class PolynomialEquationSolver 029. Inherits EquationSolver 030. 031. Private _Coefficients() As Single 032. 033. 'Array di Single che contiene i coefficienti dei 034. 'termini di i-esimo grado all'interno dell'equazione. 035. 'L'elemento 0 dell'array indica il coefficiente del 036. 'termine a grado massimo. 037. Public Property Coefficients() As Single() 038. Get 039. Return _Coefficients 040. End Get 041. Set(ByVal value As Single()) 042. _Coefficients = value 043. End Set 044. End Property 045. 046. 'Ecco quello a cui volevo arrivare. Se un metodo astratto 047. 'lo si vuole mantenere tale anche nella classe derivata, 048. 'non basta scrivere: 049. ' MustOverride Function Solve() As Single() 050. 'Perc in questo caso verrebbe interpretato come 051. 'un membro che non c'entra niente con MyBase.Solve, 052. 'e si genererebbe un errore in quanto stiamo tentando 053. 'di dichiarare un nuovo membro con lo stesso nome 054. 'di un membro della classe base. 055. 'Per questo motivo, dobbiamo comunque usare il polimorfismo 056. 'come se si trattasse di un normale metodo e dichiararlo 057. 'Overrides. In aggiunta a questo, deve anche essere 058. 'astratto, e perci aggiungiamo MustOverride: 059. Public MustOverride Overrides Function Solve() As Single() 060. 061. 'Anche in questo caso usiamo il polimorfismo, ma ci riferiamo 062. 'alla semplice funzione ToString, derivata dalla classe base 063. 'di tutte le entit esistenti, System.Object. 064. 'Questa si limita a restituire una stringa che rappresenta 065. 'l'equazione a partire dai suoi coefficienti. Ad esempio: 066. ' 3x^2 + 2x^1 + 4x^0 = 0 067. 'Potete modificare il codice per eliminare le forme ridondanti 068. 069. 'x^1 e x^0. 070. Public Overrides Function ToString() As String 071.
072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121. 122. 123. 124. 125. 126. 127. 128. 129. 130. 131. 132. 133. 134. 135. 136. 137. 138. 139. 140. 141. 142. 143.
Dim Result As String = "" For I As Int16 = 0 To Me.Coefficients.Length - 1 If I > 0 Then Result &= " + " End If Result &= String.Format("{0}x^{1}", _ Me.Coefficients(I), Me.Coefficients.Length - 1 - I) Next Result &= " = 0" Return Result End Function End Class 'Rappresenta un risolutore di equazioni non polinomiali. 'La classe non astratta, ma non presenta alcun codice. 'Per risolvere questo tipo di equazioni, necessario 'sapere qualche cosa in pi rispetto al punto in cui siamo 'arrivati, perci mi limiter a lasciare in bianco Class NonPolynomialEquationSolver Inherits EquationSolver Public Overrides Function Solve() As Single() Return Nothing End Function End Class 'Rappresenta un risolutore di equazioni di primo grado. Eredita 'da PolynomialEquationSolver poich, ovviamente, si 'tratta di equazioni polinomiali. In pi, definisce 'le propriet a e b che sono utili per inserire i 'coefficienti. Infatti, l'equazione standard : ' ax + b = 0 Class LinearEquationSolver Inherits PolynomialEquationSolver Public Property a() As Single Get Return Me.Coefficients(0) End Get Set(ByVal value As Single) Me.Coefficients(0) = value End Set End Property Public Property b() As Single Get Return Me.Coefficients(1) End Get Set(ByVal value As Single) Me.Coefficients(1) = value End Set End Property 'Sappiamo gi quanti sono i coefficienti, dato 'che si tratta di equazioni lineari, quindi ridimensioniamo 'l'array il prima possibile. Sub New() ReDim Me.Coefficients(1) End Sub 'Funzione Overrides che sovrascrive il metodo astratto della 'classe base. Avrete notato che quando scrivete: ' Inherits PolynomialEquationSolver 'e premete invio, questa funzione viene aggiunta automaticamente 'al codice. Questa un'utile feature dell'ambiente 'di sviluppo Public Overrides Function Solve() As Single() If a <> 0 Then Return New Single() {-b / a}
144. 145. 146. 147. 148. 149. 150. 151. 152. 153. 154. 155. 156. 157. 158. 159. 160. 161. 162. 163. 164. 165. 166. 167. 168. 169. 170. 171. 172. 173. 174. 175. 176. 177. 178. 179. 180. 181. 182. 183. 184. 185. 186. 187. 188. 189. 190. 191. 192. 193. 194. 195. 196. 197. 198. 199. 200. 201. 202. 203. 204. 205. 206. 207. 208. 209. 210. 211. 212. 213. 214. 215.
Return Nothing End If End Function End Class 'Risolutore di equazioni di secondo grado: ' ax<sup>2</sup> + bx + c = 0 Class QuadraticEquationSolver Inherits LinearEquationSolver Public Property c() As Single Get Return Me.Coefficients(2) End Get Set(ByVal value As Single) Me.Coefficients(2) = value End Set End Property Sub New() ReDim Me.Coefficients(2) End Sub Public Overrides Function Solve() As Single() If b ^ 2 - 4 * a * c >= 0 Then Return New Single() { _ (-b - Math.Sqrt(b ^ 2 - 4 * a * c)) / 2, _ (-b + Math.Sqrt(b ^ 2 - 4 * a * c)) / 2} Else Return Nothing End If End Function End Class 'Risolutore di equazioni di grado superiore al secondo. So 'che avrei potuto inserire anche una classe relativa 'alle cubiche, ma dato che si tratta di un esempio, vediamo 'di accorciare il codice... 'Comunque, dato che non esiste formula risolutiva per 'le equazioni di grado superiore al quarto (e gi, 'ci mancava un'altra classe!), usiamo in questo caso 'un semplice ed intuitivo metodo di approssimazione degli 'zeri, il metodo dicotomico o di bisezione (che vi pu 'essere utile per risolvere un esercizio dell'eserciziario) Class HighDegreeEquationSolver Inherits PolynomialEquationSolver Private _Epsilon As Single Private _IntervalLowerBound, _IntervalUpperBound As Single 'Errore desiderato: l'algoritmo si fermer una volta 'raggiunta una precisione inferiore a Epsilon Public Property Epsilon() As Single Get Return _Epsilon End Get Set(ByVal value As Single) _Epsilon = value End Set End Property 'Limite inferiore dell'intervallo in cui cercare la soluzione Public Property IntervalLowerBound() As Single Get Return _IntervalLowerBound End Get Set(ByVal value As Single) _IntervalLowerBound = value End Set End Property
Else
216. 217. 218. 219. 220. 221. 222. 223. 224. 225. 226. 227. 228. 229. 230. 231. 232. 233. 234. 235. 236. 237. 238. 239. 240. 241. 242. 243. 244. 245. 246. 247. 248. 249. 250. 251. 252. 253. 254. 255. 256. 257. 258. 259. 260. 261. 262. 263. 264. 265. 266. 267. 268. 269. 270. 271. 272. 273. 274. 275. 276. 277. 278. 279. 280. 281. 282. 283. 284. 285. 286. 287.
'Limite superiore dell'intervallo in cui cercare la soluzione Public Property IntervalUpperBound() As Single Get Return _IntervalUpperBound End Get Set(ByVal value As Single) _IntervalUpperBound = value End Set End Property 'Valuta la funzione polinomiale. Dati i coefficienti immessi, 'noi disponiamo del polinomio p(x), quindi possiamo calcolare 'i valori che esso assume per ogni x Private Function EvaluateFunction(ByVal x As Single) As Single Dim Result As Single = 0 For I As Int16 = 0 To Me.Coefficients.Length - 1 Result += Me.Coefficients(I) * x ^ (Me.Coefficients.Length - 1 - I) Next Return Result End Function Public Overrides Function Solve() As Single() Dim a, b, c As Single Dim fa, fb, fc As Single Dim Interval As Single = 100 Dim I As Int16 = 0 Dim Result As Single a = IntervalLowerBound b = IntervalUpperBound 'Non esiste uno zero tra a e b se f(a) e f(b) hanno 'lo stesso segno If EvaluateFunction(a) * EvaluateFunction(b) > 0 Then Return Nothing End If Do 'c il punto medio tra a e b c = (a + b) / 2 'Calcola f(a), f(b) ed f(c) fa = EvaluateFunction(a) fb = EvaluateFunction(b) fc = EvaluateFunction(c) 'Se uno tra f(a), f(b) e f(c) vale zero, allora abbiamo 'trovato una soluzione perfetta, senza errori, ed 'usciamo direttamente dal ciclo If fa = 0 Then c = a Exit Do End If If fb = 0 Then c = b Exit Do End If If fc = 0 Then Exit Do End If 'Altrimenti, controlliamo quale coppia di valori scelti 'tra f(a), f(b) ed f(c) ha segni discorsi: lo zero si trover 'tra le ascisse di questi If fa * fc < 0 Then b = c Else a = c End If Loop Until Math.Abs(a - b) < Me.Epsilon
288. 289. 290. 291. 292. 293. 294. 295. 296. 297. 298. 299. 300. 301. 302. 303. 304. 305. 306. 307. 308. 309. 310. 311. 312. 313. 314. 315. 316. 317. 318. 319. 320. 321. 322. 323. 324. 325. 326. 327. 328. 329. 330. 331. 332. 333. 334. 335. 336. 337. 338. 339. 340. 341. 342. 343. 344. 345. 346. 347. 348. 349. 350. 351. 352. 353. 354. 355. 356. 357. 358.
'Cicla finch l'ampiezza dell'intervallo non 'sufficientemente piccola, quindi assume come zero pi 'probabile il punto medio tra a e b: Result = c Return New Single() {Result} End Function End Class Sub Main() 'Contiene un generico risolutore di equazioni. Non sappiamo ancora 'quale tipologia di equazione dovremo risolvere, ma sappiamo per 'certo che lo dovremo fare, ed EquationSolver la classe 'base di tutti i risolutori che espone il metodo Solve. Dim Eq As EquationSolver Dim x() As Single Dim Cmd As Char Console.WriteLine("Scegli una tipologia di equazione: ") Console.WriteLine(" l - lineare;") Console.WriteLine(" q - quadratica;") Console.WriteLine(" h - di grado superiore al secondo;") Console.WriteLine(" e - non polinomiale;") Cmd = Console.ReadKey().KeyChar Console.Clear() If Cmd <> "e" Then 'Ancora, sappiamo che si tratta di un'equazione polinomiale 'ma non di quale grado Dim Poly As PolynomialEquationSolver 'Ottiene i dati relativi a ciascuna equazione Select Case Cmd Case "l" Dim Linear As New LinearEquationSolver() Poly = Linear Case "q" Dim Quadratic As New QuadraticEquationSolver() Poly = Quadratic Case "h" Dim High As New HighDegreeEquationSolver() Dim CoefNumber As Int16 Console.WriteLine("Inserire il numero di coefficienti: ") CoefNumber = Console.ReadLine ReDim High.Coefficients(CoefNumber - 1) Console.WriteLine("Inserire i limti dell'intervallo in cui cercare gli zeri:") High.IntervalLowerBound = Console.ReadLine High.IntervalUpperBound = Console.ReadLine Console.WriteLine("Inserire la precisione (epsilon):") High.Epsilon = Console.ReadLine Poly = High End Select 'A questo punto la variabile Poly contiene sicuramente un oggetto '(LinearEquationSolver, QuadraticEquationSolver oppure 'HighDegreeEquationSolver), anche se non sappiamo quale. Tuttavia, 'tutti questi sono pur sempre polinomiali e perci tutti 'hanno bisogno di sapere i coefficienti del polinomio. 'Ecco che allora possiamo usare Poly con sicurezza perc 'sicuramente contiene un oggetto e la propriet Coefficients ' stata definita proprio nella classe PolynomialEquationSolver. '<b>N.B.: ricordate tutto quello che abbiamo detto sull'assegnamento ' di un oggetto di classe derivata a uno di classe base!</b> Console.WriteLine("Inserire i coefficienti: ") For I As Int16 = 1 To Poly.Coefficients.Length - 1 Console.Write("a{0} = ", Poly.Coefficients.Length - I) Poly.Coefficients(I - 1) = Console.ReadLine Next 'Assegnamo Poly a Eq. Osservate che siamo andati via via dal
'caso pi particolare al pi generale: 359. ' - Abbiamo creato un oggetto specifico per un certo grado 360. ' di un'equazione polinomiale (Linear, Quadratic, High); 361. ' - Abbiamo messo quell'oggetto in uno che si riferisce 362. ' genericamente a tutti i polinomi; 363. ' - Infine, abbiamo posto quest'ultimo in uno ancora pi 364. ' generale che si riferisce a tutte le equazioni; 365. 'Questo percorso porta da oggetto molto specifici e ricchi di membri 366. '(tante propriet e tanti metodi), a tipi molto generali 367. 'e poveri di membri (nel caso di Eq, un solo metodo). 368. Eq = Poly 369. Else 370. 'Inseriamo in Eq un nuovo oggetto per risolvere equazioni non 371. 'polinomiali, anche se il codice al momento vuoto 372. Eq = New NonPolynomialEquationSolver 373. Console.WriteLine("Non implementato") 374. End If 375. 376. 'Risolviamo l'equazione. Richiamare la funzione Solve da un oggetto 377. 'EquationSolver potrebbe non dirvi nulla, ma ricordate che dentro Eq 378. ' memorizzato un oggetto pi specifico in cui 379. ' stata definita la funzione Solve(). Per questo motivo, 380. 'anche se Eq di tipo classe base, purtuttavia contiene 381. 'al proprio interno un oggetto di tipo classe derivata, ed 382. ' questo che conta: viene usato il metodo Solve della classe 383. 'derivata. 'Se ci pensate bene, vi verr pi spontaneo capire, 384. 385. 'poich noi, ora, stiamo guardando ATTRAVERSO il tipo 'EquationSolver un oggetto di altro tipo. come osservare 386. 'attraverso filtri via via sempre pi fitti (cfr 387. 'immagine seguente) 388. x = Eq.Solve() 389. 390. If x IsNot Nothing Then 391. Console.WriteLine("Soluzioni trovate: ") 392. For Each s As Single In x 393. Console.WriteLine(s) 394. Next 395. Else 396. Console.WriteLine("Nessuna soluzione") 397. End If 398. 399. Console.ReadKey() 400. End Sub 401. 402. End Module Eccovi un'immagine dell'ultimo commento:
Il piano r osso l'oggetto che r ealmente c' in memor ia (ad esempio, Linear EquationSolver ); il piano blu con tr e aper tur e ci che r iusciamo a veder e quando l'oggetto viene memor izzato in una classe astr atta PolynomialEquationSolver ; il piano blu iniziale, invece, ci a cui possiamo acceder e attr aver so un EquationSolver : il fascio di luce indica le nostr e possibilit di accesso. pr opr io il caso di dir e che c' molto di pi di ci che si vede!
Classi Sigillate
Le classi sigillate sono esattamente l'opposto di quelle astr atte, ossia non possono mai esser e er editate. Si dichiar ano con la keyw or d NotInher itable: 1. NotInheritable Class Example 2. '... 3. End Class Allo stesso modo, penser ete voi, i membr i che non possono subir e over loading sar anno mar cati con qualcosa tipo NotOver r idable... In par te esatto, ma in par te er r ato. La keyw or d NotOver r idable si pu applicar e solo e soltanto a metodi gi modificati tr amite polimor fismo, ossia Over r ides. 01. 02. 03. 04. 05. 06. 07. 08. 09. 10. 11. 12. 13. 14. 15. 16. Class A Sub DoSomething() '... End Sub End Class Class B Inherits A 'Questa procedura sovrascrive la precedente versione 'di DoSomething dichiarata in A, ma preclude a tutte le 'classi derivate da B la possibilit di fare lo stesso NotOverridable Overrides Sub DoSomething() '... End Sub End Class
Inoltr e, le classi sigillate no n possono mai espor r e membr i sigillati, anche per ch tutti i lor o membr i lo sono implicitamente (se una classe non pu esser e er editata, ovviamente non si potr anno r idefinir e i membr i con polimor fismo).
Classi Parziali
Una classe si dice par ziale quando il suo cor po suddiviso su pi files. Si tr atta solamento di un'utilit pr atica che ha poco a che veder e con la pr ogr ammazione ad oggetti. Mi sembr ava, per , or dinato espor r e tutte le keyw or d associate alle classi in un solo capitolo. Semplicemente, una classe par ziale si dichiar a in questo modo: 1. Partial Class [Nome] 2. '... 3. End Class sufficiente dichiar ar e una classe come par ziale per ch il compilator e associ, in fase di assemblaggio, tutte le classi con lo stesso nome in file diver si a quella definizione. Ad esempio: 01. 'Nel file Codice1.vb : 02. Partial Class A 03. Sub One() 04.
05. 06. 07. 08. 09. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36.
'... End Sub End Class 'Nel file Codice2.vb Class A Sub Two() '... End Sub End Class 'Nel file Codice3.vb Class A Sub Three() '... End Sub End Class 'Tutte le classi A vengono compilate come un'unica classe 'perch una possiede la keyword Partial: Class A Sub One() '... End Sub Sub Two() '... End Sub Sub Three() '... End Sub End Class
A37. Le Interfacce
Sc opo delle Interfac c e
Le inter facce sono un'entit davver o singolar e all'inter no del .NET Fr amew or k. La lor o funzione assimilabile a quella delle classi astr atte, ma il modo con cui esse la svolgono molto diver so da ci che abbiamo visto nel capitolo pr ecedente. Il pr incipale scopo di un'inter faccia definir e lo scheletr o di una classe; potr ebbe esser e scher zosamente assimilata alla r icetta con cui si pr epar a un dolce. Quello che l'inter faccia X fa, ad esempio, consiste nel dir e che per costr uir e una classe Y che r ispetti "la r icetta" descr itta in X ser vono una pr opr iet Id di tipo Integer , una funzione GetSomething senza par ametr i che r estituisce una str inga e una pr ocedur a DoSomething con un singolo par ametr o Double. Tutte le classi che avr anno intenzione di seguir e i pr ecetti di X (in ger go im plem entar e X) dovr anno definir e, allo stesso modo, quella pr opr iet di quel tipo e quei metodi con quelle specifiche signatur e (il nome ha impor tanza r elativa). Faccio subito un esempio. Fino ad or a, abbiamo visto essenzialmente due tipi di collezione: gli Ar r ay e gli Ar r ayList. Sia per l'uno che per l'altr o, ho detto che possibile eseguir e un'iter azione con il costr utto For Each: 01. 02. 03. 04. 05. 06. 07. 08. 09. 10. 11. 12. 13. 14. 15. Dim Ar() As Int32 = {1, 2, 3, 4, 5, 6} Dim Al As New ArrayList For I As Int32 = 1 To 40 Al.Add(I) Next 'Stampa i valori di Ar: For Each K As Int32 In Ar Console.WriteLine(K) Next 'Stampa i valori di Al For Each K As Int32 In Al Console.WriteLine(K) Next
Ma il sistema come fa a saper e che Ar e Al sono degli insiemi di valor i? Dopotutto, il lor o nome significativo solo per noi pr ogr ammator i, mentr e per il calcolator e non altr o che una sequenza di car atter i. Allo stesso modo, il codice di Ar r ay e Ar r ayList, definito dai pr ogr ammator i che hanno scr itto il Fr amew or k, intelligibile solo agli uomini, per ch al computer non comunica nulla sullo scopo per il quale stato scr itto. Allor a, siamo al punto di par tenza: nelle classi Ar r ay e Ar r ayList non c' nulla che possa far "capir e" al pr ogr amma che quelli sono a tutti gli effetti delle collezioni e che, quindi, sono iter abili; e, anche se in qualche str ano modo l'elabor ator e lo potesse capir e, non "sapr ebbe" (in quanto entit non senziente) come far per estr ar r e singoli dati e dar celi uno in fila all'altr o. Ecco che entr ano in scena le inter facce: tutte le classi che r appr esentano un insieme o una collezione di elementi implemen tan o l'inter faccia IEnumer able, la quale, se potesse par lar e, dir ebbe "Guar da che questa classe una collezione, tr attala di conseguenza!". Questa inter faccia obbliga le classi dalle quali implementata a definir e alcuni metodi che ser vono per l'enumer azione (Cur r ent, MoveNex t e Reset) e che vedr emo nei pr ossimi capitoli. In conclusione, quindi, il For Each pr ima di tutto contr olla che l'oggetto posto dopo la clausola "In" implementi l'inter faccia IEnumer able. Quindi r ichiama il metodo Reset per por si sul pr imo elemento, poi deposita in K il valor e esposto dalla pr opr iet Cur r ent, esegue il codice contenuto nel pr opr io cor po e, una volta ar r ivato a Nex t, esegue il metodo MoveNex t per avanzar e al pr ossimo elemento. Il For Each " sicur o" dell'esistenza di questi membr i per ch l'inter faccia IEnumer able ne impone la definizione. Riassumendo, le inter facce hanno il compito di infor mar e il sistema su quali siano le car atter istiche e i compiti di una classe. Per questo motivo, il lor o nomi ter minano spesso in "-able", come ad esempio IEnumer able, IEquatable, ICompr able, che ci dicono "- enumer abile", "- eguagliabile", "- compar abile", " ... qualcosa".
Or a che sappiamo come dichiar ar e un'inter faccia, dobbiamo scopr ir e come usar la. Per implementar e un'inter faccia in una classe, si usa questa sintassi: 1. Class Example 2. Implements [Nome Interfaccia] 3. 4. [Membro] Implements [Nome Interfaccia].[Membro] 5. End Class Si capisce meglio con un esempio: 01. Module Module1 02. Interface IIdentifiable 03. ReadOnly Property Id() As Int32 04. Function ToString() As String 05. End Interface 06. 07.
08. 09. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79.
'Rappresenta un pacco da spedire Class Pack 'Implementa l'interfaccia IIdentifiable, in quanto un pacco 'dovrebbe poter essere ben identificato Implements IIdentifiable 'Notate bene che l'interfaccia ci obbliga a definire una 'propriet, ma non ci obbliga a definire un campo 'ad essa associato Private _Id As Int32 Private _Destination As String Private _Dimensions(2) As Single 'La classe definisce una propriet id di tipo Integer 'e la associa all'omonima presente nell'interfaccia in 'questione. Il legame tra questa propriet Id e quella 'presenta nell'interfaccia dato solamente dalla 'clausola (si chiama cos in gergo) "Implements", 'la quale avvisa il sistema che il vincolo imposto ' stato soddisfatto. 'N.B.: il fatto che il nome di questa propriet sia uguale 'a quella definita in IIdentifiable non significa nulla. 'Avremmo potuto benissimo chiamarla "Pippo" e associarla 'a Id tramite il codice "Implements IIdentifiable.Id", ma 'ovviamente sarebbe stata una palese idiozia XD Public ReadOnly Property Id() As Integer Implements IIdentifiable.Id Get Return _Id End Get End Property 'Destinazione del pacco. 'Il fatto che l'interfaccia ci obblighi a definire quei due 'membri non significa che non possiamo definirne altri Public Property Destination() As String Get Return _Destination End Get Set(ByVal value As String) _Destination = value End Set End Property 'Piccolo ripasso delle propriet indicizzate e 'della gestione degli errori Public Property Dimensions(ByVal Index As Int32) As Single Get If (Index >= 0) And (Index < 3) Then Return _Dimensions(Index) Else Throw New IndexOutOfRangeException() End If End Get Set(ByVal value As Single) If (Index >= 0) And (Index < 3) Then _Dimensions(Index) = value Else Throw New IndexOutOfRangeException() End If End Set End Property Public Overrides Function ToString() As String Implements IIdentifiable.ToString Return String.Format("{0}: Pacco {1}x{2}x{3}, Destinazione: {4}", _ Me.Id, Me.Dimensions(0), Me.Dimensions(1), _ Me.Dimensions(2), Me.Destination) End Function End Class Sub Main() '... End Sub
End Module Or a che abbiamo implementato l'inter faccia nella classe Pack, tuttavia, non sappiamo che far cene. Siamo a conoscenza del fatto che gli oggetti Pack sar anno sicur amente identificabili, ma nulla di pi. Ritor niamo, allor a, all'esempio del pr imo par agr afo: cos' che r ende ver amente utile IEnumer able, al di l del fatto di r ender e funzionante il For Each? Si applica a qualsiasi collezione o insieme, non impor ta di quale natur a o per quali scopi, non impor ta nemmeno il codice che sottende all'enumer azione: l'impor tante che una vastissima gamma di oggetti possano esser e r icondotti ad un solo ar chetipo (io ne ho nominati solo due, ma ce ne sono a iosa). Allo stesso modo, potr emo usar e IIdentifiable per manipolar e una gr an quantit di dati di natur a differ ente. Ad esempio, il codice di sopr a potr ebbe esser e sviluppato per cr ear e un sistema di gestione di un ufficio postale. Eccone un esempio: 001. Module Module1 002. 003. Interface IIdentifiable 004. ReadOnly Property Id() As Int32 005. Function ToString() As String 006. End Interface 007. 008. Class Pack 009. Implements IIdentifiable 010. Private _Id As Int32 011. 012. Private _Destination As String 013. Private _Dimensions(2) As Single 014. 015. Public ReadOnly Property Id() As Integer Implements IIdentifiable.Id Get 016. Return _Id 017. 018. End Get End Property 019. 020. Public Property Destination() As String 021. Get 022. Return _Destination 023. End Get 024. Set(ByVal value As String) 025. _Destination = value 026. End Set 027. End Property 028. 029. Public Property Dimensions(ByVal Index As Int32) As Single 030. Get 031. If (Index >= 0) And (Index < 3) Then 032. Return _Dimensions(Index) 033. Else 034. Throw New IndexOutOfRangeException() 035. End If 036. End Get 037. Set(ByVal value As Single) 038. If (Index >= 0) And (Index < 3) Then 039. _Dimensions(Index) = value 040. Else 041. Throw New IndexOutOfRangeException() 042. End If 043. End Set 044. End Property 045. 046. Sub New(ByVal Id As Int32) 047. _Id = Id 048. End Sub 049. 050. Public Overrides Function ToString() As String Implements IIdentifiable.ToString 051. Return String.Format("{0:0000}: Pacco {1}x{2}x{3}, Destinazione: {4}", _ 052. Me.Id, Me.Dimensions(0), Me.Dimensions(1), _ 053. Me.Dimensions(2), Me.Destination) 054. End Function 055. End Class 056. 057. 058.
059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121. 122. 123. 124. 125. 126. 127. 128. 129. 130.
Class Telegram Implements IIdentifiable Private _Id As Int32 Private _Recipient As String Private _Message As String Public ReadOnly Property Id() As Integer Implements IIdentifiable.Id Get Return _Id End Get End Property Public Property Recipient() As String Get Return _Recipient End Get Set(ByVal value As String) _Recipient = value End Set End Property Public Property Message() As String Get Return _Message End Get Set(ByVal value As String) _Message = value End Set End Property Sub New(ByVal Id As Int32) _Id = Id End Sub Public Overrides Function ToString() As String Implements IIdentifiable.ToString Return String.Format("{0:0000}: Telegramma per {1} ; Messaggio = {2}", _ Me.Id, Me.Recipient, Me.Message) End Function End Class Class MoneyOrder Implements IIdentifiable Private _Id As Int32 Private _Recipient As String Private _Money As Single Public ReadOnly Property Id() As Integer Implements IIdentifiable.Id Get Return _Id End Get End Property Public Property Recipient() As String Get Return _Recipient End Get Set(ByVal value As String) _Recipient = value End Set End Property Public Property Money() As Single Get Return _Money End Get Set(ByVal value As Single) _Money = value End Set End Property
131. 132. 133. 134. 135. 136. 137. 138. 139. 140. 141. 142. 143. 144. 145. 146. 147. 148. 149. 150. 151. 152. 153. 154. 155. 156. 157. 158. 159. 160. 161. 162. 163. 164. 165. 166. 167. 168. 169. 170. 171. 172. 173. 174. 175. 176. 177. 178. 179. 180. 181. 182. 183. 184. 185. 186. 187. 188. 189. 190. 191. 192. 193. 194. 195. 196. 197. 198. 199. 200. 201. 202.
Sub New(ByVal Id As Int32) _Id = Id End Sub Public Overrides Function ToString() As String Implements IIdentifiable.ToString Return String.Format("{0:0000}: Vaglia postale per {1} ; Ammontare = {2}", _ Me.Id, Me.Recipient, Me.Money) End Function End Class 'Classe che elabora dati di tipo IIdentifiable, ossia qualsiasi 'oggetto che implementi tale interfaccia Class PostalProcessor 'Tanto per tenersi allenati coi delegate, ecco una 'funzione delegate che funge da filtro per i vari id Public Delegate Function IdSelector(ByVal Id As Int32) As Boolean Private _StorageCapacity As Int32 Private _NextId As Int32 = 0 'Un array di interfacce. Quando una variabile viene 'dichiarata come di tipo interfaccia, ci 'che pu contenere qualsiasi oggetto 'che implementi quell'interfaccia. Per lo stesso 'discorso fatto nel capitolo precedente, noi 'possiamo vedere <i>attraverso</i> l'interfaccia 'solo quei membri che essa espone direttamente, anche 'se il contenuto vero e proprio qualcosa 'di pi Private Storage() As IIdentifiable 'Capacit del magazzino. Assumeremo che tutti 'gli oggetti rappresentati dalle classi Pack, Telegram 'e MoneyOrder vadano in un magazzino immaginario che, 'improbabilmente, riserva un solo posto per ogni 'singolo elemento Public Property StorageCapacity() As Int32 Get Return _StorageCapacity End Get Set(ByVal value As Int32) _StorageCapacity = value ReDim Preserve Storage(value) End Set End Property 'Modifica od ottiene un riferimento all'Index-esimo 'oggetto nell'array Storage Public Property Item(ByVal Index As Int32) As IIdentifiable Get If (Index >= 0) And (Index < Storage.Length) Then Return Me.Storage(Index) Else Throw New IndexOutOfRangeException() End If End Get Set(ByVal value As IIdentifiable) If (Index >= 0) And (Index < Storage.Length) Then Me.Storage(Index) = value Else Throw New IndexOutOfRangeException() End If End Set End Property 'Restituisce la prima posizione libera nell'array 'Storage. Anche se in questo esempio non l'abbiamo 'contemplato, gli elementi possono anche essere rimossi 'e quindi lasciare un posto libero nell'array Public ReadOnly Property FirstPlaceAvailable() As Int32 Get For I As Int32 = 0 To Me.Storage.Length - 1 If Me.Storage(I) Is Nothing Then
203. 204. 205. 206. 207. 208. 209. 210. 211. 212. 213. 214. 215. 216. 217. 218. 219. 220. 221. 222. 223. 224. 225. 226. 227. 228. 229. 230. 231. 232. 233. 234. 235. 236. 237. 238. 239. 240. 241. 242. 243. 244. 245. 246. 247. 248. 249. 250. 251. 252. 253. 254. 255. 256. 257. 258. 259. 260. 261. 262. 263. 264. 265. 266. 267. 268. 269. 270. 271. 272. 273. 274.
Return I End If
'Tutti gli oggetti che inizializzeremo avranno bisogno 'di un id: ce lo fornisce la stessa classe Processor 'tramite questa propriet che si autoincrementa Public ReadOnly Property NextId() As Int32 Get _NextId += 1 Return _NextId End Get End Property 'Due possibili costruttori: uno che accetta un insieme 'gi formato di elementi... Public Sub New(ByVal Items() As IIdentifiable) Me.Storage = Items _SorageCapacity = Items.Length End Sub '... e uno che accetta solo la capacit del magazzino Public Sub New(ByVal Capacity As Int32) Me.StorageCapacity = Capacity End Sub 'Stampa a schermo tutti gli elementi che la funzione 'contenuta nel parametro Selector di tipo delegate 'considera validi (ossia tutti quelli per cui 'Selector.Invoke restituisce True) Public Sub PrintByFilter(ByVal Selector As IdSelector) For Each K As IIdentifiable In Storage If K Is Nothing Then Continue For End If If Selector.Invoke(K.Id) Then Console.WriteLine(K.ToString()) End If Next End Sub 'Stampa l'oggetto con Id specificato Public Sub PrintById(ByVal Id As Int32) For Each K As IIdentifiable In Storage If K Is Nothing Then Continue For End If If K.Id = Id Then Console.WriteLine(K.ToString()) Exit For End If Next End Sub 'Cerca tutti gli elementi che contemplano all'interno 'della propria descrizione la stringa Str e li 'restituisce come array di Id Public Function SearchItems(ByVal Str As String) As Int32() Dim Temp As New ArrayList For Each K As IIdentifiable In Storage If K Is Nothing Then Continue For End If If K.ToString().Contains(Str) Then Temp.Add(K.Id) End If Next
275. 276. 277. 278. 279. 280. 281. 282. 283. 284. 285. 286. 287. 288. 289. 290. 291. 292. 293. 294. 295. 296. 297. 298. 299. 300. 301. 302. 303. 304. 305. 306. 307. 308. 309. 310. 311. 312. 313. 314. 315. 316. 317. 318. 319. 320. 321. 322. 323. 324. 325. 326. 327. 328. 329. 330. 331. 332. 333. 334. 335. 336. 337. 338. 339. 340. 341. 342. 343. 344. 345. 346.
Dim Result(Temp.Count - 1) As Int32 For I As Int32 = 0 To Temp.Count - 1 Result(I) = Temp(I) Next Temp.Clear() Temp = Nothing Return Result End Function End Class Private Processor As New PostalProcessor(10) Private Cmd As Char Private IdFrom, IdTo As Int32 Function SelectId(ByVal Id As Int32) As Boolean Return (Id >= IdFrom) And (Id <= IdTo) End Function Sub InsertItems(ByVal Place As Int32) Console.WriteLine("Scegliere la tipologia di oggetto:") Console.WriteLine(" p - pacco;") Console.WriteLine(" t - telegramma;") Console.WriteLine(" v - vaglia postale;") Cmd = Console.ReadKey().KeyChar Console.Clear() Select Case Cmd Case "p" Dim P As New Pack(Processor.NextId) Console.WriteLine("Pacco - Id:{0:0000}", P.Id) Console.Write("Destinazione: ") P.Destination = Console.ReadLine Console.Write("Larghezza: ") P.Dimensions(0) = Console.ReadLine Console.Write("Lunghezza: ") P.Dimensions(1) = Console.ReadLine Console.Write("Altezza: ") P.Dimensions(2) = Console.ReadLine Processor.Item(Place) = P Case "t" Dim T As New Telegram(Processor.NextId) Console.WriteLine("Telegramma - Id:{0:0000}", T.Id) Console.Write("Destinatario: ") T.Recipient = Console.ReadLine Console.Write("Messaggio: ") T.Message = Console.ReadLine Processor.Item(Place) = T Case "v" Dim M As New MoneyOrder(Processor.NextId) Console.WriteLine("Vaglia - Id:{0:0000}", M.Id) Console.Write("Beneficiario: ") M.Recipient = Console.ReadLine Console.Write("Somma: ") M.Money = Console.ReadLine Processor.Item(Place) = M Case Else Console.WriteLine("Comando non riconosciuto.") Console.ReadKey() Exit Sub End Select Console.WriteLine("Inserimento eseguito!") Console.ReadKey() End Sub Sub ProcessData() Console.WriteLine("Selezionare l'operazione:") Console.WriteLine(" c - cerca;") Console.WriteLine(" v - visualizza;")
347. 348. 349. 350. 351. 352. 353. 354. 355. 356. 357. 358. 359. 360. 361. 362. 363. 364. 365. 366. 367. 368. 369. 370. 371. 372. 373. 374. 375. 376. 377. 378. 379. 380. 381. 382. 383. 384. 385. 386. 387. 388. 389. 390. 391. 392. 393. 394. 395. 396. 397. 398. 399. 400. 401. 402. 403. 404. 405. 406. 407. 408. 409. 410. 411. 412. 413. 414. 415. 416. End
Cmd = Console.ReadKey().KeyChar Console.Clear() Select Case Cmd Case "c" Dim Str As String Console.WriteLine("Inserire la parola da cercare:") Str = Console.ReadLine Dim Ids() As Int32 = Processor.SearchItems(Str) Console.WriteLine("Trovati {0} elementi. Visualizzare? (y/n)", Ids.Length) Cmd = Console.ReadKey().KeyChar Console.WriteLine() If Cmd = "y" Then For Each Id As Int32 In Ids Processor.PrintById(Id) Next End If Case "v" Console.WriteLine("Visualizzare gli elementi") Console.Write("Da Id: ") IdFrom = Console.ReadLine Console.Write("A Id: ") IdTo = Console.ReadLine Processor.PrintByFilter(AddressOf SelectId) Case Else Console.WriteLine("Comando sconosciuto.") End Select Console.ReadKey() End Sub Sub Main() Do Console.WriteLine("Gestione ufficio") Console.WriteLine() Console.WriteLine("Selezionare l'operazione da effettuare:") Console.WriteLine(" i - inserimento oggetti;") Console.WriteLine(" m - modifica capacit magazzino;") Console.WriteLine(" p - processa i dati;") Console.WriteLine(" e - esci.") Cmd = Console.ReadKey().KeyChar Console.Clear() Select Case Cmd Case "i" Dim Index As Int32 = Processor.FirstPlaceAvailable Console.WriteLine("Inserimento oggetti in magazzino") Console.WriteLine() If Index > -1 Then InsertItems(Index) Else Console.WriteLine("Non c' pi spazio in magazzino!") Console.ReadKey() End If Case "m" Console.WriteLine("Attuale capacit: " & Processor.StorageCapacity) Console.WriteLine("Inserire una nuova dimensione: ") Processor.StorageCapacity = Console.ReadLine Console.WriteLine("Operazione effettuata.") Console.ReadKey() Case "p" ProcessData() End Select Console.Clear() Loop Until Cmd = "e" End Sub Module
Avevo in mente di definir e anche un'altr a inter faccia, IPayable, per calcolar e anche il costo di spedizione di ogni pezzo: volevo far notar e come, sebbene il costo vada calcolato in manier a diver sa per i tr e tipi di oggetto (in base alle dimensioni per il pacco, in base al numer o di par ole per il telegr amma e in base all'ammontar e inviato per il vaglia), bastasse r ichiamar e una funzione attr aver so l'inter faccia per ottener e il r isultato. Poi ho consider ato che un esempio di 400 r ighe er a gi abbastanza. Ad ogni modo, user adesso quel'idea in uno spezzone tr atto dal pr ogr amma appena scr itto per mostr ar e l'uso di inter facce multiple: 01. Module Module1 02. '... 03. Interface IPayable 04. Function CalculateSendCost() As Single 05. End Interface 06. 07. 08. Class Telegram 'Nel caso di pi interfacce, le si separa con la virgola 09. Implements IIdentifiable, IPayable 10. 11. 12. '... 13. Public Function CalculateSendCost() As Single Implements IPayable.CalculateSendCost 14. 15. 'Come vedremo nel capitolo dedicato alle stringhe, 'la funzione Split(c) spezza la stringa in tante 16. 17. 'parti, divise dal carattere c, e le restituisce 18. 'sottoforma di array. In questo caso, tutte le sottostringhe 'separate da uno spazio sono all'incirca tante 19. 'quanto il numero di parole nella frase 20. Select Case Me.Message.Split(" ").Length 21. Case Is <= 20 22. Return 4.39 23. Case Is <= 50 24. Return 6.7 25. Case Is <= 100 26. Return 10.3 27. Case Is <= 200 28. Return 19.6 29. Case Is <= 500 30. Return 39.75 31. End Select 32. End Function 33. End Class 34. 35. '... 36. 37. End Class
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
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.
IComparable e IComparer
Un oggetto che implementa ICompar able comunica implicitamente al .NET Fr amew or k che pu esser e confr ontato con altr i oggetti, stabilendo se uno di essi maggior e, minor e o uguale all'altr o e abilitando in questo modo l'or dinamento automatico attr aver so il metodo Sor t di una collection. Infatti, tale metodo confr onta uno ad uno ogni elemento di una collezione o di un ar r ay e tr amite la funzione Compar eTo che ogni inter faccia ICompar able espone e li or dina in or dine cr escente o decr escente. Compar eTo una funzione di istanza che implementa ICompar able.Compar eTo e ha dei r isultati pr edefiniti: r estituisce 1 se l'oggetto passato come par ametr o minor e dell'oggetto dalla quale viene r ichiamata, 0 se uguale e -1 se maggior e. Ad esempio, questo semplice pr ogr amma illustr a il funzionamento di Compar eTo e Sor t: 01. Module Module1 02. Sub Main() 03. Dim A As Int32 04. 05. Console.WriteLine("Inserisci un numero intero:") 06. A = Console.ReadLine 07. 08. 'Tutti i tipi di base espongono il metodo CompareTo, poich 09. 'tutti implementano l'interfaccia IComparable: 10. If A.CompareTo(10) = 1 Then 11. Console.WriteLine(A & " maggiore di 10") 12. ElseIf A.CompareTo(10) = 0 Then 13. Console.WriteLine(A & " uguale a 10") 14. Else 15. Console.WriteLine(A & " minore di 10") 16. End If 17. 18. 'Il fatto che i tipi di base siano confrontabili implica 19. 'che si possano ordinare tramite il metodo Sort di una 20. 'qualsiasi collezione o array di elementi 21. Dim B() As Int32 = {1, 5, 2, 8, 10, 56} 22. 'Ordina l'array 23. Array.Sort(B) 'E visualizza i numeri in ordine crescente 24. For I As Int16 = 0 To UBound(B) 25. 26. Console.WriteLine(B(I)) Next 27. 28. 'Anche String espone questo metodo, quindi si pu ordinare 29. 'alfabeticamente un insieme di stringhe: 30. 31. Dim C As New ArrayList C.Add("Banana") 32. C.Add("Zanzara") 33. C.Add("Anello") 34. C.Add("Computer") 35. 'Ordina l'insieme 36. C.Sort() 37. For I As Int16 = 0 To C.Count - 1 38. Console.WriteLine(C(I)) 39. Next 40. 41. Console.ReadKey() 42. 43.
End Sub 44. End Module Dopo aver immesso un input, ad esempio 8, avr emo la seguente scher mata: Inserire un numero intero: 8 8 minore di 10 1 2 5 8 10 56 Anello Banana Computer Zanzara Come si osser va, tutti gli elementi sono stati or dinati cor r ettamente. Or a che abbiamo visto la potenza di ICompar able, vediamo di capir e come implementar la. L'esempio che pr ender come r ifer imento or a pone una semplice classe Per son, di cui si gi par lato addietr o, e or dina un Ar r ayList di questi oggetti pr endendo come r ifer imento il nome completo: 01. Module Module1 02. Class Person 03. Implements IComparable 04. Private _FirstName, _LastName As String Private ReadOnly _BirthDay As Date 05. 06. 07. Public Property FirstName() As String Get 08. 09. Return _FirstName End Get 10. Set(ByVal Value As String) 11. 12. If Value <> "" Then 13. _FirstName = Value 14. End If 15. End Set End Property 16. 17. 18. Public Property LastName() As String 19. Get 20. Return _LastName 21. End Get Set(ByVal Value As String) 22. If Value <> "" Then 23. 24. _LastName = Value End If 25. End Set 26. End Property 27. 28. Public ReadOnly Property BirthDay() As Date 29. Get 30. Return _BirthDay 31. End Get 32. End Property 33. 34. Public ReadOnly Property CompleteName() As String 35. Get 36. Return _FirstName & " " & _LastName 37. End Get 38. End Property 39. 40. 'Per definizione, purtroppo, CompareTo deve sempre usare 41. 'un parametro di tipo Object: risolveremo questo problema 42. 43.
44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. End
'pi in l utilizzando i Generics Public Function CompareTo(ByVal obj As Object) As Integer _ Implements IComparable.CompareTo 'Un oggetto non-nothing (questo) sempre maggiore di 'un oggetto Nothing (ossia obj) If obj Is Nothing Then Return 1 End If 'Tenta di convertire obj in Person Dim P As Person = DirectCast(obj, Person) 'E restituisce il risultato dell'operazione di 'comparazione tra stringhe dei rispettivi nomi Return String.Compare(Me.CompleteName, P.CompleteName) End Function Sub New(ByVal FirstName As String, ByVal LastName As String, _ ByVal BirthDay As Date) Me.FirstName = FirstName Me.LastName = LastName Me._BirthDay = BirthDay End Sub End Class Sub Main() 'Crea un array di oggetti Person Dim Persons() As Person = _ {New Person("Marcello", "Rossi", Date.Parse("10/10/1992")), _ New Person("Guido", "Bianchi", Date.Parse("01/12/1980")), _ New Person("Bianca", "Brega", Date.Parse("23/06/1960")), _ New Person("Antonio", "Felice", Date.Parse("16/01/1930"))} 'E li ordina, avvalendosi di IComparable.CompareTo Array.Sort(Persons) For I As Int16 = 0 To UBound(Persons) Console.WriteLine(Persons(I).CompleteName) Next Console.ReadKey() End Sub Module
Dato che il nome viene pr ima del congnome, la lista sar : Antonio, Bianca, Guido, Mar cello. E se si volesse or dinar e la lista di per sone in base alla data di nascita? Non possibile definir e due ver sioni di Compar eTo, poich devono aver e la stessa signatur e, e cr ear e due metodi che or dinino l'ar r ay sar ebbe scomodo: qui che entr a in gioco l'inter faccia ICompar er . Essa r appr esenta un oggetto che deve eseguir e la compar azione tr a due altr i oggetti, facendo quindi da tramite nell'or dinamento. Dato che Sor t accetta in una delle sue ver sioni un oggetto ICompar er , possibile or dinar e una lista di elementi con qualsiasi cr iter io si voglia semplicemente cambiando il par ametr o. Ad esempio, in questo sor gente scr ivo una classe Bir thDayCompar er che per mette di or dinar e oggetti Per son in base all'anno di nascita: 01. Module Module2 02. 'Questa classe fornisce un metodo per comparare oggetti Person 03. 'utilizzando la propriet BirthDay. 04. 'Per convenzione, classi che implementano IComparer dovrebbero 05. 'avere un suffisso "Comparer" nel nome. 06. 'Altra osservazione: se ci sono molte interfacce il cui nome 07. 'termina in "-able", definendo una caratteristica dell'oggetto 08. 'che le implementa (ad es.: un oggetto enumerabile, 09. 'comparabile, distruggibile, ecc...), ce ne sono altrettante 10. 'che terminano in "-er", indicando, invece, un oggetto 11. 'che "fa" qualcosa di specifico. 12. 'Nel nostro esempio, oggetti di tipo BirthDayComparer 13. 'hanno il solo scopo di comparare altre oggetti 14. Class BirthDayComparer 15. 'Implementa l'interfaccia 16. Implements IComparer 17. 18.
19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. End
'Anche questa funzione deve usare parametri object Public Function Compare(ByVal x As Object, ByVal y As Object) _ As Integer Implements System.Collections.IComparer.Compare 'Se entrambi gli oggetti sono Nothing, allora sono 'uguali If x Is Nothing And y Is Nothing Then Return 0 ElseIf x Is Nothing Then 'Se x Nothing, y maggiore Return -1 ElseIf y Is Nothing Then 'Se y Nothing, x maggiore Return 1 Else Dim P1 As Person = DirectCast(x, Person) Dim P2 As Person = DirectCast(y, Person) 'Compara le date Return Date.Compare(P1.BirthDay, P2.BirthDay) End If End Function End Class Sub Main() Dim Persons() As Person = _ {New Person("Marcello", "Rossi", Date.Parse("10/10/1992")), _ New Person("Guido", "Bianchi", Date.Parse("01/12/1980")), _ New Person("Bianca", "Brega", Date.Parse("23/06/1960")), _ New Person("Antonio", "Felice", Date.Parse("16/01/1930"))} 'Ordina gli elementi utilizzando il nuovo oggetto 'inizializato in linea BirthDayComparer Array.Sort(Persons, New BirthDayComparer()) For I As Int16 = 0 To UBound(Persons) Console.WriteLine(Persons(I).CompleteName) Next Console.ReadKey() End Sub Module
Usando questo meccanismo possibile or dinar e qualsiasi tipo di lista o collezione fin'or a analizzata (tr anne Sor tedList, che si or dina automaticamente), in modo semplice e veloce, par ticolar mente utile nell'ambito delle liste visuali a colonne, come vedr emo nei capitoli sulle ListView .
IDisposable
Nel capitolo sui distr uttor i si visto come sia possibile utilizzar e il costr utto Using per gestir e un oggetto e poi distr ugger lo in poche r ighe di codice. Ogni classe che espone il metodo Dispose deve obbligator iamente implementar e anche l'inter faccia IDisposable, la quale comunica implicitamente che essa ha questa car atter istica. Dato che gi molti esempi sono stati fatti sull'ar gomento distr uttor i, eviter di tr attar e nuovamente Dispose in questo capitolo.
56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. End
'l'IEnumerator predefinito per un ArrayList Public Function GetEnumerator() As IEnumerator _ Implements IEnumerable.GetEnumerator Return _Persons.GetEnumerator End Function End Class Sub Main() Dim Persons As New PersonCollection With Persons.Persons .Add(New Person("Marcello", "Rossi", Date.Parse("10/10/1992"))) .Add(New Person("Guido", "Bianchi", Date.Parse("01/12/1980"))) .Add(New Person("Bianca", "Brega", Date.Parse("23/06/1960"))) .Add(New Person("Antonio", "Felice", Date.Parse("16/01/1930"))) End With For Each P As Person In Persons Console.WriteLine(P.CompleteName) Next Console.WriteLine("Et media: " & Persons.AverageAge) '> 41 anni e 253 giorni Console.ReadKey() End Sub Module
Come si vede dall'esempio, lecito usar e Per sonCollection nel costr utto For Each: l'iter azione viene svolta dal pr imo elemento inser ito all'ultimo, poich l'IEnumer ator dell'Ar r ayList oper a in questo modo. Tuttavia, cr eando una diver sa classe che implementa IEnumer ator si pu scor r er e la collezione in qualsiasi modo: dal pi giovane al pi vecchio, al pr imo all'ultimo, dall'ultimo al pr imo, a caso, saltandone alcuni, a seconda dell'or a di cr eazione ecceter a. Quindi in questo modo si pu per sonalizzar e la pr opr ia collezione. Ci che occor r e per costr uir e cor r ettamente una classe basata su IEnumer ator sono tr e metodi fondamentali definiti nell'inter faccia: MoveNex t una funzione che r estituisce Tr ue se esiste un elemento successivo nella collezione (e in questo caso lo imposta come elemento cor r ente), altr imenti False; Cur r ent una pr opr iet ReadOnly di tipo Object che r estituisce l'elemento cor r ente; Reset una pr ocedur a senza par ametr i che r esetta il contator e e fa iniziar e il ciclo daccapo. Quest'ultimo metodo non viene mai utilizzato, ma nell'esempio che segue ne scr iver comunque il cor po: 001. Module Module1 002. Class PersonCollection 003. Implements IEnumerable 004. 'La lista delle persone 005. Private _Persons As New ArrayList 006. 007. 'Questa classe ha il compito di scorrere ordinatamente gli 008. 'elementi della lista, dal pi vecchio al pi giovane 009. Private Class PersonAgeEnumerator 010. Implements IEnumerator 011. 012. 'Per enumerare gli elementi, la classe ha bisogno di un 013. 'riferimento ad essi: perci si deve dichiarare ancora 014. 'un nuovo ArrayList di Person. Questo passaggio 015. 'facoltativo nelle classi nidificate come questa, ma 016. 'obbligatorio in tutti gli altri casi 017. Private Persons As New ArrayList 018. 'Per scorrere la collezione, si user un comune indice 019. Private Index As Int32 020. 021. 'Essendo una normalissima classe, lecito definire un 022. 'costruttore, che in questo caso inizializza la 023. 'collezione Sub New(ByVal Persons As ArrayList) 024. 'Ricordate: poich ArrayList deriva da Object, 025. 026. 'un tipo reference. Assegnare Persons a Me.Persons 'equivale ad assegnarne l'indirizzo e quindi ogni 027. 'modifica su questo arraylist privato si rifletter 028. 'su quello passato come parametro. Si pu 029. 'evitare questo problema clonando la lista 030. 031.
032. 033. 034. 035. 036. 037. 038. 039. 040. 041. 042. 043. 044. 045. 046. 047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099. 100. 101. 102. 103.
Me.Persons = Persons.Clone 'MoveNext viene richiamato prima di usare Current, 'quindi Index verr incrementata subito. 'Per farla diventare 0 al primo ciclo la si 'deve impostare a -1 Index = -1 'Dato che l'enumeratore deve scorrere la lista 'secondo l'anno di nascita, bisogna prima ordinarla Me.Persons.Sort(New BirthDayComparer) End Sub 'Restituisce l'elemento corrente Public ReadOnly Property Current() As Object _ Implements System.Collections.IEnumerator.Current Get Return Persons(Index) End Get End Property 'Restituisce True se esiste l'elemento successivo e lo 'imposta, altrimenti False Public Function MoveNext() As Boolean _ Implements System.Collections.IEnumerator.MoveNext If Index = Persons.Count - 1 Then Return False Else Index += 1 Return True End If End Function 'Resetta il ciclo Public Sub Reset() _ Implements System.Collections.IEnumerator.Reset Index = -1 End Sub End Class Public ReadOnly Property Persons() As ArrayList Get Return _Persons End Get End Property Public ReadOnly Property AverageAge() As String Get Dim Temp As TimeSpan For Each P As Person In _Persons Temp = Temp.Add(Date.Now - P.BirthDay) Next Temp = TimeSpan.FromSeconds(Temp.TotalSeconds / _Persons.Count) Dim Years As Int32 Years = Temp.TotalDays \ 365 Temp = _ Temp.Subtract(TimeSpan.FromDays((Temp.TotalDays \ 365) * 365)) Return Years & " anni e " & CInt(Temp.TotalDays) & " giorni" End Get End Property 'La funzione GetEnumerator restituisce ora un oggetto di 'tipo IEnumerator che abbiamo definito in una classe 'nidificata e il ciclo For Each scorrer quindi 'dal pi vecchio al pi giovane Public Function GetEnumerator() As IEnumerator _ Implements IEnumerable.GetEnumerator Return New PersonAgeEnumerator(_Persons) End Function End Class Sub Main()
Dim Persons As New PersonCollection 104. With Persons.Persons 105. .Add(New Person("Marcello", "Rossi", Date.Parse("10/10/1992"))) 106. .Add(New Person("Guido", "Bianchi", Date.Parse("01/12/1980"))) 107. .Add(New Person("Bianca", "Brega", Date.Parse("23/06/1960"))) 108. .Add(New Person("Antonio", "Felice", Date.Parse("16/01/1930"))) 109. End With 110. 111. 'Enumera ora per data di nascita, ma senza modificare 112. 'l'ordine degli elementi 113. For Each P As Person In Persons 114. Console.WriteLine(P.BirthDay.ToShortDateString & ", " & _ 115. P.CompleteName) 116. Next 117. 118. 'Stampa la prima persona, dimostrando che l'ordine 119. 'della lista intatto 120. Console.WriteLine(Persons.Persons(0).CompleteName) 121. Console.ReadKey() 122. 123. End Sub 124. End Module
ICloneable
Come si visto nell'esempio appena scr itto, si pr esentano alcune difficolt nel manipolar e oggetti di tipo Refer ence, in quanto l'assegnazione di questi cr eer ebbe due istanze che puntano allo stesso oggetto piuttosto che due oggetti distinti. in questo tipo di casi che il metodo Clone e l'inter faccia ICloneable assumono un gr an valor e. Il pr imo per mette di eseguir e una copia dell'oggetto, cr eando un nuo v o og g etto a tutti gli effetti, totalmente disgiunto da quello di par tenza: questo per mette di non intaccar ne accidentalmente l'integr it. Una dimostr azione: 01. Module Esempio 02. Sub Main() 03. 'Il tipo ArrayList espone il metodo Clone 04. Dim S1 As New ArrayList Dim S2 As New ArrayList 05. 06. 07. S2 = S1 08. 09. 'Verifica che S1 e S2 puntano lo stesso oggetto Console.WriteLine(S1 Is S2) 10. 11. '> True 12. 13. 'Clona l'oggetto 14. S2 = S1.Clone 15. 'Verifica che ora S2 referenzia un oggetto differente, 16. 'ma di valore identico a S1 17. Console.WriteLine(S1 Is S2) 18. '> False 19. 20. Console.ReadKey() 21. End Sub 22. End Module L'inter faccia, invece, come accadeva per IEnumer able e ICompar able, indica al .NET Fr amew or k che l'oggetto clonabile. Questo codice mostr a la funzione Close all'oper a: 01. Module Module1 02. Class UnOggetto 03. Implements ICloneable 04. Private _Campo As Int32 05. 06. Public Property Campo() As Int32 07. Get 08. Return _Campo 09. End Get 10.
11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. End
Set(ByVal Value As Int32) _Campo = Value End Set End Property 'Restituisce una copia dell'oggetto Public Function Clone() As Object Implements ICloneable.Clone 'La funzione Protected MemberwiseClone, ereditata da 'Object, esegue una copia superficiale dell'oggetto, 'come spiegher fra poco: quello che 'serve in questo caso Return Me.MemberwiseClone End Function 'L'operatore = permette di definire de due oggetti hanno un 'valore uguale Shared Operator =(ByVal O1 As UnOggetto, ByVal O2 As UnOggetto) As _ Boolean Return O1.Campo = O2.Campo End Operator Shared Operator <>(ByVal O1 As UnOggetto, ByVal O2 As UnOggetto) As _ Boolean Return Not (O1 = O2) End Operator End Class Sub Main() Dim O1 As New UnOggetto Dim O2 As UnOggetto = O1.Clone 'I due oggetti NON sono lo stesso oggetto: il secondo ' solo una copia, disgiunta da O1 Console.WriteLine(O1 Is O2) '> False 'Tuttavia hanno lo stesso identico valore Console.WriteLine(O1 = O2) '> True Console.ReadKey() End Sub Module
Or a, impor tante distinguer e due tipi di copia: quella Shallo w e quella Deep. La pr ima cr ea una copia super ficiale dell'oggetto, ossia si limita a clonar e tutti i campi. La seconda, invece, in gr ado di eseguir e questa oper azione anche su tutti gli oggetti inter ni e i r ifer imenti ad altr i oggetti: cos, se si ha una classe Per son che al pr opr io inter no contiene il campo Childer n, di tipo ar r ay di Per son, la copia Shallow cr eer un clone della classe in cui Childr en punta sempr e allo stesso oggetto, mentr e una copia Deep cloner anche Childr en. Si nota meglio con un gr afico: le fr ecce ver di indicano oggetti clonati, mentr e la fr eccia ar ancio si r ifer isce allo stesso oggetto.
Non possibile specificar e nella dichiar azione di Clone quale tipo di copia ver r eseguita, quindi tutto viene lasciato all'ar bitr io del pr ogr ammator e. Dal codice sopr a scr itto, si nota che Clone deve r estituir e per for za un tipo Object. In questo caso, il metodo si dice a tipizzazio ne debo le, ossia ser ve un oper ator e di cast per conver tir lo nel tipo desider ato; per cr ear ne una ver sione a tipizzazio ne fo r te necessar io scr iver e una funzione che r estituisca, ad esempio, un tipo Per son. Quest'ultima ver sione avr il nome Clone, mentr e quella che implementa ICloneable.Clone() avr un nome differ ente, come CloneMe().
033. 034. 035. 036. 037. 038. 039. 040. 041. 042. 043. 044. 045. 046. 047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099. 100. 101. 102. 103. 104.
If (Index >= 0) And (Index < 3) Then Return _Dimensions(Index) Else Throw New IndexOutOfRangeException() End If End Get Set(ByVal value As Single) If (Index >= 0) And (Index < 3) Then _Dimensions(Index) = value Else Throw New IndexOutOfRangeException() End If End Set End Property Public Sub New(ByVal Id As Int32) _Id = Id End Sub Public Overrides Function ToString() As String Implements IIdentifiable.ToString Return String.Format("{0:0000}: Pacco {1}x{2}x{3}, Destinazione: {4}", _ Me.Id, Me.Dimensions(0), Me.Dimensions(1), _ Me.Dimensions(2), Me.Destination) End Function End Class Public Class Telegram Implements IIdentifiable Private _Id As Int32 Private _Recipient As String Private _Message As String Public ReadOnly Property Id() As Integer Implements IIdentifiable.Id Get Return _Id End Get End Property Public Property Recipient() As String Get Return _Recipient End Get Set(ByVal value As String) _Recipient = value End Set End Property Public Property Message() As String Get Return _Message End Get Set(ByVal value As String) _Message = value End Set End Property Public Sub New(ByVal Id As Int32) _Id = Id End Sub Public Overrides Function ToString() As String Implements IIdentifiable.ToString Return String.Format("{0:0000}: Telegramma per {1} ; Messaggio = {2}", _ Me.Id, Me.Recipient, Me.Message) End Function End Class Public Class MoneyOrder Implements IIdentifiable Private _Id As Int32 Private _Recipient As String
105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121. 122. 123. 124. 125. 126. 127. 128. 129. 130. 131. 132. 133. 134. 135. 136. 137. 138. 139. 140. 141. 142. 143. 144. 145. 146. 147. 148. 149. 150. 151. 152. 153. 154. 155. 156. 157. 158. 159. 160. 161. 162. 163. 164. 165. 166. 167. 168. 169. 170. 171. 172. 173. 174. 175. 176.
Private _Money As Single Public ReadOnly Property Id() As Integer Implements IIdentifiable.Id Get Return _Id End Get End Property Public Property Recipient() As String Get Return _Recipient End Get Set(ByVal value As String) _Recipient = value End Set End Property Public Property Money() As Single Get Return _Money End Get Set(ByVal value As Single) _Money = value End Set End Property Public Sub New(ByVal Id As Int32) _Id = Id End Sub Public Overrides Function ToString() As String Implements IIdentifiable.ToString Return String.Format("{0:0000}: Vaglia postale per {1} ; Ammontare = {2}", _ Me.Id, Me.Recipient, Me.Money) End Function End Class Public Class PostalProcessor Public Delegate Function IdSelector(ByVal Id As Int32) As Boolean Private _StorageCapacity As Int32 Private _NextId As Int32 = 0 Private Storage() As IIdentifiable Public Property StorageCapacity() As Int32 Get Return _StorageCapacity End Get Set(ByVal value As Int32) _StorageCapacity = value ReDim Preserve Storage(value) End Set End Property Public Property Item(ByVal Index As Int32) As IIdentifiable Get If (Index >= 0) And (Index < Storage.Length) Then Return Me.Storage(Index) Else Throw New IndexOutOfRangeException() End If End Get Set(ByVal value As IIdentifiable) If (Index >= 0) And (Index < Storage.Length) Then Me.Storage(Index) = value Else Throw New IndexOutOfRangeException() End If End Set End Property Public ReadOnly Property FirstPlaceAvailable() As Int32 Get
177. 178. 179. 180. 181. 182. 183. 184. 185. 186. 187. 188. 189. 190. 191. 192. 193. 194. 195. 196. 197. 198. 199. 200. 201. 202. 203. 204. 205. 206. 207. 208. 209. 210. 211. 212. 213. 214. 215. 216. 217. 218. 219. 220. 221. 222. 223. 224. 225. 226. 227. 228. 229. 230. 231. 232. 233. 234. 235. 236. 237. 238. 239. 240. 241. 242. 243. 244. 245. 246. 247. 248.
For I As Int32 = 0 To Me.Storage.Length - 1 If Me.Storage(I) Is Nothing Then Return I End If Next Return (-1) End Get End Property Public ReadOnly Property NextId() As Int32 Get _NextId += 1 Return _NextId End Get End Property Public Sub New(ByVal Items() As IIdentifiable) Me.Storage = Items _StorageCapacity = Items.Length End Sub Public Sub New(ByVal Capacity As Int32) Me.StorageCapacity = Capacity End Sub Public Sub PrintByFilter(ByVal Selector As IdSelector) For Each K As IIdentifiable In Storage If K Is Nothing Then Continue For End If If Selector.Invoke(K.Id) Then Console.WriteLine(K.ToString()) End If Next End Sub Public Sub PrintById(ByVal Id As Int32) For Each K As IIdentifiable In Storage If K Is Nothing Then Continue For End If If K.Id = Id Then Console.WriteLine(K.ToString()) Exit For End If Next End Sub Public Function SearchItems(ByVal Str As String) As Int32() Dim Temp As New ArrayList For Each K As IIdentifiable In Storage If K Is Nothing Then Continue For End If If K.ToString().Contains(Str) Then Temp.Add(K.Id) End If Next Dim Result(Temp.Count - 1) As Int32 For I As Int32 = 0 To Temp.Count - 1 Result(I) = Temp(I) Next Temp.Clear() Temp = Nothing Return Result End Function End Class
249. End Namespace Notate che ho r acchiuso tutto in un namespace e ho anche messo lo scope Public a tutti i membr i non pr ivati. Se non avessi messo Public, infatti, i membr i senza scope sar ebber o stati automaticamente mar cati con Fr iend. Suppongo vi r icor diate che Fr iend r ende accessibile un membr o solo dalle classi appar tenenti allo stesso assembly (in questo caso, allo stesso pr ogetto): questo equivale a dir e che tutti i membr i Fr iend non sar anno accessibili al di fuor i della libr er ia e quindi chi la user non potr acceder vi. Ovviamente, dato che il tutto si basa sull'inter faccia IIdentifiable non potevo pr ecluder ne l'accesso agli utenti della libr er ia, e allo stesso modo i costr uttor i senza Public sar ebber o stati inaccessibili e non si sar ebbe potuto istanziar e alcun oggetto. Ecco che concludiamo la lista di tutti gli specificator i di accesso con Pr otected Fr iend: un membr o dichiar ato Pr otected Fr iend sar accessibile solo ai membr i delle classi der ivate appar tenenti allo stesso assembly. Per r icapitolar vi tutti gli scope, ecco uno schema dove le fr ecce ver di indicano gli unici accessi consentiti:
Quindi r ecatevi fino alla car tella della libr er ia cr eata, selezionate il file e pr emete OK (nell'esempio c' una delle libr er ie che ho scr itto e che potete tr ovar e nella sezione Dow nload):
or a il r ifer imento stato aggiunto al pr ogetto, ma non potete ancor a usar e le classi della libr er ia. Pr ima dovete "dir e" al compilator e che nel codice che sta per esser e letto potr este far e r ifer imento ad esse. Questo si fa "impor tando" il namespace, con il codice: 1. Imports [Nome Libreria].[Nome Namespace] Io ho chiamato la libr er ia con lo stesso nome del namespace, ma potete usar e anche nomi diver si, poich in una libr er ia ci possono esser e tanti namespace differ enti: 1. Imports PostalManagement.PostalManagement Impor ts una "dir ettiva", ossia non costituisce codice eseguibile, ma infor ma il compilator e che alcune classi del sor gente potr ebber o appar tener e a questo namespace (omettendo questa r iga, dovr ete scr iver e ogni volta PostalManagement.Pack, ad esempio, per usar e la classe Pack, per ch altr imenti il compilator e non sar ebbe in gr ado di
tr ovar e il name Pack nel contesto cor r ente). Ecco un esempio: 01. Imports PostalManagement.PostalManagement 02. 03. Module Module1 04. Sub Main() 05. 06. Dim P As New PostalProcessor(10) 07. Dim Pk As New Pack(P.NextId) 08. P.Item(P.FirstPlaceAvailable) = Pk 09. '... 10. 11. End Sub 12. 13. End Module che equivale a: 01. Module Module1 02. 03. Sub Main() 04. Dim P As New PostalManagement.PostalManagement.PostalProcessor(10) 05. Dim Pk As New PostalManagement.PostalManagement.Pack(P.NextId) 06. 07. P.Item(P.FirstPlaceAvailable) = Pk 08. '... 09. End Sub 10. 11. End Module Nella scheda ".NET" che vedete nella seconda immagine di sopr a, ci sono molte libr er ie facenti par te del Fr amew or k che user emo nelle pr ossime sezioni della guida.
Infatti, se l'applicazione dovesse er r oneamente inser ir e una str inga al posto di un numer o inter o, non ver r ebbe gener ato nessun er r or e, ma si ver ificher ebbe un'eccezione successivamente. Altr a pr oblematica legata all'uso di collezioni a tipizzazione debole (ossia che r egistr ano gener ici oggetti Object, come l'Ar r ayList, l'HashTable o la Sor tedList) dovuta al fatto che sia necessar ia una conver sione esplicita di tipo nell'uso dei suoi elementi, almeno nella maggior anza dei casi. La soluzione adottata da un pr ogr ammator e che non conoscesse i gener ics per r isolver e tali inconvenienti sar ebbe quella di cr ear e una nuova lista, ex novo, er editandola da un tipo base come CollectionBase e r idefinendone tutti i metodi (Add, Remove, Index Of ecc...). L'uso dei Gener ics, invece, r ende molto pi veloce e meno insidiosa la scr ittur a di un codice r obusto e solido nell'ambito non solo delle collezioni, ma di molti altr i ar gomenti. Ecco un esempio di come implementar e una soluzione basata sui Gener ics: 01. 02. 03. 04. 05. 'La lista accetta solo oggetti di tipo Int32: per questo motivo 'si genera un'eccezione quando si tenta di inserirvi elementi di 'tipo diverso e la velocit di elaborazione aumenta! Dim A As New List(Of Int32)
A.Add(1) A.Add(4) A.Add(8) 'A.Add("C") '<- Impossibile For Each V As Int32 In A Console.WriteLine(V) Next
E questa una dimostr azione dell'incr emento delle pr estazioni: 01. Module Module1 02. Sub Main() 03. Dim TipDebole As New ArrayList 04. Dim TipForte As New List(Of Int32) 05. Dim S As New Stopwatch 06. 07. 'Cronometra le operazioni su ArrayList 08. S.Start() 09. For I As Int32 = 1 To 1000000 10. TipDebole.Add(I) 11. Next 12. S.Stop() 13. Console.WriteLine(S.ElapsedMilliseconds & _ 14. " millisecondi per ArrayList!") 15. 16. 'Cronometra le operazioni su List 17. S.Reset() 18. S.Start() For I As Int32 = 1 To 1000000 19. TipForte.Add(I) 20. 21. Next S.Stop() 22. Console.WriteLine(S.ElapsedMilliseconds & _ 23. " millisecondi per List(Of T)!") 24. 25. Console.ReadKey() 26. End Sub 27. 28. End Module Sul mio computer por tatile l'Ar r ayList impiega 197ms, mentr e List 33ms: i Gener ics incr ementano la velocit di 6 volte! Oltr e a List, esistono anche altr e collezioni gener ic, ossia Dictionar y e Sor tedDictionar y: tutti questi sono la ver sione a tipizzazione for te delle nor mali collezioni gi viste. Ma or a vediamo come scr iver e nuove classi e metodi gener ic.
Generic s Standard
Una volta impar ato a dichiar ar e e scr iver e entit gener ics, sar anche altr ettanto semplice usar e quelli esistenti, per ci iniziamo col dar e le pr ime infor mazioni su come scr iver e, ad esempio, una classe gener ics. Una classe gener ics si r ifer isce ad un qualsiasi tipo T che non possiamo conoscer e al momento dela scr ittur a del codice, ma che il pr ogr ammator e specificher all'atto di dichiar azione di un oggetto r appr esentato da questa classe. Il fatto che essa sia di tipo gener ico indica che anche i suoi membr i, molto pr obabilmente, avr anno lo stesso tipo: pi nello specifico, potr ebber o esser ci campi di tipo T e metodi che lavor ano su oggetti di tipo T. Se nessuna di queste due condizioni ver ificata, allor a non ha senso scr iver e una classe gener ics. Ma iniziamo col veder e un semplice esempio: 001. Module Module1 002. 'Collezione generica che contiene un qualsiasi tipo T di 003. 'oggetto. T si dice "tipo generic aperto" 004. Class Collection(Of T) 005. 'Per ora limitiamoci a dichiarare un array interno 006. 'alla classe. 007. 'Vedremo in seguito che possibile ereditare da 008. 'una collezione generics gi esistente. 009. 'Notate che la variabile di tipo T: una volta che 010. 'abbiamo dichiarato la classe come generics su un tipo T, 011.
012. 013. 014. 015. 016. 017. 018. 019. 020. 021. 022. 023. 024. 025. 026. 027. 028. 029. 030. 031. 032. 033. 034. 035. 036. 037. 038. 039. 040. 041. 042. 043. 044. 045. 046. 047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083.
' come se avessimo "dichiarato" l'esistenza di T 'come tipo fittizio. Private _Values() As T 'Restituisce l'Index-esimo elemento di Values (anch'esso ' di tipo T) Public Property Values(ByVal Index As Int32) As T Get If (Index >= 0) And (Index < _Values.Length) Then Return _Values(Index) Else Throw New IndexOutOfRangeException() End If End Get Set(ByVal value As T) If (Index >= 0) And (Index < _Values.Length) Then _Values(Index) = value Else Throw New IndexOutOfRangeException() End If End Set End Property 'Propriet che restituiscono il primo e l'ultimo 'elemento della collezione Public ReadOnly Property First() As T Get Return _Values(0) End Get End Property Public ReadOnly Property Last() As T Get Return _Values(_Values.Length - 1) End Get End Property 'Stampa tutti i valori presenti nella collezione a schermo. 'Su un tipo generic sempre possibile usare 'l'operatore Is (ed il suo corrispettivo IsNot) e 'confrontarlo con Nothing. Se si tratta di un tipo value 'l'uguaglianza con Nothing sar sempre falsa. Public Sub PrintAll() For Each V As T In _Values If V IsNot Nothing Then Console.WriteLine(V.ToString()) End If Next End Sub 'Inizializza la collezione con Count elementi, tutti del 'valore DefaultValue Sub New(ByVal Count As Int32, ByVal DefaultValue As T) If Count < 1 Then Throw New ArgumentOutOfRangeException() End If ReDim _Values(Count - 1) For I As Int32 = 0 To _Values.Length - 1 _Values(I) = DefaultValue Next End Sub End Class Sub Main() 'Dichiara quattro variabili contenenti quattro nuovi 'oggetti Collection. Ognuno di questi, per, ' specifico per un solo tipo che decidiamo 'noi durante la dichiarazione. String, Int32, Date 'e Person, ossia i tipi che stiamo inserendo nel tipo
'generico T, si dicono "tipi generic collegati", 084. 'poich collegano il tipo fittizio T con un 085. 'reale tipo esistente 086. Dim Strings As New Collection(Of String)(10, "null") 087. Dim Integers As New Collection(Of Int32)(5, 12) 088. Dim Dates As New Collection(Of Date)(7, Date.Now) 089. Dim Persons As New Collection(Of Person)(10, Nothing) 090. 091. Strings.Values(0) = "primo" 092. Integers.Values(3) = 45 093. Dates.Values(6) = New Date(2009, 1, 1) 094. Persons.Values(3) = New Person("Mario", "Rossi", Dates.Last) 095. 096. Strings.PrintAll() 097. Integers.PrintAll() 098. Dates.PrintAll() 099. Persons.PrintAll() 100. 101. Console.ReadKey() 102. End Sub 103. 104. End Module Ognuna della quattr o var iabili del sor gente contiene un oggetto di tipo Collection, ma tali oggetti non sono dello stesso tipo, poich ognuno espone un differ ente tipo gener ics collegato. Quindi, nonostante si tr atti sempr e della stessa classe Collection, Collection(Of Int32) e Collection(Of Str ing) sono a tutti gli effetti due tipi diver si: come se esistesser o due classi in cui T sostituito in una da Int32 e nell'altr a da Str ing. Per dimostr ar e la lor o diver sit, basta scr iver e: 1. Console.WriteLine(Strings.GetType() Is Integers.GetType()) 2. 'Output : False
Ecco un semplice esempio: 01. Module Module1 02. 03. 'Scambia i valori di due variabili, passate 04. 'per indirizzo 05. Public Sub Swap(Of T)(ByRef Arg1 As T, ByRef Arg2 As T) 06. Dim Temp As T = Arg1 07. Arg1 = Arg2 08. Arg2 = Temp 09. End Sub 10. 11. Sub Main() 12. Dim X, Y As Double 13. Dim Z As Single 14. Dim A, B As String 15. 16. X = 90.0 17.
Y = 67.58 18. Z = 23.01 19. A = "Ciao" 20. B = "Mondo" 21. 22. 'Nelle prossime chiamate, Swap non presenta un 23. 'tipo generics collegato: il tipo viene dedotto dai 24. 'tipi degli argomenti 25. 26. 'X e Y sono Double, quindi richiama il metodo con 27. 'T = Double 28. Swap(X, Y) 29. 'A e B sono String, quindi richiama il metodo con 30. 'T = String 31. Swap(A, B) 32. 33. 'Qui viene generato un errore: nonostante Z sia 34. 'convertibile in Double implicitamente senza perdita 35. 'di dati, il suo tipo non corrisponde a quello di X, 36. 'dato che c' un solo T, che pu assumere 37. 'un solo valore-tipo. Per questo necessario 38. 'utilizzare una scappatoia 39. 'Swap(Z, X) 40. 'Soluzione 1: si esplicita il tipo generic collegato 41. Swap(Of Double)(Z, X) 42. 'Soluzione 2: si converte Z in double esplicitamente 43. Swap(CDbl(Z), X) 44. 45. Console.ReadKey() 46. End Sub 47. 48. End Module
Generic s multipli
Quando, anzich un solo tipo gener ics, se ne specificano due o pi, si par la di genr ics multipli. La dichiar azione avviene allo stesso modo di come abbiamo visto pr ecedentemente e i tipi vengono separ ati da una vir gola: 01. Module Module2 02. 'Una relazione qualsiasi fra due oggetti di tipo indeterminato 03. Public Class Relation(Of T1, T2) 04. Private Obj1 As T1 Private Obj2 As T2 05. 06. 07. Public ReadOnly Property FirstObject() As T1 08. Get 09. Return Obj1 10. End Get 11. End Property 12. 13. Public ReadOnly Property SecondObject() As T2 14. Get 15. Return Obj2 16. End Get 17. End Property 18. 19. Sub New(ByVal Obj1 As T1, ByVal Obj2 As T2) 20. Me.Obj1 = Obj1 21. Me.Obj2 = Obj2 End Sub 22. End Class 23. 24. Sub Main() 25. 'Crea una relazione fra uno studente e un insegnante, 26. 'utilizzando le classi create nei capitoli precedenti 27. Dim R As Relation(Of Student, Teacher) 28. Dim S As New Student("Pinco", "Pallino", Date.Parse("25/06/1990"), _ 29. "Liceo Scientifico N. Copernico", 4) 30. Dim T As New Teacher("Mario", "Rossi", Date.Parse("01/07/1950"), _ 31. 32.
"Matematica") 33. 34. 'Crea una nuova relazione tra lo studente e l'insegnante 35. R = New Relation(Of Student, Teacher)(S, T) 36. Console.WriteLine(R.FirstObject.CompleteName) 37. Console.WriteLine(R.SecondObject.CompleteName) 38. 39. Console.ReadKey() 40. End Sub 41. End Module Notate che anche possibile cr ear e una r elazione tr a due r elazioni (e la cosa diventa complicata): 01. Dim S As New Student("Pinco", "Pallino", Date.Parse("25/06/1990"), "Liceo Scientifico N. Copernico", 4) 02. Dim T As New Teacher("Mario", "Rossi", Date.Parse("01/07/1950"), "Matematica") 03. Dim StudentTeacherRelation As Relation(Of Student, Teacher) 04. Dim StudentClassRelation As Relation(Of Student, String) 05. Dim Relations As Relation(Of Relation(Of Student, Teacher), Relation(Of Student, String)) 06. 07. StudentTeacherRelation = New Relation(Of Student, Teacher)(S, T) 08. StudentClassRelation = New Relation(Of Student, String)(S, "5A") 09. Relations = New Relation(Of Relation(Of Student, Teacher), Relation(Of Student, String)) (StudentTeacherRelation, StudentClassRelation) 10. 11. 'Relations.FirstObject.FirstObject 12. ' > Student "Pinco Pallino" 13. 'Relations.FirstObject.SecondObject 14. ' > Teacher "Mario Rossi" 15. 'Relations.SecondObject.FirstObject 16. ' > Student "Pinco Pallino" 17. 'Relations.SecondObject.SecondObject 18. ' > String "5A"
End Class Entit con lo stesso nome ma con gener ics aper ti differ enti sono consider ate in over load. Per tanto, lecito scr iver e: 1. 2. 3. 4. 5. 6. 7. Sub Example(Of T)(ByVal A As T) '... End Sub Sub Example(Of T1, T2)(ByVal A As T1) '... End Sub
I V inc oli
I tipi gener ics sono molto utili, ma spesso sono un po' tr oppo... "gener ici" XD Faccio un esempio. Ammettiamo di aver e un metodo gener ics (Of T) che accetta due par ametr i A e B. Pr oviamo a scr iver e: 1. If A = B Then '... L'IDE ci comunica subito un er r or e: "Oper ator '=' is not definited for type 'T' and 'T'." In effetti, poich T pu esser e un
qualsiasi tipo, non possiamo neanche saper e se questo tipo implementi l'oper ator e uguale =. In questo caso, vogliamo impor r e come condizione, ossia come v inco lo , che, per usar e il metodo in questione, il tipo gener ic collegato debba obbligator iamente espor r e un modo per saper e se due oggetti di quel tipo sono uguali. Come si r ende in codice? Se fate mente locale sulle inter facce, r icor der ete che una classe r appr esenta un concetto con deter minate car atter istiche se implementa deter minate inter facce. Dovr emo, quindi, tr ovar e un'inter faccia che r appr esenta l'"eguagliabilit": l'inter faccia in questione IEquatable(Of T). Per poter saper e se due oggetti T sono uguali, quindi, T dovr esser e un qualsiasi tipo che implementa IEquatable(Of T). Ecco che dobbiamo impor r e un vincolo al tipo. Esistono cinque categor ie di vincoli: Vincolo di inter faccia; Vincolo di er editar iet; Vincolo di classe; Vincolo di str uttur a; Vincolo New . Iniziamo con l'analizzar e il pr imo di cui abbiamo par lato.
035. 036. 037. 038. 039. 040. 041. 042. 043. 044. 045. 046. 047. 048. 049. 050. 051. 052. 053. 054. 055. 056. 057. 058. 059. 060. 061. 062. 063. 064. 065. 066. 067. 068. 069. 070. 071. 072. 073. 074. 075. 076. 077. 078. 079. 080. 081. 082. 083. 084. 085. 086. 087. 088. 089. 090. 091. 092. 093. 094. 095. 096. 097. 098. 099. 100. 101. 102. 103. 104. 105.
If Me.Count > 0 Then Dim Result As T = Me(0) For Each Element As T In Me 'Ricordate che A.CompareTo(B) restituisce '1 se A > B If Element.CompareTo(Result) = 1 Then Result = Element End If Next Else Return Result
Return Nothing End If End Get End Property 'Trova il minimo elemento Public ReadOnly Property Min() As T Get If Me.Count > 0 Then Dim Result As T = Me(0) For Each Element As T In Me If Element.CompareTo(Result) = -1 Then Result = Element End If Next Else Return Result
Return Nothing End If End Get End Property 'Trova tutti gli elementi uguali ad A e ne restituisce 'gli indici Public Function FindEquals(ByVal A As T) As Int32() Dim Result As New List(Of Int32) For I As Int32 = 0 To Me.Count - 1 If Me(I).CompareTo(A) = 0 Then Result.Add(I) End If Next 'Converte la lista di interi in un array di interi 'con gli stessi elementi Return Result.ToArray() End Function End Class Sub Main() 'Tre collezioni, una di interi, una di stringhe e 'una di date Dim A As New ComparableCollection(Of Int32) Dim B As New ComparableCollection(Of String) Dim C As New ComparableCollection(Of Date) A.AddRange(New Int32() {4, 19, 6, 90, 57, 46, 4, 56, 4}) B.AddRange(New String() {"acca", "casa", "zen", "rullo", "casa"}) C.AddRange(New Date() {New Date(2008, 1, 1), New Date(1999, 12, 31), New Date(2100, 4, 12)}) Console.WriteLine(A.Min()) ' > 4 Console.WriteLine(A.Max()) ' > 90 Console.WriteLine(B.Min())
' > acca 106. Console.WriteLine(B.Max()) 107. ' > zen 108. Console.WriteLine(C.Min().ToShortDateString) 109. ' > 31/12/1999 110. Console.WriteLine(C.Max().ToShortDateString) 111. ' > 12/4/2100 112. 113. 'Trova la posizione degli elementi uguali a 4 114. Dim AEqs() As Int32 = A.FindEquals(4) 115. ' > 0 6 8 116. Dim BEqs() As Int32 = B.FindEquals("casa") 117. ' > 1 4 118. 119. Console.ReadKey() 120. End Sub 121. 122. End Module
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.
Ecco un esempio: 01. Module Module1 02. 03. 'Classe che filtra dati di qualsiasi natura Class DataFilter(Of T) 04. Delegate Function FilterData(ByVal Data As T) As Boolean 05. 06. 07. 'La signature chilometrica fatta apposta per 08. 'farvi impazzire XD Vediamo le parti una per una: ' - TSerach: deve essere un tipo uguale a T o derivato 09. 10. ' da T, in quanto stiamo elaborando elementi di tipo T; 11. ' inoltre deve anche essere clonabile, poich 12. ' salveremo solo una copia dei valor trovati. 13. ' Questo implica che TSearch sia un tipo reference, e che ' quindi lo sia anche T: questa complicazione solo 14. 15. ' per mostrare dei vincoli multipli e potete anche 16. ' rimuoverla se vi pare; 17. ' - TList: deve essere un tipo reference, esporre un 18. ' costruttore senza parametri ed implementare ' l'interfaccia IList(Of TSearch), ossia deve 19. ' essere una lista; 20. 21. ' - ResultList: lista in cui riporre i risultati (passata ' per indirizzo); 22. ' - Filter: delegate che punta alla funzione usata per 23. ' selezionare i valori; 24. ' - Data: paramarray contenente i valori da filtrare. 25. Sub Filter(Of TSearch As {ICloneable, T}, TList As {IList(Of TSearch), New, Class}) _ 26. (ByRef ResultList As TList, ByVal Filter As FilterData, ByVal ParamArray Data() As 27. TSearch) 28. 'Se la lista Nothing, la inizializza. 29. 'Notare che non avremmo potuto compararla a Nothing 30. 'senza il vincolo Class, n inizializzarla 31. 'senza il vincolo New 32. If ResultList Is Nothing Then 33. ResultList = New TList() 34. End If 35. 36. 'Itera sugli elementi di data 37. For Each Element As TSearch In Data 38. 'E aggiunge una copia di quelli che 39. 'soddisfano la condizione 40. If Filter.Invoke(Element) Then 41. 'Aggiunge una copia dell'elemento alla lista. 42. 'Anche in questo non avremmo potuto richiamare 43. 'Add senza il vincolo interfaccia su IList, n 44. 'clonare Element senza il vincolo interfaccia ICloneable 45. ResultList.Add(Element.Clone()) 46. End If 47. Next 48. End Sub 49. End Class 50. 51. 'Controlla se la stringa A palindroma 52. Function IsPalindrome(ByVal A As String) As Boolean 53. Dim Result As Boolean = True 54. 55. For I As Int32 = 0 To (A.Length / 2) - 1 56. If A.Chars(I) <> A.Chars(A.Length - 1 - I) Then 57. Result = False 58. Exit For 59. End If 60. Next 61. 62. Return Result 63. End Function 64. 65. Sub Main() 66. Dim DF As New DataFilter(Of String) 67. 68. 'Lista di stringhe: notare che la variabile non 69. 'contiene nessun oggetto perch non abbiamo usato New. 70.
'Serve per mostrare che verr inizializzata 71. 'da DF.Filter. 72. Dim L As List(Of String) 73. 74. 'Analizza le stringhe passate, trova quelle palindrome 75. 'e le pone in L 76. DF.Filter(L, AddressOf IsPalindrome, _ 77. "casa", "pane", "anna", "banana", "tenet", "radar") 78. 79. For Each R As String In L 80. Console.WriteLine(R) 81. Next 82. 83. Console.ReadKey() 84. End Sub 85. 86. End Module
La seconda si attua postponendo un punto inter r ogativo al nome del tipo: una sintassi molto br eve e concisa che tuttavia pu anche sfuggir e facilmente all'occhio. Una volta dichiar ata, una var iabile nullable pu esser e usata come una comunissima var iabile del tipo gener ic collegato specificato. Essa, tuttavia, espone alcuni membr i in pi r ispetto ai nor mali tipi value, nella fattispecie: HasValue : pr opr iet r eadonly che r estituisce Tr ue se l'oggetto contiene un valor e; Value : pr opr iet r eadonly che r estituisce il valor e dell'oggetto, nel caso esista; GetValueOr Default() : funzione che r estituisce Value se l'oggetto contiene un valor e, altr imenti il valor e di default per quel tipo (ad esempio 0 per i tipi numer ici). Ha un over load che accetta un par ametr o GetValur Or Default(X): in questo caso, se l'oggetto non contiene nulla, viene r estituito X al posto del valor e di default. Ecco un esempio: 01. Module Module1 02. 03. Sub Main() 04. 'Tre variabili di tipo value dichiarate come 05. 'nullable nei due modi diversi consentiti 06. Dim Number As Integer? 07. Dim Data As Nullable(Of Date) 08.
Dim Cost As Double? 09. Dim Sent As Nullable(Of Boolean) 10. 11. 'Ammettiamo di star controllando un database: 12. 'questo array di oggetti rappresenta il contenuto 13. 'di una riga 14. Dim RowValues() As Object = {DBNull.Value, New Date(2009, 7, 1), 67.99, DBNull.Value} 15. 16. 'Con un solo ciclo trasforma tutti i DBNull.Value 17. 'in Nothing, poich i nullable supportano solo 18. 'Nothing come valore nullo 19. For I As Int16 = 0 To RowValues.Length - 1 20. If RowValues(I) Is DBNull.Value Then 21. RowValues(I) = Nothing 22. End If 23. Next 24. 25. 'Assegna alle variabili i valori contenuti nell'array: 26. 'non ci sono mai problemi in questo codice, poich, 'trattandosi di tipi nullable, questi oggetti possono 27. 28. 'accettare anche valori Nothing. In questo esempio, 29. 'Number e Sent riceveranno un Nothing come valore: la 30. 'loro propriet HasValue varr False. 31. Number = RowValues(0) 32. Data = RowValues(1) 33. Cost = RowValues(2) Sent = RowValues(3) 34. 35. 'Scrive a schermo il valore di ogni variabile, se ne 36. 'contiene uno, oppure il valore di default se non 37. 'contiene alcun valore. 38. Console.WriteLine("{0} {1} {2} {3}", _ 39. Number.GetValueOrDefault, _ 40. Data.GetValueOrDefault, _ 41. Cost.GetValueOrDefault, _ 42. Sent.GetValueOrDefault) 43. 44. 'Provando a stampare una variabile nullable priva 45. 'di valore senza usare la funzione GetValueOrDefault, 46. 'semplicemente non stamperete niente: 47. ' Console.WriteLine(Number) 48. 'Non stampa niente e va a capo. 49. 50. Console.ReadKey() 51. End Sub 52. 53. 54. End Module
Tr ue False
Xor Xor
Null Null
Null Null
A ssem bly L'assembly l'unit logica pi piccola su cui si basa il Fr amew or k .NET. Un assembly altr o non che un pr ogr amma o una libr er ia di classi (compilati in .NET). Il Fr amew or k stesso composto da una tr entina di assembly pr incipali che costituiscono le libr er ie di classi pi impor tanti per System.Dr aw ing.dll, System.Cor e.dll, ecceter a...). la pr ogr ammazione .NET (ad esempio System.dll,
Il ter mine Reflection ha un significato molto pr egnante: la sua tr aduzione in italiano alquanto lampante e significa "r iflessione". Dato che viene usata per ispezionar e, analizzar e e contr ollar e il contenuto di assembly, r isulta evidente che mediante r eflection noi scr iviamo del codice che analizza altr o codice, anche se compilato: una specie di our obor os, il ser pente che si mor de la coda; una r iflessione della pr ogr ammazione su se stessa, appunto. Lasciando da par te questo inter cor so filosofico, c' da dir e che la r eflection di gr an lunga una delle tecniche pi utilizzate dall'IDE e dal Fr amew or k stesso, anche se spesso questi meccanismi si svolgono "dietr o le quinte" e vengono mascher ati per non far li appar ir e evidenti. Alcuni esempi sono la ser ializzazione, di cui mi occuper in seguito, ed il late binding.
Late Binding L'azione del legar e (in inglese, appunto, "bind") un identificator e a un valor e viene detta binding: si esegue un binding, ad esempio, quando si assegna un nome a una var iabile. Questo consente un'astr azione fondamentale affinch il pr ogr ammator e possa compr ender e ci che sta scr itto nel codice: nessuno r iuscir ebbe a capir e alcunch se al posto dei nomi di var iabile ci fosser o degli indir izzi di memor ia a otto cifr e. Ebbene, esistono due tipi di binding: quello statico o "ear ly", e quello dinam ico o "late". Il pr imo viene effetuato pr ima che il pr ogr amma sia eseguito, ed quello che per mette al compilator e di tr adur r e in linguaggio inter medio le istr uzioni scr itte in for ma testuale dal pr ogr ammator e. Quando assegnamo un nome ad una var iabile, o r ichiamiamo un metodo da un oggetto stiamo attuando un ear ly binding: sappiamo che quell'identificator e logicamente legato a quel pr eciso valor e di quel pr eciso tipo e che, allo stesso modo, quel nome r ichiamer pr opr io quel metodo da quell'oggetto e, non, magar i, un metodo a caso disper so nella memor ia. Il secondo, al contr ar io, viene por tato a ter mine mentr e il pr ogr amma in esecuzione: ad esempio, r ichiamar e dei metodi d'istanza di una classe Per son da un oggetto Object un esempio di late binding, poich solo a r un-time, il nome del membr o ver r letto, ver ificato, e, in caso di successo, r ichiamato. Tuttavia, non esiste alcun legame tr a una var iabile Object e una di tipo Per son, se non che, a r untime, la pr ima potr contener e un valor e di tipo Per son, ma questo il compilator e non pu saper lo in anticipo (mentr e noi s).
Esiste un unico namespace dedicato inter amente alla r eflection e si chiama, appunto, System.Reflection. Una delle classi pi impor tanti in questo ambito, invece, System.Type. Quest'ultima una classe molto speciale, poich ne esistono molte istanze, ognuna unica, ma non possibile cr ear ne di nuove. Ogni istanza di Type r appr esenta un tipo: ad esempio, c' un oggetto Type per Str ing, uno per Per son, uno per Integer , e via dicendo. Risulta logico che non possiamo cr ear e un oggetto Type, per ch non sar ebbe associato ad alcun tipo e non avr ebbe motivo di esister e: possiamo, al contr ar io, ottener e un oggetto Type gi esistente.
I Contesti
Pr ima di iniziar e a veder e come analizzar e un assembly, dobbiamo fer mar ci un attimo a capir e come funziona il sistema oper ativo a livello un po' pi basso del nor male. Questo ci sar utile per sceglier e una modalit di accesso all'assembly coer ente con le nostr e necessit. Quasi ogni sistema oper ativo composto di pi str ati sovr apposti, ognuno dei quali ha il compito di gestir e una deter minata r isor sa dell'elabor ator e e di for nir e per essa un'astr azione, ossia una visione semplificata ed estesa. Il pr imo str ato il gestor e di pr ocessi (o ker nel), che ha lo scopo di coor dinar e ed isolar e i pr ogr ammi in esecuzione r acchiudendoli in ar ee di memor ia separ ate, i pr ocessi appunto. Un pr ocesso r appr esenta un "pr ogr amma in esecuzione" e non contiene solo il semplice codice eseguibile, ma, oltr e a questo, mantiene tutti i dati iner enti al funzionamento del pr ogr amma, ivi compr esi var iabili, collegamenti a r isor se ester ne, stato della CPU, ecceter a... Oltr e ad assegnar e un dato per iodo di tempo macchina ad ogni pr ocesso, il ker nel separ a le ar ee di memor ia r iser vate a ciascuno, r endendo impossibile per un pr ocesso modificar e i dati di un altr o pr ocesso, causando, in