【问题标题】:Why is it possible to implement an interface method in base class? [duplicate]为什么可以在基类中实现接口方法? [复制]
【发布时间】:2013-01-20 03:52:11
【问题描述】:

在我的项目中,我发现了一个在 C# 中似乎完全有效的奇怪情况,因为我没有编译时错误。

简化示例如下所示:

using System;
using System.Collections.Generic;

namespace Test
{

    interface IFoo
    {
        void FooMethod();
    }

    class A
    {
        public void FooMethod()
        {
            Console.WriteLine("implementation");
        }
    }

    class B : A, IFoo
    {
    }

    class Program
    {
        static void Main(string[] args)
        {
            IFoo foo = new B();
            foo.FooMethod();
        }
    }
}

这样的代码可以编译。但是,请注意 A 不是 IFoo 并且 B 没有实现 IFoo 方法。就我而言,偶然(重构后)A 具有具有相同签名的方法。但是A为什么要知道如何实现IFoo接口的FooMethod呢? A 甚至不知道 IFoo 的存在。

对我来说,这样的设计是危险的。因为每次我实现某个接口时,我都应该检查这个接口中的每个方法是否“干扰”了基类方法。

如果这是“纯 C# 功能”?这叫什么?我错过了什么吗?

【问题讨论】:

  • 这对我来说似乎完全合乎逻辑,也正是我所期望的。
  • @BenjaminPaul 在A 类中编写FooMethod 实现但未实现IFoo 的人实际上打算实现IFoo 的可能性有多大?如果是这样,为什么他没有将IFoo 接口添加到A 实现的接口列表中?
  • 这叫什么?也许继承的(公共实例)方法实现的接口
  • 这是“纯 C# 功能”吗? 是的,在这种情况下,VB.NET 需要在新类中定义一个显式的 Implements 方法。
  • 有人能说明为什么微软以这种方式指定语言吗?程序员从中获得了什么?

标签: c# interface


【解决方案1】:

对于接口中的每个成员,编译器只查找 explicit 实现(如果有),然后是 public 实现(implicit实现),即与接口签名匹配的公共 API 上的方法。在这种情况下,A.FooMethod() 看起来很适合公共实现。如果B 对该选择不满意,它可以new 方法或使用显式实现;后者是首选:

void IFoo.FooMethod() { /* explicit implementation */ }

【讨论】:

  • 您还可以在 Visual Studio 的 Intellisense 中看到这一点。如果你输入 ` : ISomeInterface`,当你将鼠标悬停在接口名称上时,VS 会显示一个下拉框,提供(隐式)实现接口或显式实现接口的选项。
  • 没有回答 PPC 的评论:“有人可以提供一个线索,说明为什么微软以这种方式指定语言吗?程序员从中获得了什么”?您是否同意当前的行为容易出错?
【解决方案2】:

这里的关键词是implements。您的基类,虽然它对IFoo 一无所知,但已声明方法签名,它在您的类层次结构中某处的接口中实现该方法。

所以当你在派生类中实现IFoo时,它已经在类结构中实现了方法签名,因此不需要再次实现。

如果你有这个:

interface IFoo
{
  void FooMethod();
}
class A
{
  private void FooMethod(){}
}
class B : A, IFoo
{

}

在这种情况下,您需要实现IFoo,因为IFoo 结构在实现时不可访问,正如Mark 所说。您可以通过执行IFoo.FooMethod() 来隐式实现接口,以确保尽管在层次结构中已经定义了适当的方法签名,但您仍然有一个实现。

