简体   繁体   English

逆变接口方法分派/选择 C#

[英]Contravariant interfaces method dispatch/selection C#

Consider the following code:考虑以下代码:

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());
    }
}

The output is: output 是:

Void ITest<A>.DoTest(A)作废 ITest<A>.DoTest(A)

To me this is not the expected behavior, or at least not the one most developers are expecting.对我来说,这不是预期的行为,或者至少不是大多数开发人员所期望的行为。 The expected output is:预期的 output 是:

Void ITest<B>.DoTest(B)无效 ITest<B>.DoTest(B)

Here the "best" implementation should be used, with best meaning the generic interface with the most derived type parameter up to the contravariant "static" type.这里应该使用“最佳”实现,最佳意味着具有最多派生类型参数的通用接口,直到逆变“静态”类型。

Instead it seems the "worst" is chosen.相反,似乎选择了“最差”。

Inspecting the generated IL doesn't unveil the selection mechanism as the call is dispatched through the correct static type so I assume it's up to the CLR to select the implementation:检查生成的 IL 不会揭示选择机制,因为调用是通过正确的 static 类型调度的,所以我假设它取决于 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

Why it acts like this?为什么会这样? What are the rules in this case?这种情况下的规则是什么? How does it work behind the scenes?它是如何在幕后运作的?

This is specified in Section II.12.2 of ECMA-335 .这在ECMA-335的第 II.12.2 节中指定。

Relevant snippets of the spec follows:规范的相关片段如下:

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.如果由于类型参数的不同,给定接口方法有多个实现,则 class 上接口的声明顺序决定了调用哪个方法。
... ...
The inheritance/implements tree for a type T is the n-ary tree formed as follows:类型 T 的继承/实现树是 n 叉树,如下所示:

  • The root of the tree is T树的根是 T
    ... ...
  • If T has one or more explicit interfaces, I x , then the inheritance/implements tree for each I x is a child of the root node, in order.如果 T 有一个或多个显式接口 I x ,则每个 I x的继承/实现树按顺序是根节点的子节点。

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.类型 T 的接口和超类的类型声明顺序是类型 T 的继承/实现树的后序深度优先遍历,省略任何类型的任何第二个和后续副本。 Occurrences of the same interface with different type parameters are not considered duplicates具有不同类型参数的同一接口的出现不被视为重复

So we've defined the type declaration order as the order in which these interfaces appear in the class declaration.所以我们将类型声明顺序定义为这些接口在 class 声明中出现的顺序。

When an interface method is invoked, the VES shall use the following algorithm to determine the appropriate method to call:当调用接口方法时,VES 应使用以下算法来确定要调用的适当方法:

  • 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:从调用接口方法的实例的运行时 class 开始,使用上面构造的接口表,并替换泛型 arguments,如果有的话,在调用 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对于与接口方法关联的列表中的每个方法,如果存在其泛型类型 arguments 与此实例化完全匹配的方法(或者没有泛型类型参数),则调用第一个方法
    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否则,如果列表中存在一个方法,其泛型类型参数具有正确的变体关系,则调用列表中的第一个这样的方法

This is saying that if there's no exact match, then take the first method in the type declaration order whose type parameters have the correct variance relationship.这就是说,如果没有完全匹配,则采用类型声明顺序中类型参数具有正确变化关系的第一个方法。

Your example appears as Case 6 in the section "II.12.2.1 Interface Implementation Examples", where the type S4<V> implements both IVarImpl (which means implementing IVar<A> ) and IVar<B> , and the example shows that calling the method IVar<C>::P(C) on an instance of S4<A> results in the method S1<A,B>::P(:0:A) (that is, void P(A) ) being called.您的示例在“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) )被调用。

Indeed, if we swap the order of ITest<A> and ITest<B> in the declaration of Test , we can see that the ITest<B>.DoTest(B instance) implementation ends up being called.事实上,如果我们在Test的声明中交换ITest<A>ITest<B>的顺序,我们可以看到ITest<B>.DoTest(B instance)实现最终被调用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM