为什么可以在基类中实现接口方法

本文关键字:实现 接口 方法 基类 为什么 | 更新日期: 2023-09-27 17:57:34

在我的项目中,我发现了一种奇怪的情况,这种情况在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不是IFooB也没有实现IFoo方法。在我的例子中,意外的是(在重构之后),A拥有具有相同签名的方法。但是A为什么要知道如何实现IFoo接口的FooMethod呢?CCD_ 9甚至不知道CCD_。

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

如果这是"纯C#特性"?它叫什么?我是不是错过了什么?

为什么可以在基类中实现接口方法

对于接口中的每个成员,编译器只需查找显式实现(如果有),然后是公共实施(隐式implementation),即公共API上与接口签名匹配的方法。在这种情况下,A.FooMethod()看起来与公共实现非常匹配。如果B对这个选择不满意,它可以new这个方法,也可以使用显式实现;后者更可取:

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

这里的关键词是implements。您的基类,尽管它对IFoo一无所知,但已经声明了方法签名,它在类层次结构中的某个地方实现了接口中的方法。

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

如果你有这个:

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

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

你在评论中说,

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

其实,A的作者在创作A时的想法并不重要。B的编写者必须对B既继承了A又实现了IFoo这一事实负责B的作者来思考B定义的后果

你还说

在我的案例中,偶然(重构后)A有一个具有相同签名的方法

说明这种情况是在CCD_ 33和CCD_。在这种情况下,情况会发生变化:编辑从*继承的类(如A)时,编辑器有责任检查编辑对所有继承类的影响

要实现一个接口,一个类只需要(a)声明它正在实现该接口(就像你的类B所做的那样),以及(B)直接或间接地通过基类(比如你的类B所做的)为接口中定义的所有方法提供实现。

第13.4.4节。C#规范的状态:

类或结构C的接口映射为C的基类列表中指定的每个接口的每个成员定位一个实现。特定接口成员I.M的实现,其中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的实现。

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

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

接口没有继承,而是实现了接口。因此,当您从接口派生类时,它意味着

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

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

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

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

"但为什么A应该知道如何实现IFoo接口的FooMethod?A甚至不知道IFoo的存在。"

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

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

B确实实现了IFOOB继承自A,所以它实际上看起来像这样:

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

从上面的代码中可以清楚地看出,B正在实现IFoo,没有什么特别的。

虽然推测C#的创建者为什么这么做并没有特别的帮助,虽然我不喜欢这个特定的功能,但我怀疑它之所以能如此工作,部分原因是没有其他好的语法来指定接口应该由现有的基类方法实现。要求派生类必须定义只链接到基类实现的方法,这看起来很难看。

话虽如此,我认为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的实现似乎更简单。