应该C#有多重inheritance?

我遇到了许多反对在C#中包含多重inheritance的论据,其中一些包括(除了哲学论据):

  • 多inheritance过于复杂,往往含糊不清
  • 这是不必要的,因为接口提供了类似
  • 在界面不合适的情况下,构图是一个很好的替

我来自C ++背景,错过了多重inheritance的力量和优雅。 虽然它不适用于所有的软件devise,但有些情况很难否定它在接口,组合和类似的OO技术上的实用性。

是否排除了多重inheritance,认为开发者不够聪明,无法明智地使用它们,而且当它们出现时又不能解决复杂问题呢?

我个人会欢迎将多重inheritance引入到C#中(可能是C ##)。


附录 :我想知道从单一(或程序背景)与多重inheritance背景来的答复。 我经常发现没有多重inheritance经验的开发人员往往会默认为多重inheritance是不必要的,因为他们对这个范例没有任何经验。

我从来没有错过过一次。 是的,MI(MI)变得复杂了,是的,接口在很多方面做了类似的工作 – 但这并不是最大的一点:从一般意义上讲,大部分时间并不需要。 即使是单一inheritance在很多情况下也被滥用。

优先于inheritance聚合!

class foo : bar, baz 

经常用好处理

 class foo : Ibarrable, Ibazzable { ... public Bar TheBar{ set } public Baz TheBaz{ set } public void BarFunction() { TheBar.doSomething(); } public Thing BazFunction( object param ) { return TheBaz.doSomethingComplex(param); } } 

通过这种方式,您可以交换出IBarrable和IBazzable的不同实现,从而创build应用程序的多个版本,而无需另外编写一个类。

dependency injection可以帮助很多。

处理多重inheritance的一个问题是接口inheritance和实现inheritance之间的区别。

C#通过使用纯接口已经有了一个干净的接口inheritance实现(包括隐式或显式实现的select)。

如果你看看C ++,对于在class声明中的冒号后面指定的每个类,你得到的inheritancetypes由访问修饰符( privateprotectedpublic )决定。 使用publicinheritance,您将得到多重inheritance的完全混乱 – 多个接口与多个实现混合在一起。 有了privateinheritance,你只需要实现。 “ class Foo : private Barclass Foo : private Bar的对象永远不会传递给期望Bar的函数,因为它就好像Foo类真的只有一个私有Bar字段和一个自动实现的委托模式 。

纯粹的多个实现inheritance(这实际上只是自动委派)不会出现任何问题,并将在C#中真棒。

至于从类inheritance多个接口,实现这个特性有很多不同的可能的devise。 每个具有多重inheritance的语言都有自己的规则,当在多个基类中调用具有相同名称的方法时会发生什么情况。 一些语言,如Common Lisp(特别是CLOS对象系统)和Python,都有一个元对象协议,您可以在其中指定基类的优先级。

这是一个可能性:

 abstract class Gun { public void Shoot(object target) {} public void Shoot() {} public abstract void Reload(); public void Cock() { Console.Write("Gun cocked."); } } class Camera { public void Shoot(object subject) {} public virtual void Reload() {} public virtual void Focus() {} } //this is great for taking pictures of targets! class PhotoPistol : Gun, Camera { public override void Reload() { Console.Write("Gun reloaded."); } public override void Camera.Reload() { Console.Write("Camera reloaded."); } public override void Focus() {} } var pp = new PhotoPistol(); Gun gun = pp; Camera camera = pp; pp.Shoot(); //Gun.Shoot() pp.Reload(); //writes "Gun reloaded" camera.Reload(); //writes "Camera reloaded" pp.Cock(); //writes "Gun cocked." camera.Cock(); //error: Camera.Cock() not found ((PhotoPistol) camera).Cock(); //writes "Gun cocked." camera.Shoot(); //error: Camera.Shoot() not found ((PhotoPistol) camera).Shoot();//Gun.Shoot() pp.Shoot(target); //Gun.Shoot(target) camera.Shoot(target); //Camera.Shoot(target) 

在这种情况下,只有第一个列出的类的实现在冲突的情况下隐式地inheritance。 其他基本types的类必须明确指定才能实现。 为了使其更加白痴,编译器可以在冲突的情况下禁止隐式inheritance(冲突的方法总是需要强制转换)。

另外,你可以在C#中用隐式转换运算符实现多重inheritance:

 public class PhotoPistol : Gun /* ,Camera */ { PhotoPistolCamera camera; public PhotoPistol() { camera = new PhotoPistolCamera(); } public void Focus() { camera.Focus(); } class PhotoPistolCamera : Camera { public override Focus() { } } public static Camera implicit operator(PhotoPistol p) { return p.camera; } } 

