Sotto questa riga, l'eccellente soluzione di Claudio Brotto al test "Sharp-lock Holmes". Molto bene ha condotto il ragionamento, vero? Ed eccolo inserito nel blogroll :-)
Foo.dll è un assembly .NET che definisce, almeno, i tipi A, B e C. Potrebbe essere stato generato da un qualsiasi compilatore conforme a CLS, così come da ilasm a partire da codice IL.
Essendo referenziati senza qualificatore di namespace, A, B e C devono appartenere allo stesso namespace in cui è definita D (oppure deve essere presente in precedenza una direttiva using).
A, B e C possono essere classi o interfacce. Poichè le specifiche C# impongono che, in una lista di derivazione contenente più tipi, prima venga messa la classe da cui si eredita (l'ereditarietà multipla non è supportata), quindi le interfacce che si implementano, A è una classe e B e C sono interfacce. Inoltre, A, B e C devono avere visibilità public, altrimenti non sarebbero referenziabili da un assembly esterno, quello di D, appunto.
Non avendo definito esplicitamente il costuttore di D, il compilatore C# provvede ad inserirne uno di default come segue:
public D(): base() {}
Questo implica che il costruttore di A sia, almeno, protected. Dal momento che a video viene stampata la stringa "Ciao!", sarà a questo punto il costruttore di A (o un metodo che questo chiama) ad invocare la Console.WriteLine(...).
Per quanto riguarda le interfacce e la catena di ereditarietà, il discorso è un po' più lungo. D non fa l'override di alcun metodo. Se nè B nè C definiscono metodi, no problem. Se invece ne definiscono, questo influenza A. Se A, o un suo antenato nella catena di ereditarietà, implementano gli eventuali metodi dell'interfaccia B (per C il discorso è analogo) in modo implicito, cioè ad esempio
public interface B
{
void Something() {}
}
public class A: (B)
{
public (virtual) void Something() {}
}
allora D ne eredita l'implementazione. Le chiamate a tale metodo, indipendentemente dal tipo di riferimento sul quale sono effettuate, seguono il consueto dispatch per i metodi virtuali.
Da notare che il compilatore C# non impone che un metodo di implementazione di un interfaccia sia dichiarato virtual, salvo poi inserire lui i modificatori newslot e virtual a livello di metadati della classe base.
Da notare inoltre che non è necessario che A implementi B, dal momento che già D lo fa (e l'implementazione che fornisce è quella di A). Nel caso, invece, A implementi B, l'aver incluso B nella catena di derivazione di D è superfluo.
In sunto: l'unione dei metodi definiti da B e C deve avere una corrispondente implementazione in A o in una classe da cui A deriva. Se l'unione è vuota (B e C non definiscono metodi) A e la sua catena di derivazione possono non definire metodi ... a parte quelli di Object, ovviamente.
L'esempio più breve per il codice di foo.dll (in C#) potrebbe essere questo:
public class A
{
protected A() {System.Console.WriteLine("Ciao");}
}
public interface B {}
public interface C {}
Voila!