【讨论】:

    【解决方案3】:

    你在评论中说,

    A 类中编写FooMethod 实现但未实现IFoo 的人实际上打算实现IFoo 的可能性有多大?

    好吧,A 的作者在创建A 时的想法并不重要。 B 的作者必须对B 都继承自A 并实现IFoo 的事实负责。 B 的作者来考虑定义B 的后果

    你也说

    在我的情况下偶然(重构后)A 具有相同签名的方法

    暗示这种情况是在AB 都被写入之后发生的。在这种情况下,情况会发生变化:当编辑 * 继承自 * 的类时(例如 A),编辑者有责任检查编辑对所有继承类的影响

    【讨论】:

    • 请注意,这种奇怪的情况发生在重构之后,确实是一种不受欢迎的危险行为。尽管如此,它很可能未被非回归单元测试检测到。因此,我认为这种“语言功能”很危险
    • @PPC 我认为任何具有继承的语言都无法避免类似脆弱基类的问题。在 Eric Lippert 的博客上有很好的材料 - 参见例如 the brittle-base-class-problem category,尤其是 this post,它在 cmets 中引发了一场小风暴
    • 优秀的文章!谢谢
    【解决方案4】:

    要实现一个接口,一个类只需要 (a) 声明它正在实现该接口(例如您的 B 类),并且 (b) 为接口中定义的所有方法提供实现,直接或间接通过基类(例如您的 B 类)。

    【讨论】:

      【解决方案5】:

      第 13.4.4 节。 C# 规范声明:

      类或结构 C 的接口映射为 C 的基类列表中指定的每个接口的每个成员定位一个实现。特定接口成员 IM 的实现,其中 I 是声明成员 M 的接口, 通过检查每个类或结构 S 来确定,从 C 开始,对 C 的每个连续基类重复,直到找到匹配项:

      所以这似乎是明确定义的行为,因为在B 中找不到FooMethod 的正确实现,因此在其基类A 上执行搜索,其中找到具有匹配签名的方法.这甚至在规范的同一部分中明确指出:

      基类的成员参与接口映射。在示例中

      interface Interface1
      {
      void F();
      }
      class Class1
      {
          public void F() {}
          public void G() {}
      }
      class Class2: Class1, Interface1
      {
          new public void G() {}
      }
      

      Class1 中的方法 F 用于 Class2 对 Interface1 的实现。

      【讨论】:

        【解决方案6】:

        该功能称为继承。如果你不喜欢这个设计,就不要使用它。很多人不喜欢继承,所以你也可以。继承的定义是,基类的所有成员也是派生类的成员。所以没有任何编译器错误。因此 Derived 实现了IFoo 提供的合约。它是满足此要求的基类成员。

        它的美妙之处在于,您可以通过 基本功能(虚拟)实现接口,如果期望 Derived 表现不同,则可以覆盖该接口。

        【讨论】:

          【解决方案7】:

          接口不被继承,接口被实现。因此,当您从接口派生类时,这意味着

          嘿接口,你会在这里找到实现该方法的方法 您提供的签名。

          由于基类有方法的实现,接口中定义的方法签名相同,不会有问题。

          即使您编写包含相同方法签名的第二个接口,它仍然可以工作。

          interface IFoo2
          {
              void FooMethod();
          }
          
          class B : A, IFoo, IFoo2
          {
          }
          

          【讨论】:

          • 您可以在另一个接口中“继承”一个接口;)即在另一个接口中扩展现有接口。
          【解决方案8】:

          “可是A为什么要知道IFoo接口的FooMethod怎么实现呢?A连IFoo的存在都不知道。”

          A 不需要知道接口 IFoo 的存在。正确实现 FooMethod 不是 A 的责任。显然 A 实现了与 IFoo 接口方法 FooMethod 具有相同签名的方法。

          实现 FooMethod 是 B 的责任,因为它正在实现 IFoo 接口。但是由于 B 已经有一个名为 FooMethod 的方法(从 A 继承),它不需要显式地实现它。如果继承的方法没有发挥作用,B 可以新建方法并编写自己的实现。

          【讨论】:

            【解决方案9】:

            B 执行IFOOB 继承自 A 所以它实际上看起来像这样:

            class B : IFoo  //Notice there is no A here. 
            {
                public void FooMethod()
                {
                    Console.WriteLine("implementation");
                }
            }
            

            很明显(从上面的代码)B 正在实现IFoo,没有什么特别的。

            【讨论】:

              【解决方案10】:

              虽然推测 C# 的创建者为什么做了他们所做的事情并不是特别有帮助,虽然我不喜欢这个特殊的功能,但我怀疑它之所以能正常工作的部分原因是没有 other 很好的语法来指定接口应该由已经存在的基类方法实现。要求派生类必须定义除了链接到基类实现之外什么都不做的方法,这看起来很丑陋。

              话虽如此,我认为对于 C# 来说,通过提供一种将接口成员显式附加到类成员的语法来解决一般问题(链接到其他成员的接口方法很难看)会比使用自动绑定语义来处理一种特定情况,但需要在更常见的情况下进行链接(通过受保护的虚拟方法实现接口):

              受保护的虚拟 IFoo_Method(int a, int b, int c) { ... }

              IFoo.Method(int a, int b, int c) { IFoo_Method(a,b,c); }

              虽然 JITter 可能能够确定 IFoo_Method 调用应该是内联的,但它实际上不应该这样做。声明受保护的方法IFoo_Method 应该被视为IFoo.Method 的实现似乎更简洁。

              【讨论】:

                猜你喜欢
                • 2015-09-22
                • 2019-08-19
                • 2021-01-19
                • 1970-01-01
                • 2013-09-30
                • 1970-01-01
                • 2015-07-11
                • 2017-12-28
                • 1970-01-01
                相关资源
                最近更新 更多