然而,这不是完美的,因为它不被isas操作符以及System.Type.IsSubClassOf()

这是一个非常有用的情况下,我一直在进行多重inheritance。

作为一个工具包供应商,我不能改变发布的API,否则我会打破向后兼容性。 有一件事情是因为一旦我释放了接口,就无法添加接口,因为任何实现它的人都会中断编译 – 唯一的select是扩展接口。

对于现有的客户来说,这是好事,但是新的会让这个层次结构变得不必要的复杂,如果我从一开始就devise它,我不会select这样实现它 – 否则我会失去向后兼容性。 如果接口是内部的,那么我只需添加它并修复实现器。

在很多情况下,接口的新方法有一个明显的和小的默认实现,但是我不能提供它。

我宁愿使用抽象类,然后当我必须添加一个方法,添加一个虚拟的默认实现,有时我们这样做。

这个问题当然是,如果这个类可能被混合到已经扩展的东西 – 那么我们别无select,只能使用接口和处理扩展接口。

如果我们认为我们遇到了这个问题,我们可以select一个丰富的事件模型 – 我认为这可能是C#中的正确答案,但并不是每个问题都可以这样解决 – 有时您需要一个简单的公共接口而对于扩展者则更为丰富。

C#支持单inheritance,接口和扩展方法。 在它们之间,它们提供了多重inheritance提供的一切,而没有多重inheritance带来的麻烦。

CLR不以任何方式支持多重inheritance,所以我怀疑它可以以一种有效的方式得到支持,因为它在C ++(或Eiffel中,考虑到语言是专门devise的为MI)。

一个很好的替代多重inheritance被称为特质。 它可以让你将各种行为单元混合在一起。 编译器可以支持traits作为单inheritancetypes系统的编译时扩展。 您只需声明X类包含特征A,B和C,编译器将所要求的特征放在一起形成X的实现。

例如,假设您正在尝试实施IList(T)。 如果你看看IList(T)的不同实现,他们经常会分享一些完全相同的代码。 这就是特性的问题。你只要声明一个具有通用代码的特性,即使实现已经有一些其他的基类,你也可以在IList(T)的任何实现中使用这个通用代码。 这是什么语法可能是这样的:

 /// This trait declares default methods of IList<T> public trait DefaultListMethods<T> : IList<T> { // Methods without bodies must be implemented by another // trait or by the class public void Insert(int index, T item); public void RemoveAt(int index); public T this[int index] { get; set; } public int Count { get; } public int IndexOf(T item) { EqualityComparer<T> comparer = EqualityComparer<T>.Default; for (int i = 0; i < Count; i++) if (comparer.Equals(this[i], item)) return i; return -1; } public void Add(T item) { Insert(Count, item); } public void Clear() { // Note: the class would be allowed to override the trait // with a better implementation, or select an // implementation from a different trait. for (int i = Count - 1; i >= 0; i--) RemoveAt(i); } public bool Contains(T item) { return IndexOf(item) != -1; } public void CopyTo(T[] array, int arrayIndex) { foreach (T item in this) array[arrayIndex++] = item; } public bool IsReadOnly { get { return false; } } public bool Remove(T item) { int i = IndexOf(item); if (i == -1) return false; RemoveAt(i); return true; } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } IEnumerator<T> GetEnumerator() { for (int i = 0; i < Count; i++) yield return this[i]; } } 

你用这样的特质:

 class MyList<T> : MyBaseClass, DefaultListMethods<T> { public void Insert(int index, T item) { ... } public void RemoveAt(int index) { ... } public T this[int index] { get { ... } set { ... } } public int Count { get { ... } } } 

当然,我只是在这里抓表面。 有关更完整的描述,请参阅“ 性状:可组合行为单位” (PDF)。

Rust语言(来自Mozilla)以一种有趣的方式实现了Traits:他们注意到特性类似于默认的接口实现,所以他们将接口和特性统一为一个特性(他们称之为特性)。 特征和默认接口实现(现在是Java)之间的主要区别是,特征可以包含私有或受保护的方法,这与传统的必须公开的接口方法不同。 如果特征和接口没有统一成一个特征,那么另一个区别就是你可以有一个接口的引用,但是你不能引用一个特征; 特质本身不是一种types。

我实际上错过了一个具体的原因多重inheritance…处置模式。

