I tipi in C# possono essere:

  • Tipi reference: si riferisce ad un oggetto nello heap gestito.
  • Tipi value: è un oggetto (leggero) che risiede direttamente sullo stack.
  • Puntatori unsafe: sono puntatori old-school per referenziare oggetti in memoria.
Un tipo value abbastanza noto ed utilizzato è il tipo DateTime, che di fatto è una struct.

Classi
Vediamo un esempio di classe sealed e di classe static.
Supponiamo di avere necessità, in una ipotetica architettura, di dare allo sviluppatore finale la possibilità, tramite API, di estendere il nostro applicativo, supponiamo attraverso ereditarietà di alcune particolari classi.
Supponiamo che nella classe SEOSettings vi siano i metodi per renderizzare i meta tag più "consoni", all'interno di una web page. Lo sviluppatore finale, potrà estendere la classe SEOSettings, e sostituirne l'implementazione precedente.
Ma supponiamo ora non ci venga voglia di estendere la classe SiteRender (marcata sealed), per ottenere un render customizzato dell'intero sito web. Sul manuale del prodotto c'è espressamente scritto che non possiamo estendere quella classe perchè il comportamento finale risulterebbe compromesso.
Noi non leggiamo il manuale, perciò noi proviamo con la stragegia di prima e, in compilazione, già siamo fermi. Infatti la parolina chiave sealed ci permette di impedire la estendibilità di una classe, con conseguente impossibilità di ridefinirne i metodi.

Una classe static è, per definizione, una classe non istanziabile. Ma cosa può servire?
Intanto ormai oggi, TUTTE le applicazioni hanno una classe stile "Utilities", "Commons" o cose simili, al fine di contenere metodi di utilità globale (spesso già static) invocabili in qualsivoglia contesto della nostra applicazione. Una classe static afferma soltanto di contenere SOLO metodi statici, evitando che il programmatore possa crearne istanze.

Funzioni e passaggi ref e out
Un concetto molto familiare per i developer più vecchiotti, è sicuramente il passaggio parametri by-reference. Un codice di questo tipo:

void MethodA()
{
     int a = 2;
     int b = 3;
     Swap(ref a, ref b);
     Console.WriteLine(a.ToString() + " - " + b.ToString());
}
void Swap(ref int a, ref int b)
{
     int tmp = b;
     b = a;
     a = tmp;
}

Esemplifica molto bene l'utilizzo della keyword ref, usata in questo caso per lo scambio di valore di due tipi value.
Out è simile con una particolarità: si suppone che l'invocante NON abbia ancora usato la variabile e quindi, non si necessità della sua inizializzazione. Dualmente, visto che è un parametro "di output", nel metodo chiamato dovrà essere presente l'assegnamento a quella variabile.

Costruttori
Le difficoltà maggiori riguardo ai costruttori si hanno in seguito all'ereditarietà. Li vedremo più in là; per ora, da ricordare, ci sono i costruttori static che sono preposti per l'inizializzazione di tutto ciò che è appunto statico, all'interno della nostra classe. Supponiamo di avere una classe in cui è presente un dizionario di delegate, interrogabile staticamente da ogni classe del progetto.
Questo dizionario dovrà essere opportunamente compilato PRIMA che venga utilizzato da qualsiasi metodo esterno. La soluzione potrebbe essere una cosa simile:

private static Dictionary<string, Func<int, int, int>> dict =
    new Dictionary<string, Func<int, int, int>>();
static Utils(){
    dict.Add("SUM", (a, b) => { return a + b; });
    dict.Add("SUB", (a, b) => { return a - b; });
    dict.Add("MUL", (a, b) => { return a * b; });            
}
public static Delegate GetFunction(string key)
{            
    return dict[key];
}

In questo caso se invocassimo il metodo GetFunction, il class-loader di .NET invocherebbe PRIMA il costruttore statico (se è la prima volta che accediamo a quella classe) popolando i dati necessari.
Per finire l'esempio, se volessimo invocare il metodo GetFunction ora avremmo un generico delegate, per cui dovremo invocarlo successivamente passandogli i parametri in DynamicInvoke, sfruttando il late-binding.
static void Main(string[] args)
{
    Console.WriteLine(Utils.GetFunction("SUM").DynamicInvoke(10, 20));
    Console.Read();
}

Se abbiamo qualche risorsa da distruggere, connessioni da chiudere e/o operazioni "finali", è consigliato utilizzare un distruttore, oppure il pattern  Disposable tramite l'implementazione dell'interfaccia IDisposable.
Infatti, se si implementa l'interfaccia e il metodo Dispose, potremmo anche usare il nostro oggetto nel costrutto using, avvalendoci di una distruzione automatica, a fine utilizzo.

using (var s = new DisposableObject()) { 
    //operazioni
}
using (var s = new UndisposableObject())
{
    //invalido, l'oggetto non implementa IDisposable
}


Proprietà implementate automaticamente
Si spiega da solo: volete implementare una proprietà in associazione 1-1 con un campo privato? Basta questo costrutto:

public int MyProperty { get; set; }

e il gioco è fatto! Il miracoloso compilatore di C# produrrà un IL tale che la nostra classe avrà un campo _MyProperty di tipo int.

Tipi Enum e Flags Enum
I tipi enum sono usati spessissimo per favorire il costrutto switch. Quello che possiamo aggiungere alla knowledge base è che il tipo enum è in realtà un tipo value (in particolare un int) che noi possiamo anche cambiare con un altro tipo value. Dichiarazioni tipo sono:

enum MyEnum:ushort
{
    A,
    B,
    C
}

In cui sono ammessi sono byte, ubyte, short, ushort, int, uint, long, ulong.

Le Flags Enum invece sono quelle enum tanto carine utilizzate spesso per identificare insiemi di attributi di una particolare entità.
Si dichiarano così:

[Flags]
enum Attrs
{
    IS_LOCKED=0x01,
    IS_READY = 0x02,
    IS_NICE = 0x04,
    IS_MOODY = 0x08,
    ALL=IS_LOCKED|IS_MOODY|IS_NICE|IS_READY
}

E il comportamento è intuitivo: si utilizza l'OR logico, per assegnare particolari stati "composti" da più condizioni elementari.