为什么这个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.GetName
而f1
被声明为Bar
types。 除非明确声明为虚拟和重写,否则不会向运行时types的实现发送调度。
同样,对于f2.GetName()
, Baz.GetName
在Bar
隐藏了实现,所以在通过对接口的引用使用dispatch时不会被调用。 接口被“映射”到在Bar
声明的方法,因为这是接口被声明的types。 Baz
有一个相同名称的兼容方法并不重要。 接口映射规则在规范的第13.4.4节中定义。 如果GetName
已经在Bar
被声明为虚拟的,它可以被覆盖,然后通过接口被调用。 结果也是“酒吧”。
对于f3.GetName()
, Quux
重新实现了IFoo
因此它可以将自己的映射定义为GetName
。 请注意,它也隐藏了从Bar
inheritance的实现。 没有必要使用new来重新执行,只是简单地隐藏了关于隐藏的警告。 所以结果是“Quux”。
这就解释了你看到的输出:“Bar-Bar-Quux”
Eric Lippert的这篇文章讨论了这个棘手的function中的一些细微差别。
接口根据定义没有相关的实现,也就是说他们的方法总是虚拟的和抽象的。 相反,上面的类Bar
定义了GetName
的具体实现。 这符合实施IFoo
所需的合同。
Baz
类现在inheritanceBar
并声明一个new
方法GetName
。 也就是说父类Bar
有一个名字相同的方法,但是在明确使用Baz
对象的时候它是完全被忽略的。
但是,如果一个Baz
对象被转换为一个Bar
,或者简单地分配给一个Bar
或IFoo
types的variables,它将会按照它的操作行为,并像Bar
。 换句话说,方法名GetName
引用Bar.GetName
而不是Baz.GetName
。
现在,在第三种情况下, Quux
从Bar
inheritance并实现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
。 但是 ,它被input为Bar
。 因为Bar
暴露了GetName
,所以当使用f1.GetName()
时,这就是被调用的方法 – 不pipe Baz
实现了GetName
。 原因是f1
不是键入为Baz
,如果是,它会调用Baz
的GetName
方法。 这方面的一个例子将是检查的输出
Console.WriteLine(((Baz)f1).GetName() + "-" + f2.GetName() + "-" + f3.GetName()); //Baz-Bar-Quux
这是可能的,因为两个事实。 首先, f1
最初被实例化为Baz
,它被简单地键入为Bar
。 其次, Baz
确实有一个GetName
方法,并且在其定义中使用new
隐藏inheritance的Bar
的GetName
方法,允许调用Baz
的GetName
。
f2.GetName()
一个非常相似的键入与f2
发生,被定义为
IFoo f2 = new Baz();
虽然Baz类实现了一个GetName
方法,但它并没有实现IFoo
的GetName
方法,因为Baz
不能从IFoo
inheritance,因此该方法不可用。 Bar
实现IFoo
,并且由于Baz
从Bar
inheritance, Bar
的GetName
是在f2
types为IFoo
时暴露的方法。
再一次,因为f2
最初被实例化为Baz
,它仍然可以投给Baz
。
Console.WriteLine(f1.GetName() + "-" + ((Baz)f2).GetName() + "-" + f3.GetName()); //Bar-Baz-Quux
并将有相同的输出结果为上述原因f1
( f2
最初键入为Baz
,和Baz
的GetName
方法隐藏inheritanceBar
的GetName
方法)。
f3.GetName()
这里有不同的故事 Quux
inheritance并实现了IFoo
, 并通过使用new
隐藏了Bar
的IFoo
实现。 结果是Quux
的GetName
方法就是被调用的方法。