In C# è possibile identificare due tipi di variabili: i value types e i reference types: una qualsiasi variabile che dichiariamo all'interno del codice è mutuamente appartenente a uno dei due tipi citati.

Alla maggior parte dei programmatori questa affermazione non provoca particolare interesse: al massimo può provocare un fragoroso: .... e chi se frega ?? Oppure più mestamente provocare confusi ricordi legati alla prima formazione ricevuta su C#.

Infatti usualmente quando si studia il C# questa differenza viene ben spiegata e approfondita, ma poi avendo implicazioni pratiche molto limitate usualmente "si perde il concetto".

In effetti il programmatore mediamente può assolutamente disinteressarsi di questa differenza, e considerala micragnosa e petulante, ma quando si hanno esigenze di perfomance..... allora proprio lì si va a finire !

Inoltre...... non è interessante capire come "funziona il giocattolo" ??

Dunque: torniamo ai value types e i reference types.

Quando il programma viene eseguito viene allocato un certo spazio di RAM delegato a contenere i dati necessari per l'esecuzione: all'interno di questo spazio  vengono riservate alcune zone che hanno compiti ben precisi e stabiliti.

Le zone delegate a contenere il valore delle variabili dichiarate all'interno del programma sono due, e sono chiamate managed heap e lo stack.

Facendola breve il managed heap contiene il valore di tutte le viariabili reference type, mentre lo stack contiene il valore delle variabili value type.

 

Come fare per riconoscere una variabile value type ??

Semplice: tutte le variabili primitive offerte dal linguaggio, ad eccezione delle stringhe, sono value type. A queste si aggiungono le struct.

Tutte le altre varaibili sono reference type. Semplice no ?

Un altro modo per comprendere se una variabile è di un tipo oppure dell'altro è vedere il da chi discende. Tutte le variabili di tipo value type derivano direttamente da System.ValueType: diversamentre le variabili di tipo reference type derivano direttamente da System.Object.

Attenzione perchè poi nella gerarchia il System.ValueType deriva a sua volta deriva da System.Object, ma nel caso delle variabili value type è chiaro che la derivazione da System.Object non è diretta !

Quindi è possibile esemplificare al massimo usando il seguente

<Nome Variabile>.GetType().BaseType -> System.Object -> E' un reference type
<Nome Variabile>.GetType.BaseType -> System.ValueType -> E un value type

Perchè occorrono due aree di memoria distinte ??

Esemplificando al massimo le variabili di tipo reference type hanno una occupazione di RAM molto variabile e che è difficile stabilire a priori: diversamente le variabili di tipo value type hanno una occupazione stabilita e ben precisa.

Es.: L'occupazione di una varabile di tipo bool è sempre di 1 byte.

Per tale motivo sono necessarie due aree distinte: una in cui vengono posti i modo ordinato e ben preciso i valori delle variabili a lunghezza fissa (stack) mentre nell'altra sono memorizzati i valori delle variabili di cui non è possibile stabilire con precisione l'occupazione (managed heap).

Nel managed heap opera il garbage collector, il processo di .Net framework delegato a tenere in ordine quanto possibile e deframmentare i dati memorizzati.

Ora il problema è che quando si opera con una variabile di tipo object (=System.Object), si sta lavorando con una variabile di tipo reference type, e quindi il suo valore viene memorizzato nel managed heap.

object obj = 1;

Cosa succede con il codice sopra ?

Semplice: si alloca dello spazio nel managed heap, e si "sbatte dentro" il valore 1. Però il managed heap non è fatto per memorrizare semplicemente il valore 1, ma per ospitare strutture più complesse capaci di modificare la propria capienza.

Per tale motivo tale valore viene incapsulato dentro un oggetto creato al volo dal framework, che ha il solo scopo di contenere detto valore.

Per tale motivo questo processo viene chiamato boxing.

Il processo inverso, per analoghi motivi, viene invece chiamato unboxing.

int i = (int)obj;

Estremizzano al massimo si ha un boxing quando si converte un value type in una varaibile di tipo object, e l'unboxing quando si tira fuori un value type da una varabile di tipo object. Initile osservare che il boxing è implicito, mentre l'unboxing è soggetto a cast.

Ora cazzegiamo un poco intorno a quanto esposto.

int i = 9;
object obj = i;
i = 10;

Console.WriteLine("Valore di i: " + i.ToString());
Console.WriteLine("Valore di obj: " + obj.ToString());

Ecco il risultato stampato a video.

Valore di i: 10
Valore di obj: 9

Quanto sopra dimostra inequivocabilmente che quando si esegue il boxing del contenuto di una variabile value type si duplica il valore ivi contenuto. Infatto agendo sulla variabiel i il valore di obj rimane intonso.

Similmente avviene quando si opera solo sul reference type.

object obj = 1;
object obj2 = obj;
obj2 = 10;

Console.WriteLine("Valore di obj: " + obj.ToString());
Console.WriteLine("Valore di obj2: " + obj2.ToString());

Ecco il risultato.

Valore di obj: 1
Valore di obj2: 10

Quanto sopra dimostra che quando si esegue un boxing si crea un oggetto al volo e si copia dentro questo il valore.

Con l'unboxing entrano in gioco ugualmente svariate necessità computazionali: il .Net framework deve controllare il tipo e verificare se è compatibile, e tutta un'altra serie di attività molto onerose.

Questo si traduce in una cosa: perfomance. Agire usando boxing e unboxing si traduce in un depauperamento delle prestazioni: anzi è possibile affermare che questi sono tra i processi più onerosi che .Net framework metta in campo.

Un'altra difficoltà messa sul tavolo è il "type safety": tirare fuori da un object un value type tramite casting è un processo critico e foriero di errore e malfunzioni: infatti un cast errato può portare a un errore irreversibile nel nostro programma.