为什么这个C#代码会返回它的功能

时间:2014-07-29 03:57:48

标签: c# interface code-snippets

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

3 个答案:

答案 0 :(得分:57)

这里发生了两件事。一个是成员隐藏。这是众所周知的,涵盖elsewhere。另一个鲜为人知的特性是C#5规范第13.4.6节中介绍的接口重新实现。引用:

  

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

  

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

f1.GetName()的结果是“条形码”,因为方法Baz.GetName隐藏Bar.GetNamef1被声明为类型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,或者只是将其分配给BarIFoo类型的变量,则会按照它的说明进行操作表现得像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继承自IFooBar&#39; s IFooBaz时公开的方法{1}}输入为Bar

同样,由于Bar最初被实例化为GetName,因此仍然可以将其转换为f2

IFoo

由于f2Baz最初输入为BazConsole.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}}方法是被调用的方法。