为什么C#允许通过接口扩展方法而不是类来进行多重继承?

时间:2012-04-02 21:18:24

标签: c# inheritance extension-methods multiple-inheritance

我已经检查了其他问题,而且令人惊讶的是,似乎没有问过这个问题。使用Extension方法,接口提供有限但真正的实现多重继承。这带来了Diamond问题,与基于类的多重继承相同。为什么这比基于类的多重继承更好或更可接受,这么多人似乎觉得这么可怕?它实际上似乎是实现多重继承的一种更糟糕的方式,因为扩展方法不能进入接口本身,甚至不能进入实现接口的类,但最终可能分散在多个静态实用程序类中。

Eric Lippert在他的博客(2009年10月5日上午9:29)似乎对扩展属性的概念持开放态度,甚至提到了扩展事件,扩展操作符,扩展构造函数(也称为“工厂模式”)的可能性。因此,可以进一步扩展通过接口的实现。

编辑:为了澄清一个类是否继承了两个接口,这两个接口都具有相同名称和类型参数的扩展方法,如果在没有明确命名的情况下调用方法,则会产生编译错误界面。考虑到这一点,我错了,因为这不是钻石问题。然而,考虑到这一点提出了一个问题,即钻石问题与其他歧义相比有多重要?为什么Diamond问题是如此困难,以至于它不能用简单的编译错误来获取,就像接口扩展方法类碰撞并且不能隐式解析一样?即使在基于类的多重继承中,也可能存在非基于Diamond的成员签名冲突。

4 个答案:

答案 0 :(得分:13)

  

使用扩展方法,接口提供有限但真实的实现多重继承。

这句话是整个问题的基础,但我无法做出正面或反面,因此很难回答这个问题。

首先,让我们清楚地定义“继承”。当我们说类型D继承自类型B时,我们的意思是 B的每个成员也是D†的成员。那是所有,我们的意思是C#中的“继承”。

类(或结构)从正好一个(††)基类继承成员。一个类可以实现任意数量的接口;这与基类继承完全不同。类不需要具有与其实现的接口相同的成员集合,因为该类可以使用显式接口实现来提供实现,而无需成为班级。显式接口实现只能通过接口访问,并且不能以任何其他方式访问,因此将它视为从接口“继承”的类的“成员”会很奇怪。

接口从任意数量的其他接口“继承”成员。从技术上讲,这可以被认为是继承;基接口的成员是派生接口的成员。但我希望我们没有在规范中描述它;我认为说接口不从基接口继承会更清楚;相反,接口可以要求实现其他接口作为其合同的一部分。

既然我们已经解决了这个问题,那么扩展方法呢?扩展方法不是任何类型的继承;扩展的类型不会获得任何新成员。扩展方法只是一种更愉快地编写对静态方法的调用的方法。

  

这带来了Diamond问题,与基于类的多继承相同

目前还不清楚该句中“这个”是指什么。你指的是(1)实现多个接口的类,(2)从多个接口继承的接口,或者(3)关于扩展方法的东西,或者(4)完全不同的东西?我不明白钻石问题与你的问题有什么关系。你能澄清一下吗?

  

为什么这比基于类的多重继承更好或更可接受,这么多人似乎觉得这么可怕?

为什么更好

我根本不理解这个问题,但似乎在某处有某种有用的问题。你能澄清一下这个问题吗?最好用一些简短的示例代码来演示您正在谈论的内容。


†不是每个成员。例如,构造函数和析构函数是成员,但不是可继承的成员。私有成员 已继承,但可能无法通过名称​​访问

††除object以外,它继承自零类。每个其他类都从一个类继承。

答案 1 :(得分:8)

任何实现多重继承的扩展方法的出现完全是一种错觉。他们没有。

扩展方法是简单的编译器技巧。它们编译为普通的静态方法,其外观和工作方式与从第一个参数中删除this时一样。

考虑:

myObj.Extension();
...
public static class MyExtension
{
    public static void Extension(this MyObj myobj)

调用扩展名等同于:

MyExtension.Extension(myObj);

当然,您甚至可以在代码中将其称为。<​​/ p>

答案 2 :(得分:5)

C#类实现的接口列表是 flattened ,因此当一个类通过它实现的多个接口继承它来实现接口时,该类需要提供的实现数量仍然存在之一。

例如,如果一个类实现了两个都继承自IDisposable的接口,那么该类仍然只需要实现Dispose()一次。这与C ++形成对比,C ++需要通过多个非虚拟继承路径从相同基类继承的函数单独覆盖。

扩展方法与此问题正交,因为它们提供的实现无法覆盖。我写了blog post关于扩展方法及其在“横向”共享实现中的作用。我认为它们是一种以完全独立于通过类继承获得的“垂直”实现共享的方式提供功能的机制。

答案 3 :(得分:2)

扩展方法只是美化静态方法,看起来像调用时的实例方法(如果调用者选择的话)。

您所描述的情况不会发生,因为编译器会将调用标记为不明确的:

interface I1 { }

interface I2 { }

class C : I1, I2 { }

static class Ex1 {
  public static void M(this I1 self) { }
}
static class Ex2 {
  public static void M(this I2 self) { }
}

...

new C().M(); // ERROR: The call is ambiguous

扩展方法仅在导入包含带有当前上下文中的扩展方法的静态类的命名空间时生效(通过using指令);或者如果您的声明与它们位于相同的命名空间中。因此,即使可以创建不明确的声明,如果将它们添加到不同的命名空间,那么调用者只能通过导入所需的命名空间来消除歧义。

此外,要消除歧义,调用者可以将该方法作为常规静态方法调用:

Ex1.M(new C()); // not ambiguous anymore

或者你可以转换到适当的界面:

((I1)new C()).M(); // not ambiguous anymore

所以,这并不是说你“继承”必须在声明时解决的冲突成员,你有两个,你必须告诉编译器你想在调用时使用哪一个。

旁注:我发现这种扩展接口的能力是一种在C#中创建mixin形式的有趣方式。我以前写过这篇文章,例如here