I Reference Type
Come già detto nella precedente lezione un tipo reference memorizza il valore della sua variabile nell’area di memoria chiamata heap, mentre mantiene il rifermento a tale area di memoria nell’area di memoria chiamata stack.
Mentre lo stack viene pulito man mano che le variabili in esso memorizzate perdono visibilità (out-of-scope), l’heap viene gestito da un sistema chiamato garbage collector, attraverso un procedimento chiamato garbage collection che consiste nel verificare periodicamente i riferimenti esistenti ad una certa area di memoria heap e pulirla quando non esistono più riferimenti di questo tipo.
Il modo più semplice di capire il funzionamento pratico di un tipo reference è quello di confrontarlo con un value type. Consideriamo ad esempio il value type personalizzato creato l’ultima volta con un esempio di utilizzo:
// C#
struct MioTipo
{
public int i;
public bool b;
}
class Program
{
static void Main(string[] args)
{
MioTipo primo;
primo.i = 1;
primo.b = true;
MioTipo secondo = primo;
secondo.i = 2;
secondo.b = false;
Console.WriteLine("a: {0}, {1}; b: {2}, {3}", primo.i, primo.b, secondo.i, secondo.b);
}
}
' VB
Structure MioTipo
Dim i As Integer
Dim b As Boolean
End Structure
Sub Main()
Dim primo As MioTipo
primo.i = 1
primo.b = True
Dim secondo As MioTipo = primo
secondo.i = 2
secondo.b = False
Console.WriteLine("a: {0}, {1}; b: {2}, {3}", primo.i, primo.b, secondo.i, secondo.b)
End Sub
Il risultato in output è il seguente:

Per costrruire un reference type basta sotituire il costrutto struct (Structure in Visual Basic) con il costrutto Class:
// C#
public class MioTipo
{
public int i;
public bool b;
}
class Program
{
static void <st1:place w:st="on">Main</st1:place>(string[] args)
{
MioTipo primo = new MioTipo();
primo.i = 1;
primo.b = true;
MioTipo secondo = primo;
secondo.i = 2;
secondo.b = false;
Console.WriteLine("a: {0}, {1}; b: {2}, {3}", primo.i, primo.b, secondo.i, secondo.b);
}
}
' VB
Public Class MioTipo
Public i As Integer
Public b As Boolean
End Class
Sub <st1:place w:st="on">Main</st1:place>()
Dim primo As New MioTipo
primo.i = 1
primo.b = True
Dim secondo As MioTipo = primo
secondo.i = 2
secondo.b = False
Console.WriteLine("a: {0}, {1}; b: {2}, {3}", primo.i, primo.b, secondo.i, secondo.b)
End Sub
Il risultato in questo caso invece è il seguente:

