Casting e Boxing/Unboxing
Se 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;

riusciremmo subito ad individuare il caso in cui avviene il boxing di un tipo value. Per il casting, esso avviene in "uscita": infatti è lecito assegnare un tipo derivato ad una reference di classe base ma non è lecito il contrario; questa istruzione:
string e = a;

porta ad un errore di compilazione, motivo per cui va fatto il casting a stringa:
//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();

Il Boxing avviene invece sia in "entrata" che in "uscita", motivo per cui esiste il termine boxing e unboxing, che sta proprio ad indicare che per riportare un tipo value a poter essere utilizzato dopo che esso è stato wrappato dentro un oggetto, esso va "scoperto" ovvero "Unboxato":
//L'unboxing è sintatticamente uguale al casting
int f=(int)c;

Detto questo arriviamo a parlare dei generici e del perchè sono così interessanti.

Generici
Il 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);

Quindi ora costruiamoci il nostro generico secondo questa definizione:
static class GenUtils<T>
{
    public static void ShowType(T obj){
        Console.WriteLine(obj.GetType().Name);
    }
}

Il tipo sopra costruito è una classe statica che imporrà al developer di impostare il tipo per ogni utilizzo della classe stessa:
GenUtils<int>.ShowType(10);
GenUtils<string>.ShowType("ciao");

Ovviamente è un esempio un pò inutile e qualcuno potrebbe anche dire: "come mai dobbiamo impostare il generico a livello classe e non a livello metodo?". Non siamo costretti, infatti questa è l'alternativa:
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");

Che portano allo stesso risulato. Ora supponiamo di volere un metodo generico che però sappiamo per certo funzionerà (o avrà senso) solo per un certo set di classi, ovvero tipicamente per tutte quelle classi che siano "almeno" di un tipo derivato ad un certo tipo noto. La definizione cambierà così:
//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));

I vincoli di derivazione sono molto eleganti e permettono di evitare situazioni spiacevoli in cui il tipo generico sia stato implementato secondo una logica applicabile a solo un certo set di tipi, e in cui il programmatore invece tenti di usarlo anche per altri.

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);
    }
}

Invece il vincolo new() è a dire poco geniale. Supponiamo di aver la nostra classe generica e, al suo interno fare una invocazione di questo tipo:
T obj=new T();

ovvero istanziare un nuovo oggetto del tipo generico dichiarato dalla classe. Se però poi il tipo attuale non avrà un costruttore di default vuoto, il programma non funzionerà (motivo per cui il codice sopra non compila). Perciò si può anche imporre che il tipo T attuale, per poter essere passato, dovrà disporre di un costruttore di default vuoto. La sintassi è questa:
static class GenUtils<T> where T:new()
{
    public static void ShowType(T obj){
        Console.WriteLine(obj.GetType().Name);
    }
}

A riprova di quello che ci siamo detti:
//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));

That's all!