为什么C#允许通过接口扩展方法而不是类进行多重继承
本文关键字:多重继承 方法 许通过 接口 扩展 为什么 | 更新日期: 2023-09-27 18:29:43
我检查了其他问题,令人惊讶的是,这个问题似乎没有被问到。使用Extension方法,接口提供有限但真实的实现多重继承。这带来了Diamond问题,与基于类的多重继承问题相同。为什么这比许多人觉得如此可怕的基于类的多重继承更好或更容易接受?实际上,实现多重继承似乎是一种更糟糕的方式,因为扩展方法不能进入接口本身,甚至不能进入实现接口的类,但最终可能分散在多个静态实用程序类上。
Eric Lippert在他的博客(2009年10月5日上午9:29)中似乎对扩展属性的想法持开放态度,甚至提到了扩展事件、扩展运算符、扩展构造函数(也称为"工厂模式")的可能性。因此,通过接口的实现可以进一步扩展。
编辑:若要澄清一个类是否从两个接口继承,而这两个接口都具有相同名称和类型参数的扩展方法,则如果调用方法时未显式命名接口,则会产生编译错误。考虑到这一点,我错了,因为这不是钻石的问题。然而,考虑到这一点,就会产生这样一个问题:与其他模棱两可的问题相比,钻石问题的重要之处是什么?为什么Diamond问题如此困难,以至于它不能像接口扩展方法类冲突时那样,通过一个简单的编译错误来解决,并且无法隐式解决?即使在基于类的多重继承中,也可能存在非基于Diamond的成员签名冲突。
通过扩展方法,接口提供了有限但真实的实现多重继承。
这句话是整个问题的基础,但我无法理解,所以很难回答这个问题。
首先,让我们明确定义"继承"。当我们说D型继承自B型时,我们的意思是B的每个成员也是D†的成员。这就是我们在C#中所说的"继承"。
一个类(或结构)恰好从一个(††)基类继承成员。一个类可以实现任意数量的接口;这与基类继承大不相同。类不需要具有与其实现的接口相同的成员集,因为该类可以使用显式接口实现来提供实现,而不需要成为该类的成员。显式接口实现只能通过接口访问,不能以任何其他方式访问,因此将其视为从接口"继承"的类的"成员"会很奇怪。
一个接口从任意数量的其他接口"继承"成员。从技术上讲,这可以被认为是继承;基本接口的成员是派生接口的成员。但我希望我们没有在说明书中这样描述它;我认为应该更清楚地说,接口不是从基本接口继承的;相反,一个接口可以要求实现其他接口作为其合同的一部分。
既然我们已经解决了这个问题,那么扩展方法呢?扩展方法不是任何类型的继承;扩展的类型没有获得任何新成员。扩展方法只是一种更愉快地编写对静态方法的调用的方法。
这带来了Diamond问题,与基于类的多重继承相同
目前尚不清楚这句话中的"this"指的是什么。你指的是(1)实现多个接口的类,(2)从多个接口继承的接口,还是(3)关于扩展方法的东西,或者(4)完全是其他东西?我不明白钻石问题和你的问题有什么关系。你能澄清一下吗?
为什么这比许多人觉得如此可怕的基于类的多重继承更好或更容易接受?
为什么什么更好?
我一点也不理解这个问题,但这里似乎有一些有用的问题。你能澄清这个问题吗?最好使用一些简短的示例代码来演示您正在谈论的内容。
†并非所有成员。例如,构造函数和析构函数是成员,但不是可继承的成员。私有成员是继承的,但可能无法通过名称访问。
††除了从零类继承的object
。每个其他类都继承自一个类。
任何实现多重继承的扩展方法的出现都完全是一种幻觉。他们没有。
扩展方法是简单的编译器技巧。它们编译为普通的旧静态方法,这些方法的外观和工作方式与从第一个参数中删除this
时相同。
考虑:
myObj.Extension();
...
public static class MyExtension
{
public static void Extension(this MyObj myobj)
调用扩展相当于:
MyExtension.Extension(myObj);
当然,您甚至可以在代码中这样调用它。
C#类实现的接口列表是扁平化的,因此当一个类通过其实现的多个接口继承接口来实现接口时,该类需要提供的实现数量仍然是一个。
例如,如果一个类实现了两个接口,这两个接口都继承自IDisposable
,那么该类仍然只需要实现Dispose()
一次。这与C++形成对比,在C++中,通过多个非虚拟继承路径从同一基类继承的函数需要单独重写。
扩展方法与这个问题正交,因为它们提供的实现不能被覆盖。我写了一篇关于扩展方法及其在"横向"共享实现中的作用的博客文章。我认为它们是一种提供功能的机制,完全独立于通过类继承获得的"垂直"实现共享。
扩展方法只是美化的静态方法,在调用时看起来像实例方法(如果调用者选择的话)。
您所描述的情况不会发生,因为编译器会将调用标记为不明确:
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
因此,这并不是说你"继承"了必须在声明时解决的冲突成员,你有两个可供选择,你必须告诉编译器你想在call时间使用哪一个。
附带说明:我发现这种扩展接口的能力是在C#中创建一种形式的mixin的一种有趣的方式。我以前写过关于它的文章,例如,在这里。