逆变接口方法 dispatch/selection C#

Contravariant interfaces method dispatch/selection C#

考虑以下代码:

interface ITest<in T>
{
    void DoTest(T instance);
}

class A {}
class B : A {}
class C : B {}

class Test : ITest<A>, ITest<B>
{
    void ITest<A>.DoTest(A instance)
    {
        Console.WriteLine(MethodInfo.GetCurrentMethod());
    }
    
    void ITest<B>.DoTest(B instance)
    {
        Console.WriteLine(MethodInfo.GetCurrentMethod());
    }
}

public class Program
{
    public static void Main()
    {
        ITest<C> test = new Test();
        test.DoTest(new C());
    }
}

输出为:

无效 ITest.DoTest(A)

对我来说,这不是预期的行为,或者至少不是大多数开发人员所期望的行为。预期输出为:

作废 ITest.DoTest(B)

此处应使用“最佳”实现,最佳是指具有最多派生类型参数直至逆变“静态”类型的通用接口。

相反,似乎选择了“最差”。

检查生成的 IL 不会揭示 selection 机制,因为调用是通过正确的静态类型分派的,所以我假设这取决于 CLR select 实现:

.method public hidebysig static 
    void Main () cil managed 
{
    // Method begins at RVA 0x207c
    // Code size 20 (0x14)
    .maxstack 2
    .locals init (
        [0] class ITest`1<class C>
    )

    IL_0000: nop
    IL_0001: newobj instance void Test::.ctor()
    IL_0006: stloc.0
    IL_0007: ldloc.0
    IL_0008: newobj instance void C::.ctor()
    IL_000d: callvirt instance void class ITest`1<class C>::DoTest(!0)
    IL_0012: nop
    IL_0013: ret
} // end of method Program::Main

为什么会这样?这种情况下的规则是什么?它在幕后是如何运作的?

这在 ECMA-335 的第 II.12.2 节中指定。

规范的相关片段如下:

Where there are multiple implementations for a given interface method due to differences in type parameters, the declaration order of the interfaces on the class determines which method is invoked.
...
The inheritance/implements tree for a type T is the n-ary tree formed as follows:

  • The root of the tree is T
    ...
  • If T has one or more explicit interfaces, Ix, then the inheritance/implements tree for each Ix is a child of the root node, in order.

The type declaration order of the interfaces and super classes of a type T is the postorder depth-first traversal of the inheritance/implements tree of type T with any second and subsequent duplicates of any type omitted. Occurrences of the same interface with different type parameters are not considered duplicates

所以我们定义了类型声明顺序作为这些接口出现在class声明中的顺序。

When an interface method is invoked, the VES shall use the following algorithm to determine the appropriate method to call:

  • Beginning with the runtime class of the instance through which the interface method is invoked, using its interface table as constructed above, and substituting generic arguments, if any, specified on the invoking class:
    1. For each method in the list associated with the interface method, if there exists a method whose generic type arguments match exactly for this instantiation (or there are no generic type parameters), then call the first method
    2. Otherwise, if there exists a method in the list whose generic type parameters have the correct variance relationship, then call the first such method in the list

这就是说,如果没有完全匹配,则采用类型声明顺序中类型参数具有正确方差关系的第一种方法。

您的示例在“II.12.2.1 接口实现示例”部分中显示为 案例 6,其中类型 S4<V> 实现了两者 IVarImpl (这意味着实现 IVar<A>)和 IVar<B>,该示例显示在 S4<A> 的实例上调用方法 IVar<C>::P(C) 会导致方法 S1<A,B>::P(!0:A)(即是,void P(A)) 被调用。

确实,如果我们在 Test 的声明中交换 ITest<A>ITest<B> 的顺序,我们可以看到 ITest<B>.DoTest(B instance) 实现最终被调用。