每次我需要实现这个configuration模式的时候,我都会对自己说:“我希望我能从一个实现了configuration模式的类中派生出几个虚拟的覆盖。” 我复制并粘贴相同的锅炉代码到每个实现IDispose的类,我恨它。

仅仅因为你说的原因,我会反对多重inheritance。 开发人员会滥用它。 我已经看到每一个从工具类inheritance的类都有足够的问题,所以你可以从每个类调用一个函数,而不需要input太多,知道在很多情况下多重inheritance会导致错误的代码。 GoTo也可以说是同样的事情,这是它使用的原因之一是如此皱眉。 我认为多重inheritance和GoTo一样有很好的用处,在一个理想的世界里,只有在适当的时候才使用它们,那么就没有问题了。 但是,世界并不理想,所以我们必须保护不好的程序员。

是! 是! 是的!

说真的,我一直在开发GUI库,MI(多重inheritance)使得这个FAR比SI(单一inheritance)更容易,

首先,我在C ++中使用了SmartWin ++ (MI被大量使用),然后我做了Gaia Ajax,最后是Ra-Ajax ,我可以用非常自信的状态来说明MI在某些地方的规则。 其中一个地方是GUI库…

而且这个说法“太复杂”了,大多数人都是在试图构build语言战争的人们身上,恰巧属于“目前没有心肌梗死”的阵营。

就像函数式编程语言(如Lisp)已经被非函数式编程语言倡导者所教导(由“非Lispers”)被称为“太复杂”。

人们害怕未知的…

MI规则!

