逆变接口方法调度/选择 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());
}
}
输出为:
Void ITest.DoTest(A)
对我来说,这不是预期的行为,或者至少不是大多数开发人员所期望的行为。预期输出为:
Void ITest.DoTest(B)
这里应该使用“最佳”实现,最好的含义是具有最多派生类型参数的泛型接口,直到逆变“静态” “ 类型。
相反,似乎选择了“最差”。
检查生成的 IL 并不会揭示选择机制,因为调用是通过正确的静态类型分派的,因此我假设由 CLR 来选择实现:
.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
为什么它会这样?这种情况下的规则是什么?它在幕后是如何运作的?
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:
Void 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:
Void 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:
.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?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
这是在 ECMA-335< 的第 II.12.2 节中指定的/a>.
规范的相关片段如下:
因此我们将类型声明顺序定义为这些接口在类声明中出现的顺序。
这就是说,如果没有完全匹配,则采用类型声明顺序中类型参数具有正确方差关系的第一个方法。
您的示例在“II.12.2.1 接口实现示例”部分中显示为情况 6,其中类型
S4
实现了IVarImpl
(这意味着实现IVar
)和IVar
,示例显示调用该方法S4
实例上的
IVar::P(C)
会产生方法S1::P(! 0:A)
(即void P(A)
)被调用。事实上,如果我们在
Test
的声明中交换ITest
和ITest
的顺序,我们可以看到 < code>ITest.DoTest(B instance) 实现最终被调用。This is specified in Section II.12.2 of ECMA-335.
Relevant snippets of the spec follows:
So we've defined the type declaration order as the order in which these interfaces appear in the class declaration.
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 bothIVarImpl
(which means implementingIVar<A>
) andIVar<B>
, and the example shows that calling the methodIVar<C>::P(C)
on an instance ofS4<A>
results in the methodS1<A,B>::P(!0:A)
(that is,void P(A)
) being called.Indeed, if we swap the order of
ITest<A>
andITest<B>
in the declaration ofTest
, we can see that theITest<B>.DoTest(B instance)
implementation ends up being called.