Why is multiple inheritance possible in C++, but not in C#?
Penso (senza avere un riferimento difficile), che in Java volevano limitare l'espressività della lingua per rendere il linguaggio più facile da apprendere e perché il codice che utilizza l'ereditarietà multipla è più spesso troppo complesso per il suo bene. E poiché l'ereditarietà multipla completa è molto più complicata da implementare, quindi semplifica anche la macchina virtuale (l'ereditarietà multipla interagisce in modo particolare con il garbage collector, perché richiede il mantenimento dei puntatori nel mezzo dell'oggetto (all'inizio della base) )
E quando progettavo C # penso che guardassero a Java, ho visto che l'ereditarietà multipla non è stata trascurata e ha scelto di mantenere le cose semplici.
How does C++ resolve the ambiguity of identical method signatures inherited from multiple base classes?
È non . C'è una sintassi per chiamare esplicitamente il metodo della classe base dalla base specifica, ma non c'è modo di sovrascrivere solo uno dei metodi virtuali e se non si esegue l'override del metodo nella sottoclasse, non è possibile chiamarlo senza specificare la base classe.
And why is the same design not incorporated into C#?
Non c'è niente da incorporare.
Poiché Giorgio ha menzionato i metodi di estensione dell'interfaccia nei commenti, spiegherò quali sono i mixin e come sono implementati in varie lingue.
Le interfacce in Java e C # sono limitate ai soli metodi di dichiarazione. Ma i metodi devono essere implementati in ogni classe che eredita l'interfaccia. Esiste tuttavia un'ampia classe di interfacce, in cui sarebbe utile fornire implementazioni predefinite di alcuni metodi in termini di altri. Un esempio comune è comparabile (in pseudo-lingua):
mixin IComparable {
public bool operator<(IComparable r) = 0;
public bool operator>(IComparable r) { return r < this; }
public bool operator<=(IComparable r) { return !(r < this); }
public bool operator>=(IComparable r) { return !(r > this); }
public bool operator==(IComparable r) { return !(r < this) && !(r > this); }
public bool operator!=(IComparable r) { return r < this || r > this; }
};
La differenza dalla classe completa è che questo non può contenere alcun membro di dati. Ci sono diverse opzioni per l'implementazione di questo. Ovviamente l'ereditarietà multipla è una. Ma l'ereditarietà multipla è piuttosto complicata da implementare. Ma non è davvero necessario qui. Invece, molti linguaggi implementano questo dividendo il mixin in un'interfaccia, che è implementata dalla classe e un repository di implementazioni del metodo, che vengono iniettate nella classe stessa o una classe di base intermedia viene generata e vengono posizionate lì. Questo è implementato in Ruby e D , sarà implementato in Java 8 e può essere implementato manualmente in C ++ usando modello di modello ricorrente ricorrente . Quanto sopra, in forma CRTP, assomiglia a:
template <typename Derived>
class IComparable {
const Derived &_d() const { return static_cast<const Derived &>(*this); }
public:
bool operator>(const IComparable &r) const { r._d() < _d(); }
bool operator<=(const IComparable &r) const { !(r._d() < _d(); }
...
};
e viene usato come:
class Concrete : public IComparable<Concrete> { ... };
Questo non richiede che nulla venga dichiarato virtuale come farebbe una normale classe base, quindi se l'interfaccia viene utilizzata nei modelli lascia aperte le opzioni di ottimizzazione utili. Nota che in C ++ questo probabilmente verrebbe comunque ereditato come secondo genitore, ma in lingue che non consentono l'ereditarietà multipla è inserito nella catena di ereditarietà singola, quindi è più simile a
template <typename Derived, typename Base>
class IComparable : public Base { ... };
class Concrete : public IComparable<Concrete, Base> { ... };
L'implementazione del compilatore può o non può evitare l'invio virtuale.
È stata selezionata una diversa implementazione in C #. In C # le implementazioni sono metodi statici di classe completamente separata e la sintassi della chiamata al metodo viene interpretata in modo appropriato dal compilatore se non esiste un metodo di nome specificato, ma viene definito un "metodo di estensione". Ciò ha il vantaggio che i metodi di estensione possono essere aggiunti alla classe già compilata e lo svantaggio che tali metodi non possono essere sostituiti ad es. per fornire una versione ottimizzata.