为什么这个C#代码返回它所做的

有人可以帮我理解为什么这段代码片段返回“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()); } } 

这里发生了两件事情。 一个是成员隐藏。 这是相当知名的,并在其他地方报道。 另一个鲜为人知的特性是C#5规范第13.4.6节中介绍的接口重新实现。 去引用:

允许inheritance接口实现的类通过将接口包含在基类列表中来重新实现该接口。 接口的重新实现遵循与接口的初始实现完全相同的接口映射规则。 因此,inheritance的接口映射对为重新实现接口而build立的接口映射没有任何影响。

inheritance的公共成员声明和inheritance的显式接口成员声明参与重新实现的接口的接口映射过程。

f1.GetName()的结果是“Bar”,因为Baz.GetName方法隐藏Bar.GetNamef1被声明为Bartypes。 除非明确声明为虚拟和重写,否则不会向运行时types的实现发送调度。

同样,对于f2.GetName()Baz.GetNameBar隐藏了实现,所以在通过对接口的引用使用dispatch时不会被调用。 接口被“映射”到在Bar声明的方法,因为这是接口被声明的types。 Baz有一个相同名称的兼容方法并不重要。 接口映射规则在规范的第13.4.4节中定义。 如果GetName已经在Bar被声明为虚拟的,它可以被覆盖,然后通过接口被调用。 结果也是“酒吧”。

对于f3.GetName()Quux重新实现了IFoo因此它可以将自己的映射定义为GetName 。 请注意,它也隐藏了从Barinheritance的实现。 没有必要使用new来重新执行,只是简单地隐藏了关于隐藏的警告。 所以结果是“Quux”。

这就解释了你看到的输出:“Bar-Bar-Quux”

Eric Lippert的这篇文章讨论了这个棘手的function中的一些细微差别。

接口根据定义没有相关的实现,也就是说他们的方法总是虚拟的和抽象的。 相反,上面的类Bar定义了GetName的具体实现。 这符合实施IFoo所需的合同。

Baz类现在inheritanceBar并声明一个new方法GetName 。 也就是说父类Bar有一个名字相同的方法,但是在明确使用Baz对象的时候它是完全被忽略的。

但是,如果一个Baz对象被转换为一个Bar ,或者简单地分配给一个BarIFootypes的variables,它将会按照它的操作行为,并像Bar 。 换句话说,方法名GetName引用Bar.GetName而不是Baz.GetName

现在,在第三种情况下, QuuxBarinheritance实现IFoo 。 现在,当作为一个IFoo ,它将提供自己的实现(根据Mike Z的答案中提供的规范)。

当一个Quux被当作一个酒吧时,它会返回“Bar”,就像Baz所做的那样。

由于在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但是 ,它被inputBar 。 因为Bar暴露了GetName ,所以当使用f1.GetName()时,这就是被调用的方法 – 不pipe Baz实现了GetName 。 原因是f1不是键入为Baz ,如果是,它会调用BazGetName方法。 这方面的一个例子将是检查的输出

 Console.WriteLine(((Baz)f1).GetName() + "-" + f2.GetName() + "-" + f3.GetName()); //Baz-Bar-Quux 

这是可能的,因为两个事实。 首先, f1最初被实例化为Baz ,它被简单地键入为Bar 。 其次, Baz 确实有一个GetName方法,并且在其定义中使用new隐藏inheritance的BarGetName方法,允许调用BazGetName

f2.GetName()

一个非常相似的键入与f2发生,被定义为

 IFoo f2 = new Baz(); 

虽然Baz类实现了一个GetName方法,但它并没有实现IFooGetName方法,因为Baz不能从IFooinheritance,因此该方法不可用。 Bar实现IFoo ,并且由于BazBarinheritance, BarGetName是在f2types为IFoo时暴露的方法。

再一次,因为f2最初被实例化为Baz ,它仍然可以投给Baz

 Console.WriteLine(f1.GetName() + "-" + ((Baz)f2).GetName() + "-" + f3.GetName()); //Bar-Baz-Quux 

并将有相同的输出结果为上述原因f1f2最初键入为Baz ,和BazGetName方法隐藏inheritanceBarGetName方法)。

f3.GetName()

这里有不同的故事 Quuxinheritance并实现了IFoo通过使用new隐藏了BarIFoo实现。 结果是QuuxGetName方法就是被调用的方法。