60

在我的项目中,我发现了一个在 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知道如何实现FooMethod接口IFoo呢?A甚至不知道IFoo存在。

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

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

4

10 回答 10

42

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

void IFoo.FooMethod() { /* explicit implementation */ }
于 2013-02-05T09:20:31.197 回答
20

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

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

如果你有这个:

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

}

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

于 2013-02-05T09:19:30.583 回答
12

你在评论中说,

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

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

你也说

就我而言,偶然(重构后)A具有具有相同签名的方法

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

于 2013-02-05T13:39:38.703 回答
2

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

于 2013-02-05T09:20:28.393 回答
2

第 13.4.4 节。C# 规范指出:

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

因此,这似乎是定义明确的行为,因为在FooMethod中找不到正确的实现B,因此在其基类上执行搜索,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 实现。

于 2013-02-05T12:26:18.233 回答
1

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

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

于 2013-02-05T09:22:59.407 回答
1

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

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

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

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

interface IFoo2
{
    void FooMethod();
}

class B : A, IFoo, IFoo2
{
}
于 2013-02-05T09:23:29.547 回答
1

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

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

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

于 2013-02-06T03:48:57.103 回答
0

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

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

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

于 2013-02-05T09:31:17.843 回答
0

虽然推测 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.

于 2013-02-05T21:28:42.243 回答