我很高兴C#没有多重inheritance,尽pipe它有时会很方便。 我想看到的是能够提供接口方法的默认实现。 那是:

 interface I { void F(); void G(); } class DefaultI : I { void F() { ... } void G() { ... } } class C : I = DefaultI { public void F() { ... } // implements IF } 

在这种情况下, ((I)new C()).F()将调用CIF()的实现,而((I)new C()).G()将调用DefaultIIG()

语言devise者在将这些语言添加到语言之前,必须先解决一些问题,但是没有一个是非常困难的,其结果将涵盖使多重inheritance成为可取的许多需求。

从C#开始,我一直在使用它作为alpha / beta版本,并且从来没有错过多重inheritance。 MI对于某些事情来说很好,但是几乎总是有其他方法来达到相同的结果(其中一些实际上最终变得更简单或者创build更容易理解的实现)。

一般而言,多重inheritance是有用的,许多OO语言都以这种或那种方式实现(C ++,Eiffel,CLOS,Python …)。 这是必要的吗? 不,有,好吗? 是。

更新
我向所有投票给我的人挑战,向我展示任何多重inheritance的例子,我不能轻易将其转换为具有单一inheritance的语言。 除非任何人可以显示任何这样的样本,我声称它不存在。 我已经将大量的C ++代码(MH)移植到Java(无MH)中,而且不pipe这个C ++代码使用了多lessMH,这都不是问题。


迄今为止,没有人可以certificate多重inheritance与您在文章中提到的其他技术(使用接口和委托我可以得到完全相同的结果,没有太多的代码或开销) 相比有任何优势 ,而它有一些众所周知的缺点( 钻石问题是最讨厌的问题)。

实际上多重inheritance通常被滥用。 如果您使用OOdevise以某种方式将现实世界build模为类,那么您将永远无法达到多重inheritance实际上有意义的地步。 你能提供一个有用的多重inheritance的例子吗? 到目前为止,我所看到的大多数例子其实都是“错误的”。 他们把某个东西做成一个子类,实际上只是一个额外的属性,因此实际上是一个接口。

看看Sather 。 它是一种编程语言,接口具有多重inheritance,为什么不是(它不能创build钻石问题),但是没有接口的类没有任何inheritance。 他们只能实现接口,并且可以“包含”其他对象,这些其他对象使得这些对象成为其中的一个固定部分,但这与inheritance是不一样的,而是一种委托forms(包含对象的方法调用“inheritance”实际上只是被转发到封装在你的对象中的这些对象的实例)。 我认为这个概念非常有趣,它表明你可以有一个完整的干净的OO语言,根本没有任何实现inheritance。

没有。

(用于投票)

DataFlex 4GL v3 +(我知道,我知道,数据什么)的真正好的和(当时的)新颖的东西是它支持mixininheritance – 来自任何其他类的方法可以在你的类中重用; 只要你的课程提供了这些方法使用的属性,它就行得通,而且没有“钻石问题”或其他多重inheritance问题。

我想在C#中看到这样的事情,因为它会简化某些types的抽象和build造问题

而不是多重inheritance,你可以使用mixin ,这是一个更好的解决scheme。

我认为如果没有提供足够的投资回报率,这会使事情复杂化。 我们已经看到人们对.NET代码的inheritance树太深了。 如果人们有权力做多重inheritance,我可以想象这些暴行。

我不否认它有潜力,但我看不到足够的好处。

虽然确实有用处,但我发现大部分时候我觉得我需要它,但实际上并不是这样。

一位同事在这个博客中写了关于如何在C#中使用dynamic编译来获取类似多重inheritance的内容:

http://www.atalasoft.com/cs/blogs/stevehawley/archive/2008/09/29/late-binding-in-c-using-dynamic-compilation.aspx

我觉得它很简单。 就像任何其他复杂的编程范例一样,你可以滥用它并伤害自己。 你可以误用物体(哦,是!),但这并不意味着OO本身就不好。

与MI类似。 如果你没有一个大型的inheritance类的“树”,或者很多提供了相同命名方法的类,那么MI将会是完美的。 事实上,随着MI提供的实现,你通常会比SI实现更好,你必须重新编写代码,或者把方法剪切并粘贴到委托对象上。 在这种情况下,代码越less越好。通过尝试通过接口inheritance来重用对象,可以使代码共享变得无所适从。 而这样的解决方法并不正确。

我认为.NET的单一inheritance模型是有缺陷的:它们应该只用接口,或者只用MI。 具有“一半和一半”(即单个实现inheritance加上多个接口inheritance)比它应该是更混乱,而且不完美。

我来自心理学背景,我并不害怕或被它烧毁。

我已经在这里发布了几次,但我只是觉得它很酷。 你可以在这里学习如何伪造MI。 我也认为这篇文章强调了为什么心肌梗死是如此痛苦,即使那不是打算。

我既不想念也不需要它,我更喜欢用物体的组合来达到目的。 这也是文章的重点。

我自己也在C ++中使用了多重inheritance,但是为了不让自己陷入困境,特别是如果你有两个共享一个祖父母的基类,你必须知道自己在做什么。 然后你可以进入虚拟inheritance的问题,不得不声明你将要调用链的每个构造函数(这使得二进制重用更困难)… 可能是一团糟。

更重要的是,目前CLI的构build方式妨碍了MI的实施。 我敢肯定,如果他们愿意的话,他们可以做到,但是我更希望在CLI中看到其他的东西,而不是多重inheritance。

我希望看到的东西包括Spec#的一些function,如不可空的引用types。 我还希望通过能够将参数声明为const来声明更多的对象安全性,并声明一个函数const(这意味着你保证一个对象的内部状态不会被方法改变,编译器双重检查你)。

我认为在单inheritance,多接口inheritance,generics和扩展方法之间,你可以做任何你需要的事情。 如果有什么东西可以改善某些人对心肌梗死的要求,我认为某种语言结构将会使得更容易的聚集和组合成为必要。 这样你可以有一个共享的接口,但是然后把你的实现委托给你通常inheritance的类的私有实例。 现在,这需要大量的锅炉板代码。 有一个更自动化的语言function,将有助于显着。

我更喜欢C ++。 我已经使用了Java,C#等。当我的程序在这样的OO环境中变得更复杂时,我发现自己错过了多重inheritance。 这是我的主观经验。

它可以使惊人的意大利面代码…它可以使令人惊叹的优雅的代码。

我相信像C#这样的语言应该给程序员一个select。 只是因为它可能太复杂,并不意味着它太复杂。 编程语言应该为开发人员提供构build程序员想要的东西的工具。

你select使用那些已经写好的开发者API,你也没有。

给C#的含义 ,你不会错过多重inheritance,或任何inheritance。

不,我没有。 我使用所有其他OOfunction来开发我想要的。 我使用接口和对象封装,我从来没有限制我想要做什么。

我尽量不要使用inheritance。 我可以每次都less。

否,除非钻石问题得到解决。 你可以使用构图,直到这不解决。

不,我们离开了。 你现在需要它。

如果我们引入多重inheritance,那么我们再次面对C ++的旧钻石问题…

然而,对于那些认为这是不可避免的,我们仍然可以引入多种inheritance效果的组成(在一个对象中组合多个对象,并公开将委托责任的公共方法,以组成的对象和返回)…

So why bother to have multiple inheritance and make your code vulnerable to unavoidable exceptions…