多次实现协变接口:这种行为定义正确吗
本文关键字:定义 实现 接口 | 更新日期: 2023-09-27 18:30:00
给定以下协变通用接口
public interface IContainer<out T>
{
T Value { get; }
}
我们可以为多个泛型类型创建一个实现该接口多次的类。在我感兴趣的场景中,这些泛型类型共享一个公共的基类型。
public interface IPrint
{
void Print();
}
public class PrintA : IPrint
{
public void Print()
{
Console.WriteLine("A");
}
}
public class PrintB : IPrint
{
public void Print()
{
Console.WriteLine("B");
}
}
public class SuperContainer : IContainer<PrintA>, IContainer<PrintB>
{
PrintA IContainer<PrintA>.Value => new PrintA();
PrintB IContainer<PrintB>.Value => new PrintB();
}
现在,当通过IContainer<IPrint>
类型的引用使用这个类时,事情变得有趣起来。
public static void Main(string[] args)
{
IContainer<IPrint> container = new SuperContainer();
container.Value.Print();
}
它编译和运行时没有问题,并打印"A"。我在规范中发现了什么:
特定接口成员I.M的实现,其中I声明成员M的接口由检查每个类或结构S,从C开始并重复C的每个连续基类,直到找到匹配:
- 如果S包含显式接口成员实现的声明匹配I和M,那么这个成员就是I.M的实现
- 否则,如果S包含非静态公共成员的声明匹配M,那么这个成员就是I.M的实现
第一个要点似乎是相关的,因为接口实现是显式的。然而,当有多个候选者时,它并没有说明选择哪个实现。
如果我们为IContainer<PrintA>
实现使用公共poperty,则会变得更加有趣:
public class SuperContainer : IContainer<PrintA>, IContainer<PrintB>
{
public PrintA Value => new PrintA();
PrintB IContainer<PrintB>.Value => new PrintB();
}
现在,根据上面的规范,因为有一个通过IContainer<PrintB>
的显式接口实现,我希望它打印"B"。然而,它却在使用公共财产,并且仍在打印"A"。
类似地,如果我通过公共属性显式地实现IContainer<PrintA>
和IContainer<PrintB>
,它仍然打印"A"。
输出似乎唯一依赖的是接口的声明顺序。如果我将申报更改为
public class SuperContainer : IContainer<PrintB>, IContainer<PrintA>
所有东西都印着"B"!
规范的哪一部分定义了这种行为,如果定义正确的话?
我在规范中找不到它,但您所看到的是意料之中的。IContainer<PrintA>
和IContainer<PrintB>
具有不同的完全限定名称(无法找到如何形成该FQN的规范),因此编译器将SuperContainer
识别为两个不同接口的实现类,每个接口都有一个void Print();
方法。
因此,我们有两个不同的接口,每个接口都包含一个具有相同签名的方法。正如您在规范(13.4.2)中链接的那样,首先通过查看IContainer<PrintA>
,查找适当的映射,然后查看IContainer<PrintB>
来选择Print()
的实现。
由于在IContainer<PrintA>
中找到了合适的映射,IContainer<PrintA>.Print()
被用于SuperContainer
对IContainer<PrintB>
的实现。
来自同一规格(位于最底部):
基类的成员参与接口映射。在示例中
interface Interface1
{
void F();
}
class Class1
{
public void F() {}
public void G() {}
}
class Class2: Class1, Interface1
{
new public void G() {}
}
Class1中的方法F用于Class2的Interface1的实现。
所以最后,是的,顺序决定了调用哪个Print()
方法。