动态类型和双重调度的神秘感
本文关键字:调度 神秘感 类型 动态 | 更新日期: 2023-09-27 18:31:00
最近我遇到了一个有趣的问题,试图通过动态类型实现双重调度。
一点背景:在我的一个项目中,我使用 StructureMap 容器和动态类型作为在运行时调度方法调用的干净方式。在将 StructureMap 容器更新到较新版本(3)后,我的一些单元测试开始永久挂起。
为了重现这个问题,我创建了 2 个最大程度简化的单元测试:第一个测试永远挂在标有 (*) 的行上,第二个测试按预期通过。它们之间的唯一区别是第一个方法返回StructureMap的类型为LambdaInstance的对象。
悬挂测试:
[TestFixture]
[Category("Unit")]
public class when_trying_to_call_method_with_dynamic_argument1
{
private class A {}
private static LambdaInstance<object> Method(A obj)
{
throw new NotImplementedException();
}
[Test]
[ExpectedException(typeof(NotImplementedException))]
public void should_succeed()
{
var instance = (dynamic)new A();
Method(instance); //(*)hangs forever on this line
}
}
合格测试:
[TestFixture]
[Category("Unit")]
public class when_trying_to_call_method_with_dynamic_argument2
{
private class A {}
private static object Method(A obj)
{
throw new NotImplementedException();
}
[Test]
[ExpectedException(typeof(NotImplementedException))]
public void should_succeed()
{
var instance = (dynamic)new A();
Method(instance);
}
}
怎么可能?还是我只是累了,需要睡觉?
无论如何,这是概念和教育问题,而不是愿意为特定库中的特定问题找到解决方案。
UPDATE1:已验证4.0和4.5目标框架是否存在问题,已在VS2010(SP1),VS2013中验证。
UPDATE2:简单的控制台应用程序也挂在同一行上(因此,这不是测试运行器的问题):
class Program
{
private class A { }
private static LambdaInstance<object> Method(A obj)
{
throw new NotImplementedException();
}
static void Main(string[] args)
{
var instance = (dynamic)new A();
Method(instance); //(*)hangs forever on this line
}
}
我还在GitHub上创建了独立示例。
问题位于 StructureMap 的LambdaInstance<T>
类继承。C# 动态的使用涉及创建多态调用站点,这些调用站点确实使用运行时绑定器。
考虑 LambdaInstance 类的简化继承树:
class Program
{
private static LambdaInstance<object> Method(object obj)
{
throw new NotImplementedException();
}
static void Main(string[] args)
{
var instance = (dynamic)new object();
Method(instance);
}
}
public class LambdaInstance<T> : LambdaInstance<T, T>
{
}
public class LambdaInstance<T, TPluginType> : ExpressedInstance<LambdaInstance<T, TPluginType>, T, TPluginType>
{
}
public abstract class ExpressedInstance<T>
{
}
public abstract class ExpressedInstance<T, TReturned, TPluginType> : ExpressedInstance<T>
{
}
如上所示,LambdaInstance<T, TPluginType>
继承了ExpressedInstance<T, TReturned, TPluginType>
,但具有T:LambdaInstance<T, TPluginType>
的专业化。所以泛型参数T
的专用化是子类型定义 - LambdaInstance<T, TPluginType>
.这会在运行时获取构造类型的情况下创建循环引用,这是运行时绑定程序对不变多态行为的要求。
如果您需要问题的根源,请查看Microsoft.CSharp.RuntimeBinder.SymbolTable
类(Microsoft.CSharp.dll程序集)的私有方法LoadSymbolsFromType(Type originalType)和GetConstructedType(Type type,AggregateSymbol agg)。方法LoadSymbolsFromType和GetConstructedType在实例化新类型时相互回避调用。
要在没有框架源代码的情况下检查这一点,请尝试通过提供预定义的类型来剖析泛型专用化,即 System.Int32
例如。
class Program
{
private static LambdaInstance<object> Method(object obj)
{
throw new NotImplementedException();
}
static void Main(string[] args)
{
var instance = (dynamic)new object();
Method(instance);
}
}
public class LambdaInstance<T> : LambdaInstance<T, int>
{
}
public class LambdaInstance<T, TPluginType> : ExpressedInstance<LambdaInstance<T, TPluginType>, int, TPluginType>
{
}
public abstract class ExpressedInstance<T>
{
}
public abstract class ExpressedInstance<T, TReturned, TPluginType> : ExpressedInstance<T>
{
}
运行应用程序。System.StackOverflowException
将被抛出。使用调试器和反汇编模式 - 问题源将是 System.RuntimeTypeHandle.Instantiate(System.Type[])。