Dalle raccolte di routine degli anni '80 ad oggi, la storia delle librerie evolve con l'identico scopo di creare codice riutilizzabile.
Con .NET le raccolte sono diventate librerie di classi, cioè assembly o, più colloquialmente, DLL. Queste librerie raggruppano sotto una unica denominazione molte classi, di solito omogenee tra loro, che con i loro metodi permettono di definire del codice riutilizzabile. I vantaggi sono notevoli:
- per chi crea le librerie, basti pensare alle librerie di componenti di terze parti create per la vendita senza dover distribuire il codice sorgente;
- per chi le utilizza, perché semplificano il lavoro, riducono i tempi di sviluppo e di test e aumentano la produttività: il codice delle librerie è già testato e non occorre "reinventare l'acqua calda".
Creare libreria di classi con Visual Studio
Naturalmente anche in Visual Studio 2012 possiamo creare delle librerie di classi..
Visual Studio 2012 fornisce tutti gli strumenti necessari per creare, distribuire e utilizzare librerie di classi. Tra questi strumenti, ora, è compresa anche una nuova funzionalità che è una novità assoluta per Visual Studio: il namespace Global.
In questo articolo parleremo proprio di questo importantissimo namespace, ma prima facciamo un piccolo ripasso sui namespace standard e su quelli personalizzati (definiti dagli sviluppatori). Vedremo poi cosa cambia con l'introduzione del nuovo namespace Global e perché è così importante.
I namespace
I namespace sono concettualmente simili a contenitori e servono a organizzare il codice in unità ben definite. Sono organizzati in modo gerarchico: ciascun namespace può contenere una o più classi, ma anche altri namespace. Ogni namespace di secondo livello può contenere altri namespace e classi e così via, in una struttura gerarchica anche molto complessa e con molti livelli.
È molto importante organizzare bene la gerarchia di namespace e di classi, perché una gerarchia disordinata provoca confusione nello sviluppatore che utilizza la libreria, causando la perdita di tutti i vantaggi che si volevano ottenere.
Anche il .NET Framework è organizzato in questo modo, con moltissimi namespace nidificati a più livelli, come le scatole cinesi. Ogni namespace raggruppa tutte le classi che fanno parte di una specifica categoria: per esempio il namespace System.IO
contiene tutte le classi che servono per lavorare con i file, le cartelle e i dischi, con i flussi di input e output e così via.
Qui troviamo anche altri namespace specializzati: Compression
, IsolatedStorage
, Packaging
, Pipes
e Ports
. A loro volta, questi namespace contengono altre classi associate alla specifica categoria. Per esempio il namespace Compression
contiene le classi necessarie a gestire i file compressi.
Il Default namespace
Anche se non ce ne rendiamo conto, nei nostri progetti utilizziamo sempre almeno un namespace: il Root namespace (spazio dei nomi "radice"). Nella documentazione disponibile in rete, in alcuni casi potreste trovare questo elemento denominato anche come Default namespace (Spazio dei nomi predefinito), ma lo potete considerare come sinonimo.
Per verificare che quello che abbiamo affermato è vero, potete aprire qualsiasi soluzione di Visual Studio ed esaminare le proprietà del progetto. Nella scheda Applicazione
, infatti, troviamo una proprietà di nome Spazio dei nomi radice
:
Come potete vedere in figura, il nome del namespace del progetto è uguale al nome del progetto e dell'assembly, ma potete modificarlo secondo le vostre preferenze (il nome non può iniziare comunque con un carattere numerico).
Definire i namespace nel codice
Chiunque può definire nuovi namespace nei progetti Visual Studio, anche nidificandoli in namespace a più livelli. Per esempio possiamo definire un codice come quello seguente:
Namespace MyNamespace
Public Class Classe1
' qui scriviamo il codice della classe Classe1
End Class
Namespace MyNamespace2
Public Class Classe2
' qui scriviamo il codice della classe Classe2
End Class
End Namespace
Namespace MyNamespace3
Public Class Classe3
' qui scriviamo il codice della classe Classe3
End Class
Namespace MyNamespace4
Public Class Classe4
' qui scriviamo il codice della classe Classe4
End Class
End Namespace
End Namespace
End Namespace
Per accedere alle varie classi dobbiamo utilizzare una sintassi che tenga conto di tutto il "percorso" che è necessario seguire per raggiungere la classe, in modo non molto diverso da quello che si fa normalmente nel file system per indicare un file contenuto in una delle sottocartelle. Per esempio, se vogliamo accedere alla Classe1
possiamo utilizzare il riferimento:
MyNamespace.Classe1
Per accedere alla Classe4
, invece, dobbiamo utilizzare il riferimento:
MyNamespace.MyNamespace3.MyNamespace4.Classe4
Poiché i namespace hanno sempre un livello di accesso di tipo Public
, possiamo accedere liberamente ai namespace da qualsiasi punto del codice.
Proviamo ora a vedere un esempio con una applicazione di prova.
"Metodo tradizionale" in un'applicazione Visual Basic
Consideriamo come "metodo tradizionale" quello che si è sempre fatto prima dell'introduzione del nuovo namespace Global. In seguito vedremo cosa cambia utilizzando Global
.
Dopo aver creato un'applicazione di tipo WPF (che noi abbiamo chiamato MyLibrary_VS2012_01_VB
), inseriamo tre caselle di testo (Valore1
, Valore2
e Risultato
), tre controlli di tipo Label
per le descrizioni e due pulsanti (Somma1
e Somma2
), disposti come in figura:
Il codice XAML è il seguente:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="MainWindow" Title="MainWindow" Height="330" Width="394">
<Grid Margin="10,0,2,3">
<TextBox x:Name="Valore1" HorizontalAlignment="Left" VerticalAlignment="Top"
Height="37" TextWrapping="Wrap" Text="0"
Width="180" Margin="140,28,0,0" FontSize="20"/>
<TextBox x:Name="Valore2" HorizontalAlignment="Left" VerticalAlignment="Top"
Height="37" TextWrapping="Wrap" Text="0" Width="180"
Margin="140,70,0,0" FontSize="20"/>
<Button x:Name="Somma1" Content="Somma senza Global" HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="275" Margin="45,124,0,0"
FontSize="20"/>
<Button x:Name="Somma2" Content="Somma con Global" HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="275" Margin="45,160,0,0"
FontSize="20"/>
<Label Content="Valore 1" HorizontalAlignment="Left" VerticalAlignment="Top"
Margin="45,24,0,0" FontSize="20"/>
<Label Content="Valore 2" HorizontalAlignment="Left" VerticalAlignment="Top"
Margin="45,66,0,0" FontSize="20"/>
<Label Content="Risultato" HorizontalAlignment="Left" VerticalAlignment="Top"
Margin="45,221,0,0" FontSize="20"/>
<TextBox x:Name="Risultato" HorizontalAlignment="Left" VerticalAlignment="Top"
Height="37" TextWrapping="Wrap" Text="0" Width="180"
Margin="140,225,0,0" FontSize="20"/>
</Grid>
</Window>
L'obiettivo che ci poniamo è quello di sommare i due valori inseriti nelle caselle di testo e inserire il risultato nella terza casella di testo. Ovviamente è un obiettivo semplice da raggiungere e anche poco interessante: proprio per la semplicità del compito possiamo concentrarci meglio sul vero obiettivo del progetto, cioè una migliore definizione di una libreria di classi.
Aggiungete una classe di nome Class1.vb
nel progetto Visual Basic, aprite il file con un doppio clic e inserite il seguente codice:
' Esempio: MyLibrary_VS2012_01_VB
Public Class Class1
Public Function Somma(ByVal valore1 As Long, ByVal valore2 As Long) As Long
Return valore1 + valore2
End Function
End Class
Questa classe espone una funzione (Somma
) che prende due interi di tipo Long
come argomenti di ingresso, e ne restituisce la somma. Come abbiamo già specificato, la semplicità della funzione è voluta per non aggiungere un ulteriore livello di complessità. Naturalmente voi potete definire qualsiasi funzione, anche qualcosa di molto complesso.
Dopo aver aggiunto una nuova classe di nome Class2.vb, inserite il seguente codice:
' Esempio: MyLibrary_VS2012_01_VB
Namespace Utilities
Public Class Class2
Public Function Somma( ByVal valore1 As Long, ByVal valore2 As Long) As Long
Return valore1 + valore2
End Function
End Class
End Namespace
Come potete vedere, Class2
contiene esattamente le stesse istruzioni di Class1
, ma in questo caso è inserita nella dichiarazione del namespace Utilities
. Questo nome è inventato, per seguire l'esempio potete scegliere lo stesso nome o quello che preferite.
A prima vista, il namespace Utilities
dovrebbe essere un namespace di primo livello, ma come vedremo le cose non stanno esattamente in questi termini.
Ora dobbiamo attivare i due pulsanti, in modo che premendoli succeda qualcosa di utile.
Torniamo al file MainWindow.xaml
ed eseguiamo un doppio-clic su entrambi i pulsanti, in modo da creare lo schema vuoto dei loro gestori degli eventi Click
. Inseriamo quindi il seguente codice:
' Esempio: MyLibrary_VS2012_01_VB
Class MainWindow
Private Sub Somma1_Click(sender As Object, e As RoutedEventArgs) _
Handles Somma1.Click
Risultato.Text = "0"
If IsNumeric(Valore1.Text) And IsNumeric(Valore2.Text) Then
Dim Utilities As New MyLibrary_VS2012_01_VB.Class1
Risultato.Text = Utilities.Somma(Long.Parse(Valore1.Text), Long.Parse(Valore2.Text)).ToString
Else
MessageBox.Show("Uno dei due valori non è numerico")
End If
End Sub
Private Sub Somma2_Click(sender As Object, e As RoutedEventArgs) _
Handles Somma2.Click
Risultato.Text = "0"
If IsNumeric(Valore1.Text) And IsNumeric(Valore2.Text) Then
Dim Utilities As New MyLibrary_VS2012_01_VB.Utilities.Class2
Risultato.Text = Utilities.Somma(Long.Parse(Valore1.Text), Long.Parse(Valore2.Text)).ToString
Else
MessageBox.Show("Uno dei due valori non è numerico")
End If
End Sub
End Class
Abbiamo verificato che entrambe le caselle di testo abbiano un valore numerico valido: se entrambi i valori sono validi viene eseguita la somma, se anche un solo valore non è numerico (testo o vuoto) viene visualizzato un messaggio di errore.
I due metodi sono pressoché uguali, tranne per la terza istruzione che rispettivamente si presenta così:
Dim Utilities As New MyLibrary_VS2012_01_VB.Class1
e così:
Dim Utilities As New MyLibrary_VS2012_01_VB.Utilities.Class2
Vedremo in seguito il motive di questa differenza. per ora limitiamoci ad avviare l'applicazione. Dopo aver inserito due valori numerici nelle prime due caselle di testo provate a premere il primo pulsante. Se il codice è scritto correttamente, dovremmo vedere nella terza casella di testo il valore risultante dalla somma dei due valori inseriti.
Lo stesso avverrà premendo il secondo pulsante, a dimostrazione che i due metodi sono assolutamente equivalenti.
Notiamo, però, che nel codice abbiamo dovuto inserire un riferimento esplicito allo Spazio dei nomi radice (MyLibrary_VS2012_01_VB
). Nel secondo caso, poi, vediamo che il namespace Utilities
dipende dallo spazio dei nomi radice, cioè è un namespace di secondo livello, mentre noi volevamo che questo diventasse un namespace principale, cioè un namespace di primo livello.
Nella figura seguente osserviamo la posizione delle classi Class1
e Class2
nella gerarchia di classi dell'applicazione:
Continuiamo ora con il namespace Global.
Il namespace Global
Cerchiamo ora di migliorare la struttura dell'applicazione utilizzando la parola riservata Global.
Per prima cosa modifichiamo la dichiarazione delle classi Class1
e Class2
nel seguente modo (il codice all'interno delle due classi rimane invariato):
' Esempio: MyLibrary_VS2012_02_VB
Namespace Global
Public Class Class1
Public Function Somma(ByVal valore1 As Long, ByVal valore2 As Long) As Long
Return valore1 + valore2
End Function
End Class
End Namespace
' Esempio: MyLibrary_VS2012_02_VB
Namespace Global.Utilities
Public Class Class2
Public Function Somma(ByVal valore1 As Long, ByVal valore2 As Long) As Long
Return valore1 + valore2
End Function
End Class
End Namespace
Modifichiamo poi le chiamate presenti nel codice dei gestori degli eventi Click dei due pulsanti:
' Esempio: MyLibrary_VS2012_02_VB
Class MainWindow
Private Sub Somma1_Click(sender As Object, e As RoutedEventArgs) _
Handles Somma1.Click
Risultato.Text = "0"
If IsNumeric(Valore1.Text) And IsNumeric(Valore2.Text) Then
Dim Utilities As New Class1
Risultato.Text = Utilities.Somma(Long.Parse(Valore1.Text), Long.Parse(Valore2.Text)).ToString
Else
MessageBox.Show("Uno dei due valori non è numerico")
End If
End Sub
Private Sub Somma2_Click(sender As Object, e As RoutedEventArgs) _
Handles Somma2.Click
Risultato.Text = "0"
If IsNumeric(Valore1.Text) And IsNumeric(Valore2.Text) Then
Dim Utilities As New Utilities.Class2
Risultato.Text = Utilities.Somma(Long.Parse(Valore1.Text), Long.Parse(Valore2.Text)).ToString
Else
MessageBox.Show("Uno dei due valori non è numerico")
End If
End Sub
End Class
La modifica che abbiamo apportato al codice è veramente minima, ma gli effetti sono molto interessanti. Diamo un'occhiata al Visualizzatore classi
, confrontando il precedente progetto con quello che abbiamo appena modificato:
la struttura è sostanzialmente identica.
Cos'è Global?
Global è una nuova parola riservata che definisce un namespace di primo livello. Potendo essere solo un namespace di primo livello, non può essere utilizzata per definire un namespace nidificato, come nell'esempio seguente:
' ### definizione non corretta! ###
Namespace MiaLibreria
Namespace Global.NuovaLibreria
' ... codice
End Namespace
End Namespace
Evitare le collisioni
Poiché Global definisce un namespace di primo livello, è possibile definire al suo interno dei namespace di livello inferiore anche con lo stesso nome di altri namespace già esistenti nel .NET Framework, senza che questo sia la causa di "collisioni".
Supponiamo, per esempio, di scrivere un blocco di codice come quello che segue:
' ### definizione non corretta! ###
Namespace System.Text
Class CodificaTesto
' ... codice
End Class
End Namespace
Module Module1
Sub Main()
Dim codifica As New System.Text.CodificaTesto
' la seguente istruzione provocherà un'errore
' in fase di compilazione, perché il namespace
' standard ha perso l'ambito di visibilità:
Dim sb As New System.Text.StringBuilder
End Sub
End Module
Mentre IntelliSense mostra di riconoscere la classe System.Text.CodificaTesto
, possiamo notare che invece non riconosce più la classe System.Text.StringBuilder
, una classe del .NET Framework.
Questo comportamento può apparire strano, ma in realtà è normale: il namespace System.Text
che abbiamo creato, infatti, blocca la visibilità del namespace presente nel .NET Framework ed espone solamente il namespace creato localmente.
Estendere un namespace del .NET Framework con Global
La soluzione, in questo caso, ci viene dalla parola Global
: utilizzandola, infatti, possiamo estendere i namespace del .NET Framework con la massima facilità.
Proviamo a vedere lo stesso caso con le opportune modifiche.
' questa definizione funziona!
Namespace Global.Math
Public Class Operazioni
Public Function SommaIntera(ByVal valore1 As Long, ByVal valore2 As Long) As Long
Return valore1 + valore2
End Function
End Class
End Namespace
Module Module1
Sub Main()
Dim somma As New Math.Operazioni()
somma.SommaIntera(1, 2)
End Sub
End Module
Dopo la semplice aggiunta di Global alla definizione del namespace, saranno riconosciute sia le classi standard del .NET Framework (per esempio System.Text.StringBuilder
, System.Math
), sia le estensioni che abbiamo definito nel nostro codice (in questo caso System.Math.Operazioni
).
Concludiamo allora con l'ultimo esempio che permette alla nostra classe Operazioni
di diventare un'estensione del namespace System.Math
. Per ottenere questo risultato, aggiungiamo qualche modifica in più.
Creiamo una nuova soluzione Visual Studio e procediamo pressappoco con le stesse modalità utilizzate negli esempi precedenti.
In questo caso, però, invece di una classe aggiungeremo un modulo (Module1.vb
). In questo modulo inseriremo il seguente codice:
' Esempio: MyLibrary_VS2012_03_VB
Namespace Global.System.Math
Public Class Operazioni
Public Function SommaIntera(ByVal valore1 As Long, ByVal valore2 As Long) As Long
Return valore1 + valore2
End Function
End Class
End Namespace
All'interno del modulo abbiamo definito il namespace Global.System.Math
e, nidificato a questo, la classe Operazioni
. Quest'ultima contiene un unico metodo di nome SommaIntera
che richiede due argomenti di tipo Long
e, a sua volta, restituisce un valore di tipo Long che rappresenta la somma dei due valori di ingresso.
Nel file MainWindow.xaml.vb
modifichiamo il codice come segue:
' Esempio: MyLibrary_VS2012_03_VB
Class MainWindow
Private Sub Somma1_Click(sender As Object, e As RoutedEventArgs) _
Handles Somma1.Click
If IsNumeric(Valore1.Text) And IsNumeric(Valore2.Text) Then
Dim Utilities As New System.Math.Operazioni()
Risultato.Text = Utilities.SommaIntera(Long.Parse(Valore1.Text), Long.Parse(Valore2.Text)).ToString()
Else
MessageBox.Show("Uno dei due valori non è numerico")
End If
End Sub
End Class
In questo caso abbiamo creato un'istanza della classe System.Math.Operazioni
che è esattamente la classe che abbiamo definito noi. Se non fosse per il nome della classe, espresso in lingua italiana, sembrerebbe essere una classe inclusa nativamente nel namespace System.Math
.