Perché l'ereditarietà multipla è possibile in C ++, ma non in C #?
Penso (senza avere un forte riferimento) che in Java volevano limitare l'espressività della lingua per rendere la lingua più facile da imparare 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 ha semplificato molto anche la macchina virtuale (l'ereditarietà multipla interagisce particolarmente male con Garbage Collector, poiché richiede di mantenere i puntatori nel mezzo dell'oggetto (all'inizio della base) )
E durante la progettazione di C # penso che abbiano guardato Java, visto che l'eredità multipla completa non è mancata molto ed è stata scelta per rendere le cose semplici.
In che modo C ++ risolve l'ambiguità di firme di metodi identici ereditate da più classi di base?
Lo fa non . Esiste una sintassi per chiamare esplicitamente il metodo della classe di base da una base specifica, ma non c'è modo di sovrascrivere solo uno dei metodi virtuali e se non si sostituisce il metodo nella sottoclasse, non è possibile chiamarlo senza specificare la base classe.
E perché lo stesso design non è incorporato in C #?
Non c'è niente da incorporare.
Poiché Giorgio ha menzionato i metodi di estensione dell'interfaccia nei commenti, spiegherò cosa 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. L'esempio comune è comparabile (in pseudo-linguaggio):
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 rispetto alla classe completa è che questo non può contenere alcun membro di dati. Esistono diverse opzioni per implementarlo. Ovviamente l'eredità multipla è una. Ma l'ereditarietà multipla è piuttosto complicata da implementare. Ma non è davvero necessario qui. Invece, molti linguaggi implementano ciò suddividendo il mixin in un'interfaccia, che è implementata dalla classe e da un repository di implementazioni di metodi, che vengono iniettati nella classe stessa o viene generata una classe base intermedia e vengono posizionati lì. Questo è implementato in Ruby e D , sarà implementato in Java 8 e può essere implementato manualmente in C ++ usando il modello di modello curiosamente 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(); }
...
};
ed è usato come:
class Concrete : public IComparable<Concrete> { ... };
Ciò non richiede nulla per essere dichiarato virtuale come farebbe una normale classe base, quindi se l'interfaccia utilizzata nei modelli lascia aperte utili opzioni di ottimizzazione. Si noti che in C ++ questo probabilmente sarebbe ancora ereditato come secondo genitore, ma in linguaggi che non consentono l'ereditarietà multipla è inserito nella catena dell'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 meno evitare l'invio virtuale.
Una diversa implementazione è stata selezionata in C #. In C # le implementazioni sono metodi statici di classe completamente separata e la sintassi della chiamata del metodo viene interpretata in modo appropriato dal compilatore se non esiste un metodo con un determinato nome, 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 esempio per fornire una versione ottimizzata.