何时应该在C#中使用默认接口方法?

15
在C# 8及以后的版本中,我们拥有默认接口方法,因此:
这难道不破坏了接口的原则吗?
何时应该使用默认接口方法而不是基类(抽象类)

1
默认接口方法背后的理念是可扩展性。假设您已经有了一个代码库。现在,一个新的功能请求需要您的其中一个接口包含一个新方法。如果没有默认接口方法,您将会遇到很大的麻烦。现在,您可以添加新方法,在需要的地方实现它,并且仍然可以让预先存在的代码正常工作而不需要进行更改。 - Fildor
1
这个问题很难,甚至在 Stack Overflow 上回答也是不可能的,因为它将基于观点。你问是否是一个“好”主意,你问是否有做其他事情的“更好”的主意。如果你先检查一下 SO 上的问题和答案是否已经涵盖了你的问题,如果没有,重写问题以减少基于观点的内容,那会更好。 - Lasse V. Karlsen
1
这个问题已经被问过了(针对Java而不是C#,但原则上基本相同)在软件工程上,并且有一些相对详细的答案(像这样的理论/设计问题通常更适合SE)。还有许多文章回答了这个问题,例如Red Hat的博客,类似于@Fildor的评论,指出它们“提供了一种扩展接口的方式……而不会破坏之前的实现者”。 - Salem
1
如果您查看“correct”标签的描述,您会找到解释和示例。不,它不会破坏接口,也不类似于基类。DIM用于版本控制、特征和与已经使用默认方法的平台(如Android SDK)的互操作性。 - Panagiotis Kanavos
2个回答

23

为什么我们需要接口?

从理论上讲,接口实现和类继承都解决了同样的问题:它们允许您在类型之间定义子类型关系

那么为什么在C#中我们需要同时拥有它们?为什么我们需要接口?难道我们不能像在C++中一样将接口定义为抽象类吗?

原因是菱形继承问题(图片来源)

enter image description here

如果B和C都以不同的方式实现A.DoSomething(),那么D应该继承哪个实现呢?这是一个难题,Java和C#的设计者决定通过仅允许多重继承特殊基类型(它们不包括任何实现)来避免这个问题。他们决定将这些特殊的基类型称为接口。
因此,并没有“接口原则”。接口只是解决特定问题的“工具”。
那么为什么我们需要默认实现呢?
向后兼容性。您编写了一个被全球数千开发人员使用的非常成功的库。您的库包含一些接口I,现在您决定需要额外的方法M。问题在于:
您无法将另一个方法M添加到I中,因为那会破坏实现I的现有类(因为它们不实现M),并且
您无法将I更改为抽象基类,因为这也会破坏实现I的现有类,并且您将失去进行多重继承的能力。
那么默认实现如何避免钻石问题呢?
通过不继承这些默认方法(示例源自this article中的例子,请参阅完整文章以获取一些有趣的边角案例)。
interface I1
{
    void M() { Console.WriteLine("I1.M"); } // default method
}

interface I2
{
    void M() { Console.WriteLine("I2.M"); } // default method
}

class C : I1, I2 { }

class Program
{
    static void Main(string[] args)
    {
        // c, i1 and i2 reference the same object
        C c = new C();
        I1 i1 = c;
        I2 i2 = c;

        i1.M(); // prints "I1.M"
        i2.M(); // prints "I2.M"
        c.M();  // compile error: class 'C' does not contain a member 'M'
    }
}

4
我读过的最清晰简单的解释之一。 - BenKoshy
2
@Heinzi 从菱形问题开始讲起,使得答案更有趣,感谢你花时间写这么全面的答案。 - Hassan Monjezi

5
文档中所解释的那样,其主要应用有两种:

扩展现有API

虚拟扩展方法使API作者能够在未来版本中向接口添加方法,而不会破坏对该接口的现有实现的源或二进制兼容性。

因此,它旨在扩展API(一组接口)而无需更新所有现有实现(因为它们需要实现新添加的函数)。

特征模式

特征模式允许使用多组预实现方法扩展类。这在抽象类中是不可能的:给定的类只能继承自一个父类,但可以从多个接口中继承。

请注意,除了这些特定应用之外,此功能不应用于替代抽象类,因为其中存在钻石继承问题


4
DIM(默认接口方法)与普通接口一样,并不会存在钻石继承问题。对于普通接口,显式接口实现可以避免这个问题。DIMs的调用方式与显式接口实现相同。 - Panagiotis Kanavos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接