Come mai questa differenza? Molto semplicemente perché in entrambi i casi l’assegnazione alla variabile secondo del valore di primo consiste in una copia del valore delle variabili, che nel primo caso (value type) è proprio il valore della variabile, mentre nel secondo caso (reference type) si tratta del riferimento al valore della variabile, di conseguenza dopo l’assegnazione nel secondo caso secondo punta alla stessa locazione di memoria heap di primo.
Un’altra grande differenza è sicuramente l’utilizzo dell’operatore new (New in Visual Basic) che permette di istanziare la classe MioTipo, creando così lo spazio nello heap destinato alla variabile, cosa non necessaria per i value type dato che l’allocazione avviene sullo stack.
I più comuni reference type sono i seguenti:
- System.Object, la classe base di tutti i tipi del .net framework
- System.String, la classe per il contenimento delle stringhe di testo
- System.Text.StringBuilder, la classe per il contenimento di stringhe di testo dinamiche
- System.Array, la classe base di tutti gli array del framework,
- System.IO.Stream, la classe base per la gestione degli stream di dati
- System.Exception, la classe base di tutte le eccezioni del farmework
Del significato della parola classe base parleremo nei prossimi articoli, adesso focalizziamo la nostra attenzione sulla differenza non necessariamente ovvia tra String e StringBuilder.
String vs StringBuilder
Come potrete immaginare sul tipo String sono definite le più comuni operazioni tra stringhe quale ad esempio la somma, perché dunque avere una classe che permetta la gestione di stringhe dinamiche?
Il tipo System.String è un tipo cosiddetto immutabile, il che significa molto semplicemente che ogni volta che la stringa viene modificata, il runtime di .NET crea una nuova stringa, abbandonando la vecchia. Molti programmatori non ci fanno caso perché la cosa avviene in maniera trasparente ma sapendolo vi renderete sicuramente conto di quanto possa essere uno spreco un simile comportamento. Al fine di evitare questo spiacevole inconveniente è stata creata la classe StringBuilder:
// C#
StringBuilder sb = new StringBuilder();
sb.Append("DotNetCampania - ");
sb.Append("Il primo User Group su .NET ");
sb.Append("tutto campano!");
Console.WriteLine(sb);
' VB
Dim sb As New System.Text.StringBuilder
sb.Append("DotNetCampania - ")
sb.Append("Il primo User Group su .NET ")
sb.Append("tutto campano!")
Console.WriteLine(sb)
Il risultato è del tutto immaginabile:
Di default il costruttore (il metodo della classe che inizializza lo stato di un oggetto, ne parleremo nei prossimi articoli) di StringBuilder crea un buffer di 16 byte che ingrandisce in base alle necessità, ma è possibile specificare la dimensione iniziale e anche la dimensione massima.
Creare e ordinare Arrays
Uno dei contenitori più importanti di dati, dopo la variabile, è l’array, per capirci lo possiamo vedere come un insieme contiguo di variabili dello stesso tipo indicizzate in base alla posizione nell’insieme. La creazione in .NET è molto semplice: le parentesi tonde in Visual Basic e le parentesi quadre in C#.
// C#
int[] ar = {20, 30, 10};
' VB
Dim ar() As Integer = {20, 30, 10}
Grazie alla classe stratta Array e al suo metodo statico Sort, l’ordinamento di un array è veramente un’operazione banale:
// C#
Array.Sort(ar);
Console.WriteLine("{0}, {1}, {2}", ar[0], ar[1], ar[2]);
' VB
Array.Sort(ar)
Console.WriteLine("{0}, {1}, {2}", ar(0), ar(1), ar(2))
L’ovvio risultato:

