我们什么时候应该在 C# 中使用默认接口方法?
When should we use default interface method in C#?
在 C# 8 及更高版本中,我们有 default interface methods,因此:
这不是破坏了接口的原理吗?
我们什么时候应该使用默认接口方法而不是base(抽象)class?
如 documentation 中所述,有两个主要应用程序:
扩展现有 API
Virtual extension methods enable an API author to add methods to an interface in future versions without breaking source or binary compatibility with existing implementations of that interface.
因此,它旨在作为一种扩展 API(= 一组接口)而无需更新所有现有实现(因为它们需要实现新添加的功能)的方法。
traits pattern
traits 模式允许使用多组预实现方法扩展 class。这对于抽象 classes 是不可能的:给定的 class 只能继承自单个父 class,但可以继承多个接口。
请注意,此功能随 diamond inheritance problem 一起提供。因此,除了这些特定的应用程序之外,不应使用此功能来替代抽象 classes.
为什么我们有接口?
从理论上讲,接口实现和class继承都解决了同一个问题:它们允许你在类型之间定义一个subtype relationship。
那么为什么我们在 C# 中同时拥有这两者?为什么我们需要接口?我们不能像在 C++ 中那样将接口定义为抽象 class 吗?
原因是the diamond problem: (Image source)
如果B
和C
实现A.DoSomething()
的方式不同,D
应该继承哪个实现?这是一个难题,Java 和 C# 设计者决定通过仅允许不包含任何实现的特殊基类型进行多重继承来避免它。他们决定将这些特殊的基本类型称为 interfaces.
所以,没有“接口原理”。接口只是解决特定问题的“工具”。
那么为什么我们需要默认实现?
向后兼容。您编写了一个非常成功的库,被全球数以千计的开发人员使用。您的库包含一些接口 I
,现在您决定需要一个额外的方法 M
。问题是:
- 您不能将另一个方法
M
添加到 I
,因为这会破坏现有的 classes 实现 I
(因为它们没有实现 M
), 和
- 您不能将
I
更改为抽象基础 class,因为那样也会破坏现有的 classes 实现 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'
}
}
在 C# 8 及更高版本中,我们有 default interface methods,因此:
这不是破坏了接口的原理吗?
我们什么时候应该使用默认接口方法而不是base(抽象)class?
如 documentation 中所述,有两个主要应用程序:
扩展现有 API
Virtual extension methods enable an API author to add methods to an interface in future versions without breaking source or binary compatibility with existing implementations of that interface.
因此,它旨在作为一种扩展 API(= 一组接口)而无需更新所有现有实现(因为它们需要实现新添加的功能)的方法。
traits pattern
traits 模式允许使用多组预实现方法扩展 class。这对于抽象 classes 是不可能的:给定的 class 只能继承自单个父 class,但可以继承多个接口。
请注意,此功能随 diamond inheritance problem 一起提供。因此,除了这些特定的应用程序之外,不应使用此功能来替代抽象 classes.
为什么我们有接口?
从理论上讲,接口实现和class继承都解决了同一个问题:它们允许你在类型之间定义一个subtype relationship。
那么为什么我们在 C# 中同时拥有这两者?为什么我们需要接口?我们不能像在 C++ 中那样将接口定义为抽象 class 吗?
原因是the diamond problem: (Image source)
如果B
和C
实现A.DoSomething()
的方式不同,D
应该继承哪个实现?这是一个难题,Java 和 C# 设计者决定通过仅允许不包含任何实现的特殊基类型进行多重继承来避免它。他们决定将这些特殊的基本类型称为 interfaces.
所以,没有“接口原理”。接口只是解决特定问题的“工具”。
那么为什么我们需要默认实现?
向后兼容。您编写了一个非常成功的库,被全球数以千计的开发人员使用。您的库包含一些接口 I
,现在您决定需要一个额外的方法 M
。问题是:
- 您不能将另一个方法
M
添加到I
,因为这会破坏现有的 classes 实现I
(因为它们没有实现M
), 和 - 您不能将
I
更改为抽象基础 class,因为那样也会破坏现有的 classes 实现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'
}
}