在我的项目中,我发现了一个在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
应该知道如何实现FooMethod
接口的IFoo
? A
甚至不知道IFoo
存在。
对我来说,这样的设计是危险的。因为每次实现某个接口时,我都应检查此接口中的每个方法是否“干扰”基类方法。
如果这是“纯C#功能”?这叫什么?我错过了什么吗?
答案 0 :(得分:39)
对于界面中的每个成员,编译器只查找显式实现(如果有),然后查找 public 实现(隐式实现),即公共API上与接口签名匹配的方法。在这种情况下,A.FooMethod()
看起来像是公共实现的完美匹配。如果B
对该选择不满意,可以new
方法,或使用显式实现;后者将是首选:
void IFoo.FooMethod() { /* explicit implementation */ }
答案 1 :(得分:20)
这里的关键词是implements
。您的基类,虽然它对IFoo
一无所知,但已经声明了方法签名,它在您的类层次结构中的某个地方实现了该方法。
因此,当您在派生类中实现IFoo
时,它已经在类结构中实现了方法签名,因此不需要再次实现它。
如果你有这个:
interface IFoo
{
void FooMethod();
}
class A
{
private void FooMethod(){}
}
class B : A, IFoo
{
}
在这种情况下,您需要实现IFoo
,因为IFoo
结构在实现时无法访问,正如Mark所说。您可以通过执行IFoo.FooMethod()
来隐式实现接口,以确保您拥有实现,尽管已在层次结构中定义了适当的方法签名。
答案 2 :(得分:11)
你在评论中说,
在
FooMethod
类中编写A
实现但未实现IFoo
的人实际意图实现IFoo
的可能性有多大?
嗯,A
创作时A
的作者所想的并不重要。 B
的作者必须对B
继承自A
并实施IFoo
这一事实负责。 B
的作者需要考虑B
定义的后果。
你也说
在我的情况下(重构后)
A
的方法具有相同的签名
建议在A
和B
写完之后出现这种情况。在这种情况下,情况会发生变化:当编辑*继承自*的类(例如A
)时,编辑负责检查编辑对所有继承类的影响
答案 3 :(得分:2)
要实现接口,类只需要(a)声明它正在实现该接口(例如您的B类),以及(b)为接口中定义的所有方法提供实现,无论是直接还是通过基类间接(例如你的B类)。
答案 4 :(得分: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实现。
答案 5 :(得分:1)
该功能称为继承。如果你不喜欢这个设计,就不要使用它。很多人不喜欢继承,所以你也可能。继承的定义是,基类的所有成员也是派生类的成员。所以没有任何编译器错误。因此,Derived实现了IFoo
提供的合同。它是基类成员,可以满足这一要求。
它的美妙之处在于,您可以通过基本功能(虚拟)实现接口,如果期望Derived的行为不同,则可以覆盖该接口。
答案 6 :(得分:1)
不继承接口,实现接口。因此,当您从接口派生类时,它意味着
嘿接口,你会发现一个实现该方法的方法 你提供的签名。
由于基类具有该方法的实现,并且在接口中定义了相同的方法,所以没有问题。
即使您编写第二个包含相同方法签名的界面,它仍然有效。
interface IFoo2
{
void FooMethod();
}
class B : A, IFoo, IFoo2
{
}
答案 7 :(得分:1)
“但是为什么A应该知道如何实现IFoo接口的FooMethod?甚至不知道IFoo存在。”
A不需要知道接口IFoo的存在。它不是A正确实施FooMethod的责任。显然A碰巧实现了与IFoo接口方法FooMethod具有相同签名的方法。
B负责实现FooMethod,因为它实现了IFoo接口。但由于B已经有一个名为FooMethod的方法(继承自A),因此它不需要显式实现它。如果继承的方法没有完成它的工作,B可以新建方法并编写自己的实现。
答案 8 :(得分:0)
B
实施IFOO
。 B
继承自A
,所以实际上看起来像这样:
class B : IFoo //Notice there is no A here.
{
public void FooMethod()
{
Console.WriteLine("implementation");
}
}
很明显(从上面的代码中)B
正在实施IFoo
,没有什么是特别的。
答案 9 :(得分:0)
虽然推测为什么C#的创造者做了他们所做的事情并没有特别帮助,虽然我不喜欢这个特殊的功能,但我怀疑它的部分原因是因为它确实没有< em>其他良好的语法,用于指定接口应由现有的基类方法实现。要求派生类必须定义除了链接到基类实现之外什么都不做的方法,这看起来很难看。
有人说过,我认为通过提供一种语法来明确地将接口成员附加到类成员而不是使用C#来解决这个一般性问题(链接到其他成员的接口方法很难看)会更加清晰自动绑定语义来处理一种特定情况,但需要在更常见的情况下进行链接(通过受保护的虚方法实现接口):
protected virtual 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
的实现似乎更清晰。