Casting e Boxing/UnboxingSe noi scrivessimo una serie di assegnamenti di questo tipo:
//Lecito, possiamo assegnare un tipo derivato ad una reference di classe base object a = "Stringa"; //Ovvio string b = "Stringa"; //Qui avviene boxing, ovvero il tipo value viene wrappato in un oggetto //in modo tale da poter essere assegnato alla reference general-purpose object c = 10; //Ovvio, non avviene Boxing int d = 10;
string e = a;
//Casting esplicito string e = (string)a; //Casting più elegante, abbiamo visto negli articoli precedenti la semantica dell'operatore as string e = a as string; //In questo particolare caso, l'invocazione del metodo ToString (virtuale e sicuramente sovrascritto dalla classe String) provoca proprio il ritorno della stringa che stiamo cercando. NB: non è un metodo generale. string e = a.ToString();
//L'unboxing è sintatticamente uguale al casting int f=(int)c;
GenericiIl vantaggio dei generici risiede nella riduzione delle operazioni Boxing/Casting che dovrebbero avvenire a run-time nel caso utilizzassimo le collection general-purpose. Con una collection di object, dovremmo avere operazioni di casting o boxing/unboxing ogni qual volta dovessimo utilizzare i nostri oggetti della collezione. Perciò il concetto dietro ai generic è questo: dichiarare prima (ovvero in fase di dichiarazione della collection) quale sarà il tipo attuale di ogni dato della stessa, in modo tale che il compilatore possa costruire una collezione di oggetti specifici e risparmiare tempo per l'accesso ad ogni elemento.NB: in questo modo non possiamo avere collezioni miste, ma credo di non averne quasi mai avuto bisogno nella vita (anche per la complessità intrinseca di gestione). Tutti avremo visto almeno una volta i generici, per cui mi limito alla sola dichiarazione e semplice utilizzo:
List<string> listadiStringhe = new List<string>(); listadiStringhe.Add("ciao"); //Errore, il compilatore vede la variabile lista, come un array di stringhe listadiStringhe.Add(10);
static class GenUtils<T> { public static void ShowType(T obj){ Console.WriteLine(obj.GetType().Name); } }
GenUtils<int>.ShowType(10); GenUtils<string>.ShowType("ciao");
static class GenUtils { public static void ShowType<T>(T obj){ Console.WriteLine(obj.GetType().Name); } } //e queste le chiamate GenUtils.ShowType<int>(10); GenUtils.ShowType<string>("ciao");
//Imponiamo che T debba essere una stream => dicesi vincolo di derivazione static class GenUtils<T> where T:Stream { public static void ShowType(T obj){ Console.WriteLine(obj.GetType().Name); } } //ovviamente queste righe saranno incompilabili GenUtils<int>.ShowType(10); GenUtils<string>.ShowType("ciao"); //ma queste si GenUtils<Stream>.ShowType(new StreamWriter("x:/test.xml").BaseStream); GenUtils<MemoryStream>.ShowType(new MemoryStream(10));
Vincoli Value, Reference e new()I vincoli value e reference sono banali e semplicemente impongono che il tipo generico debba essere, rispettivamente, un value o un reference, quindi:
//Vincolo value static class GenUtils<T> where T:struct { public static void ShowType(T obj){ Console.WriteLine(obj.GetType().Name); } } //Vincolo reference static class GenUtils<T> where T:class { public static void ShowType(T obj){ Console.WriteLine(obj.GetType().Name); } }
T obj=new T();
static class GenUtils<T> where T:new() { public static void ShowType(T obj){ Console.WriteLine(obj.GetType().Name); } }
//Non compila perchè Stream non ha il costruttore vuoto GenUtils<Stream>.ShowType(new StreamWriter("x:/test.xml").BaseStream); //Compila perchè MemoryStream, ce l'ha GenUtils<MemoryStream>.ShowType(new MemoryStream(10));