Prima legge dell'informatica moderna, vera in praticamente tutti i linguaggi OO degli ultimi 20 anni:
Tutti le classi derivano da Object.

Detto questo il resto ne segue. Se Object ha N metodi pubblici, la classe derivata avrà quei metodi pubblici.
Generalizzando se una classe Foo ha 2 metodi pubblici, 1 private, 3 protected e 5 internal, la sua classe figlia Boo che metodi vedrà al suo interno?

E, non meno importante, che metodi vedranno gli oggetti che vi vogliono accedere tramite una istanza e l'operatore punto?
Case 1:
I due metodi pubblici saranno visti come tali anche nella derivata, perciò accessibili dall'esterno da qualunque classe del runtime.
Case 2:
Il metodo private sarà nascosto alla stessa istanza di classe derivata, perciò ancora di più all'esterno.
Case 3:
I metodi protected saranno "diventati" private nella classe derivata, con conseguente perdita di visibilità all'esterno.
Case 4:
I metodi internal (dualmente al modificatore default di Java), saranno visibili solo al namespace della classe.

Che metodi ha Object, che quindi ereditano tutte le classi del runtime?

  • Equals => Confronta una istanza con un'altra. Deve essere algebricamente consistente per cui, essendo una uguaglianza, deve godere della proprietà:
    • riflessiva [a.Equals(a)=true]
    • simmetrica [a.Equals(b)=b.Equals(a)]
    • transitiva [a.Equals(b) & b.Equals(c) => a.Equals(c)].
  • GetHashCode => se si implementa Equals, si deve implementare anche GetHashCode, siccome quest'ultimo deve essere consistente rispetto all'uguaglianza.
  • MemberwiseClone => clona i tipi value di una istanza in una seconda e copia i riferimenti; fa quello che di solito si dice shallow copy.
  • GetType, ToString, RefenceEquals sono banali.


Virtual, Override e New
Per un javista come me questo oscuro mondo dell'ereditarietà .NET è stato un duro colpo, in passato.
Ora ho capito che il 90% dei problemi che mi ponevo erano proprio perchè volevo applicare una concezione Java ad un altro mondo di sviluppo, ovviamente a torto. Il senso dei metodi virtuali è tanto profondo quanto è complessa la soluzione che si intende implementare.
Partiamo dagli esempi per inferire la teoria.

class Veicolo
{        
    public void A()
    {
        Console.WriteLine("Classe Veicolo");
    }
}
class Auto:Veicolo
{
    public void A()
    {
        Console.WriteLine("Classe Auto");
    }
}

Con il codice sopra abbiamo questo comportamento:
Veicolo a = new Veicolo();
a.A(); //Stamperà ovviamente "Classe Veicolo"
Auto b = new Auto();
b.A(); //Stamperà "Classe Auto"
Veicolo c = new Auto();
c.A(); //Stampa "Classe Veicolo" perchè la reference è a Veicolo e non c'è override

Secondo esempio (modifica virtual+override):
class Veicolo
{        
    public virtual void A()
    {
        Console.WriteLine("Classe Veicolo");
    }
}
class Auto:Veicolo
{
    public override void A()
    {
        Console.WriteLine("Classe Auto");
    }
}
Veicolo c = new Auto();
c.A(); //Stampa "Classe Auto" perchè la reference "sa" che deve andare a ricercare l'implementazione più specializzata di cui dispone, nell'albero dei figli.

Infine l'ultimo esempio, non perchè sia l'ultimo in assoluto, ma l'ultimo rilevante per noi:
class Auto:Veicolo
{
    public new void A()
    {
        Console.WriteLine("Classe Auto");
    }
}
Veicolo c = new Auto();
c.A(); //Stampa "Classe Veicolo" perchè la figlia ha volutamente interrotto la catena di riscrittura premessa dal padre.


Metodi di estensione
Una cosa magica e sempre affascinante sono gli extension methods, che ci permettono di "aggiungere" metodi a classi qualsiasi, sealed o no, interne o esterne al nostro progetto.
Sarebbe carino chiedersi se un numero sia primo o no. Però invece che creare la solita classe di metodi statici vorremmo che, anche per una questione di correttezza semantica, il controllo del numero sia un metodo che espone il numero stesso.
Perciò, senza poter estendere la classe intero (o senza volerlo fare), scriviamo questo codice:

public static bool IsPrime(this int number)
{
    //controllo dei divisori
    return true;
}

A questo punto, come per magia, ogni intero (in questo caso) nel nostro progetto (o ovunque la classe e il metodo scritti abbiano scope), avrà questo metodo aggiuntivo e si potrà invocare:
int number = 10;
Console.WriteLine(number.IsPrime());

Implementazione Esplicita vs. Normale
In realtà la cosa "strana" è rappresentata dall'implementazioni esplicita che antepone ai nomi dei metodi, l'interfaccia di riferimento, creando così un modo per nascondere i metodi alle reference dell'oggetto attuale, a meno che non lo si referenzi da una reference di tipo interfaccia, per esempio:

interface Wingable
{
    void Fly();
}
class Bird : Wingable
{
    public void Fly()
    {
        throw new NotImplementedException();
    }
}

In questo caso le righe seguenti compilano.
Bird b = new Bird();
b.Fly(); //corretto perchè l'implementazione è implicita

In quest'altro caso:
class Bird : Wingable
{   
    void Wingable.Fly()
    {
        throw new NotImplementedException();
    }
}

L'implementazione è esplicita e le righe sopra non compilano più a meno di referenziare l'oggetto attuale con un tipo Wingable:
Wingable b = new Bird();
b.Fly(); //corretto perchè l'implementazione è esplicita

Nota sul Boxing e sugli operatori is e as:
Il boxing consiste nell'incapsulare un oggetto value in un oggetto reference. Ovvero tipicamente copiarlo nello heap.
L'operatore is restituisce true se l'operando di sinistra è del tipo specificato a destra, false altrimenti.
L'operatore as fa il soft-casting dell'oggetto a sinistra al tipo specificato a destra; se il casting non va a buon fine, restituisce null.