有人可以帮我理解为什么这段代码片会返回" Bar-Bar-Quux"?即使在阅读界面后,我也很难理解这一点。
interface IFoo
{
string GetName();
}
class Bar : IFoo
{
public string GetName() { return "Bar"; }
}
class Baz : Bar
{
public new string GetName() { return "Baz"; }
}
class Quux : Bar, IFoo
{
public new string GetName() { return "Quux"; }
}
class Program
{
static void Main()
{
Bar f1 = new Baz();
IFoo f2 = new Baz();
IFoo f3 = new Quux();
Console.WriteLine(f1.GetName() + "-" + f2.GetName() + "-" + f3.GetName());
}
}
答案 0 :(得分:57)
这里发生了两件事。一个是成员隐藏。这是众所周知的,涵盖elsewhere。另一个鲜为人知的特性是C#5规范第13.4.6节中介绍的接口重新实现。引用:
允许继承接口实现的类通过将接口包含在基类列表中来重新实现接口。接口的重新实现遵循与接口的初始实现完全相同的接口映射规则。因此,继承的接口映射对于为重新实现接口而建立的接口映射没有任何影响。
和
继承的公共成员声明和继承的显式接口成员声明参与重新实现的接口的接口映射过程。
f1.GetName()
的结果是“条形码”,因为方法Baz.GetName
隐藏Bar.GetName
而f1
被声明为类型Bar
。除非将其显式声明为虚拟和重写,否则不会调度运行时类型的实现。
同样,对于f2.GetName()
,Baz.GetName
隐藏了Bar
中的实现,因此在通过对接口的引用使用dispatch时不会调用它。接口被“映射”到Bar
中声明的方法,因为这是声明接口的类型。 Baz
具有相同名称的兼容方法并不重要。接口映射的规则在规范的第13.4.4节中定义。如果在GetName
中将Bar
声明为虚拟,则可以覆盖它,然后通过接口调用它。因此结果也是“Bar”。
对于f3.GetName()
,Quux
重新实现IFoo
,以便定义自己的GetName
映射。请注意,它还隐藏了从Bar
继承的实现。没有必要使用new来重新实现,它只是抑制了有关隐藏的警告。因此结果是“Quux”。
这样就解释了你看到的输出:“Bar-Bar-Quux”
Eric Lippert的这篇post讨论了这个棘手特征中的一些细微差别。
答案 1 :(得分:7)
根据定义,接口没有关联的实现,也就是说它们的方法总是虚拟的和抽象的。相反,上面的类Bar
定义了GetName
的具体实现。这符合实施IFoo
所需的合同。
类Baz
现在继承自Bar
并声明new
方法GetName
。也就是说,父类Bar
具有一个具有相同名称的方法,但在明确使用Baz
个对象时会完全忽略它。
但是,如果将Baz
个对象转换为Bar
,或者只是将其分配给Bar
或IFoo
类型的变量,则会按照它的说明进行操作表现得像Bar
。换句话说,方法名称GetName
引用Bar.GetName
而不是Baz.GetName
。
现在,在第三种情况下,Quux
都继承自Bar
和实现IFoo
。现在,当转换为IFoo
时,它将提供自己的实现(根据Mike Z的答案中提供的规范)。
当Quux被转换为Bar时,它会返回“Bar”,就像Baz一样。
答案 2 :(得分:3)
由于在Console.WriteLine方法调用中对GetName()进行了3次调用,因此输出为Bar-Bar-Quux。
Bar f1 = new Baz();
IFoo f2 = new Baz();
IFoo f3 = new Quux();
Console.WriteLine(f1.GetName() + "-" + f2.GetName() + "-" + f3.GetName());
//Bar-Bar-Quux
让我们检查每个电话,以便更清楚地了解会发生什么。
<强> f1.GetName()强>
f1
被实例化为Baz
。 但是,键入为Bar
。因为Bar
公开了GetName
,所以当使用f1.GetName()
时,这就是所谓的方法 - 无论 Baz
还实现了GetName
{1}}。原因是f1
未键入Baz
,如果是,则会调用Baz
的{{1}}方法。一个例子是检查
GetName
这是可能的,因为有两个事实。首先,Console.WriteLine(((Baz)f1).GetName() + "-" + f2.GetName() + "-" + f3.GetName());
//Baz-Bar-Quux
最初被实例化为f1
,它只是输入为Baz
。其次,Bar
具有Baz
方法,并且在其定义中使用GetName
会隐藏继承的new
&#39; s { {1}}允许调用Bar
GetName
的方法。
<强> f2.GetName()强>
Baz
正在进行非常类似的输入,定义为
GetName
虽然Baz类确实实现了f2
方法,但它没有实现IFoo f2 = new Baz();
的{{1}}方法,因为GetName
不会从{{1}继承因此该方法不可用。 IFoo
实施GetName
,由于Baz
继承自IFoo
,Bar
&#39; s IFoo
是Baz
时公开的方法{1}}输入为Bar
。
同样,由于Bar
最初被实例化为GetName
,因此仍然可以将其转换为f2
。
IFoo
由于f2
(Baz
最初输入为Baz
,Console.WriteLine(f1.GetName() + "-" + ((Baz)f2).GetName() + "-" + f3.GetName());
//Bar-Baz-Quux
&#39; s {{f1
,上述原因会产生相同的输出结果1}}方法隐藏了继承的f2
&#39; Baz
方法。
<强> f3.GetName()强>
这里有不同的故事。 Baz
继承并实施GetName
,和它隐藏了Bar
GetName
的{{1}}实现使用Quux
。结果是IFoo
的{{1}}方法是被调用的方法。