为啥 C# 中的基类允许实现接口契约而不继承它?

Posted

技术标签:

【中文标题】为啥 C# 中的基类允许实现接口契约而不继承它?【英文标题】:Why is a base class in C# allowed to implement an interface contract without inheriting from it?为什么 C# 中的基类允许实现接口契约而不继承它? 【发布时间】:2011-02-25 22:22:25 【问题描述】:

我偶然发现了 C# 的这个“特性” - 实现接口方法的基类不必派生自它

例子:

public interface IContract

    void Func();


// Note that Base does **not** derive from IContract
public abstract class Base

    public void Func()
    
        Console.WriteLine("Base.Func");
    


// Note that Derived does *not* provide implementation for IContract
public class Derived : Base, IContract


发生的事情是Derived 神奇地选择了一个公共方法Base.Func,并决定它将实现IContract.Func

这种魔法背后的原因是什么?

恕我直言:这种“准实现”功能非常不直观,并且使代码检查更加困难。你怎么看?

【问题讨论】:

我“认为”您可以通过在 Derived 类中显式实现接口来解决副作用。 这对我来说很有意义。当 Derived 继承 IContract 时,这意味着它提供了 Func(),它确实如此。它通过继承来做到这一点只是偶然的。请注意,Base 实现 IContract - 如果您尝试将 Base 转换为 IContract,它将失败。 【参考方案1】:

为什么你认为这很奇怪和不自然?基类的每个公共成员也是派生类的公共成员。所以这里没有矛盾。无论如何,如果你愿意,你可以显式地实现接口。

【讨论】:

恕我直言,这种功能给语言增加了不必要的复杂性。简单而自然的方法是从 IContract 派生 Base 以明确声明它实现了它。人类是简单的生物 - 我们无法立即解析所有代码并交叉引用继承图以了解 IContract.Func 和 Base.Func 是相关的(重命名 IContract.Func 必须手动 i> 重命名 Base.Func)。当所有三个类都驻留在不同的项目/解决方案中时,情况会变得更加复杂。 简而言之 - 这个特性是邪恶的,因为代码可读性受到很大影响程序员(可能)必须遍历每个基类才能找到接口实现,即使是与接口完全无关的基类。 好的,我会试着解释一下:你试图描述的情况是罕见且不自然的。尤其是这个 - “当所有三个类都驻留在不同的项目/解决方案中时,它变得更加复杂。”。再一次,您可以显式地实现接口,这样您就可以避免与基类方法发生任何冲突。 @etarassov:假设你没有实现 Base 但你实现了 Derived。然后你不能让它实现你想要的接口。假设您要实现 Derived 和 OtherDerived,并且希望 OtherDerived 不实现合同。在这两种情况下,您都不能将接口添加到 Base。因此必须提出一些不需要将接口添加到Base的机制。 @Eric Lippert:好点。但我仍然倾向于认为,在这两种(角落?)情况下,组合应该优于继承,因为 Base、Derived 和最终 OtherDerived 之间所需关系的复杂性。许多人建议更喜欢组合而不是继承(通常)。【参考方案2】:

原因是您的评论根本不正确:

// 请注意,Derived 为 IContract 提供实现

确实如此。遵循逻辑。

Derived 需要提供与 IContract 的每个成员对应的公共成员。 基类的所有可继承成员也是派生类的成员;这就是继承的定义。 因此,Derived 提供了 IContract 的实现;其继承的成员是满足要求的成员 因此,没有错误。

此功能非常不直观,并且使代码检查更加困难。你怎么看?

如果您不喜欢该功能,我认为您不应该使用它。如果您发现阅读使用此功能的代码感到困惑和奇怪,请鼓励使用此功能的同事停止这样做。

此功能与使用派生类中的基类方法的任何其他功能有何不同?在派生类中使用或提及基类中的方法有多种不同的方式——方法调用、覆盖、方法组转换等等。

此外,这是一个相对简单、直接的案例。如果您真的想抱怨 C# 中令人困惑的接口语义,我会花时间抱怨 接口重新实现语义。那是真正似乎在烤人的面条。我总是必须在规范中查找该内容,以确保我得到了正确的语义。

【讨论】:

感谢您的详细回答!我确实了解如何它的工作原理,但恕我直言,它的工作方式是违反直觉的,原因有很多:(1) 一个人先阅读价格标签,然后付款,同样的方式一个第一个 declares 并且只有 after implements smth。 (2) Derived 是一个IContract,没有实现它,而是使用Base 来实现IContract,它本身不是一个IContract (3) 通过阅读Base 的代码无法确定Base.Func 是否与IContract.Func 相关(关系声明/实现)。 (4) 更改Base.Func会产生编译错误,甚至不会提及Base “如果你不喜欢这个功能,我认为你不应该使用它。” - 100% 同意。不幸的是,其他人可以使用它(或现有的代码库),这将使代码难以阅读/理解。真正伟大的语言指导开发人员编写好的代码(理想情况下会强制它)。 C#(一种非常好的语言)只有在删除此功能时才会受益。但这将是非常困难和痛苦的......就像使用循环变量的 foreach 内的闭包一样。无论如何感谢“接口重新实现语义”的链接!

以上是关于为啥 C# 中的基类允许实现接口契约而不继承它?的主要内容,如果未能解决你的问题,请参考以下文章

C# 抽象类abstract

C# 包含继承类的基类数组,访问非继承字段

继承中的基类是不是复制到派生类?

为啥我的类不继承其基类中定义的方法?

c#基础 继承和接口,扩展类型

C# 浅谈 接口(Interface)的作用