Gli stream di dati
Nel framework .NET l’accesso al disco o la scambio dati nelle comunicazioni di rete sono uniformati dal concetto di Stream, un tipo base grazie al quale è possibile unificare l’accesso ai dati tra varie fonti. Tecnicamente stiamo parlando di una classe stratta da cui vengono create delle classi specifiche della fonte dei dati:
|
System.IO Type
|
Usato per
|
|
FileStream
|
Scrivere e leggere da un file
|
|
MemoryStream
|
Scrivere e leggere dalla memoria
|
|
StreamReader
|
Leggere dati da un file di testo
|
|
StreamWriter
|
Scrivere dati in un file di testo
|
Grazie a queste classi operazioni come la scrittura in un file di testo diventano veramente elementari:
// C#
StreamWriter sw = new StreamWriter("text.txt");
sw.WriteLine("DotNetCampania");
sw.Close();
StreamReader sr = new StreamReader("text.txt");
Console.WriteLine(sr.ReadToEnd());
sr.Close();
' VB
Dim sw As New StreamWriter("text.txt")
sw.WriteLine("DotNetCampania")
sw.Close()
Dim sr As New StreamReader("text.txt")
Console.WriteLine(sr.ReadToEnd)
sr.Close()
Il risultato è abbastanza intuitivo:
Data la loro importanza approfondiremo gli stream nei prossimi articoli.
Gestione delle eccezioni
Che succede se il nostro programma sta leggendo un file da una penna usb e la nostra sorellina, con quell’aria furtiva ma con quello sguardo angelico che ci impedisce di terminarla, ci tira fuori la penna dalla porta USB? Nella speranza che la penna non si sia rotta l’unico problema e che il programma verrebbe interrotto da un’eccezione, cioè un evento inaspettato che interrompe la normale esecuzione dell’algoritmo.
Se una tale eventualità non è stata gestita molto probabilmente il programma termina miseramente con un messaggio di errore generato dal framework, ci va bene? Direi di no! Ecco che quindi ci vengono messi a disposizione i costrutti necessari a gestire una tale eventualità: il blocco Try…Catch…Finally in Visual Basic, try…catch…finally in C#.
Niente è più illuminante di un esempio:
// C#
StreamReader sr = null;
try
{
sr = new StreamReader("text.txt");
Console.WriteLine(sr.ReadToEnd());
}
catch (Exception ex)
{
Console.WriteLine("Errore: " + ex.Message);
}
finally
{
sr.Close();
}
' VB
Dim sr As StreamReader = Nothing
Try
sr = New StreamReader("text.txt")
Console.WriteLine(sr.ReadToEnd())
Catch ex As Exception
Console.WriteLine("Errore: " + ex.Message)
Finally
sr.Close()
End Try
In pratica si divide il codice in due o tre blocchi (il finally è facoltativo): nel primo, il try, si inserisce il codice da sottoporre a gestione delle eccezioni; nel secondo, il catch, si gestisce l’eccezione, nel nostro esempio abbiamo stampato un messaggio di errore; nell’eventuale terzo, il finally, si inseriscono le operazioni da eseguire in qualsiasi caso, sia di normale flusso dell’applicazione che di eccezione. Notate la dichiarazione dello StreamReader fuori dal try: è l’unico modo di renderlo visibile anche al blocco finally.
L’esempio ci da inoltre modo di notare un oggetto molto interessante, Exception, che contiene tutte le informazioni che ci posso essere utili sull’eccezione che è stata generata. La sua struttura è molto generica e viene usata come classe base per creare eccezioni più specifiche, quali ad esempio FileNotFoundException.
La cosa interessante è che mentre il blocco try e il blocco finally sono unici, possiamo creare tutti i blocchi catch che vogliamo a patto di gestire tipi di eccezioni diverse e ricordando che solamente uno sarà utilizzato in caso di eccezione. Facciamo un esempio:
// C#
StreamReader sr = null;
try
{
sr = new StreamReader("text.txt");
Console.WriteLine(sr.ReadToEnd());
}
catch (System.IO.FileNotFoundException ex)
{
Console.WriteLine("File non trovato!");
}
catch (System.UnauthorizedAccessException ex)
{
Console.WriteLine("Non di dispone dei permessi necessari!");
}
catch (Exception ex)
{
Console.WriteLine("Errore: " + ex.Message);
}
finally
{
sr.Close();
}
' VB
Dim sr As StreamReader = Nothing
Try
sr = New StreamReader("text.txt")
Console.WriteLine(sr.ReadToEnd())
Catch ex As System.IO.FileNotFoundException
Console.WriteLine("File non trovato!")
Catch ex As System.UnauthorizedAccessException
Console.WriteLine("Non di dispone dei permessi necessari!")
Catch ex As Exception
Console.WriteLine("Errore: " + ex.Message)
Finally
sr.Close()
End Try
Il bello di questo meccanismo è che possiamo decidere cosa fare in base alla tipologia di eccezione che è stata generata. Tieniamo presente che l’ordine in cui sono messi i catch non è assolutamente casuale, anzi, essendo Exception più generica sia di FileNotFoundException che di UnauthorizedAccessException, metterla in cima ai blocchi catch avrebbe provocato la completa inutilità dei blocchi sottostanti: qualsiasi eccezione sarebbe stata sempre gestita dal blocco catch Exception. Fortuna che il nostro ambiente di sviluppo sa essere molto esplicativo:
Conclusioni
Se non lo avete già fatto penso sia arrivato il momento di aprire visual studio e cominciare a smanettare un po’ altrimenti farete fatica a continuare a seguire questa serie di articoli. Vi do appuntamento al prossimo articolo in cui parleremo di OOP e del contributo di Microsoft a questo paradigma di programmazione che ha rivoluzionato il modo di programmare.
Happy Coding.