Por que a herança múltipla é possível em C ++, mas não em C #?
Penso (sem ter uma referência rígida), que em Java eles queriam limitar a expressividade da linguagem para facilitar a aprendizagem e porque o código que usa herança múltipla costuma ser muito complexo para seu próprio bem. E como a herança múltipla completa é muito mais complicada de implementar, também simplificou muito a máquina virtual (herança múltipla interage especialmente mal com o coletor de lixo, porque requer manter os ponteiros no meio do objeto (no início da base) )
E, ao projetar o C #, acho que eles observaram o Java, viram que a herança múltipla completa realmente não foi perdida e optaram por manter as coisas simples também.
Como o C ++ resolve a ambiguidade de assinaturas de método idênticas herdadas de várias classes base?
Isso não acontece . Há uma sintaxe para chamar explicitamente o método da classe base de base específica, mas não há como substituir apenas um dos métodos virtuais e, se você não substituir o método na subclasse, não será possível chamá-lo sem especificar a base classe.
E por que o mesmo design não foi incorporado ao C #?
Não há nada a incorporar.
Como Giorgio mencionou métodos de extensão de interface nos comentários, explicarei o que são mixins e como eles são implementados em vários idiomas.
As interfaces em Java e C # são limitadas apenas a métodos de declaração. Mas os métodos precisam ser implementados em cada classe que herda a interface. No entanto, existe uma grande classe de interfaces, na qual seria útil fornecer implementações padrão de alguns métodos em termos de outros. Exemplo comum é comparável (em pseudo-idioma):
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; }
};
A diferença da classe completa é que isso não pode conter nenhum membro de dados. Existem várias opções para implementar isso. Obviamente, herança múltipla é uma. Mas a herança múltipla é bastante complicada de implementar. Mas não é realmente necessário aqui. Em vez disso, muitas linguagens implementam isso dividindo o mixin em uma interface, implementada pela classe e um repositório de implementações de métodos, que são injetados na própria classe ou é gerada uma classe base intermediária e são colocadas lá. Isso é implementado em Ruby e D , será implementado em Java 8 e pode ser implementado manualmente em C ++ usando o padrão de modelo curiosamente recorrente . O acima, no formato CRTP, se parece com:
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 é usado como:
class Concrete : public IComparable<Concrete> { ... };
Isso não requer que nada seja declarado virtual como a classe base regular exigiria, portanto, se a interface for usada em modelos, deixa opções úteis de otimização em aberto. Observe que, em C ++, isso provavelmente ainda seria herdado como segundo pai, mas em idiomas que não permitem herança múltipla, ele é inserido na cadeia de herança única, por isso é mais parecido com
template <typename Derived, typename Base>
class IComparable : public Base { ... };
class Concrete : public IComparable<Concrete, Base> { ... };
A implementação do compilador pode ou não evitar o despacho virtual.
Uma implementação diferente foi selecionada em C #. Em C #, as implementações são métodos estáticos de classe completamente separada e a sintaxe da chamada de método é adequadamente interpretada pelo compilador se um método de nome determinado não existir, mas um "método de extensão" for definido. Isso tem a vantagem de que métodos de extensão podem ser adicionados à classe já compilada e a desvantagem de que tais métodos não podem ser substituídos, por exemplo, para fornecer uma versão otimizada.