多次实现协变接口:这种行为定义正确吗

本文关键字:定义 实现 接口 | 更新日期: 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()被用于SuperContainerIContainer<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()方法。