【问题标题】:Mystery with dynamic type & double dispatch动态类型和双重调度的奥秘
【发布时间】:2015-01-16 16:10:39
【问题描述】:

最近我在尝试通过动态类型实现双重调度时遇到了一个有趣的问题。

一点背景知识:在我的一个项目中,我使用 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);
        }
    }

这怎么可能?还是我只是累了需要睡觉?

无论如何,这是一个概念性和教育性的问题,而不是愿意在特定图书馆中找到解决特定问题的方法。

更新1: 已验证 4.0 和 4.5 目标框架存在问题,已在 VS2010(SP1)、VS2013 中验证。

更新2: 简单的控制台应用程序也挂在同一行(所以,这不是 test-runner 的问题):

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 上创建了独立示例。

【问题讨论】:

  • 你为什么要分配给动态?只需分配给对象,因为您将它传递给无论如何都需要对象的方法。至于这个问题,我有时会发现测试框架遇到类型问题是导致挂起的真正原因。
  • @Steve Lillis,感谢您的建议,但是,就像我在描述中所说的那样,它是实际工作代码的非常简化的版本 - 只是为了调查问题。我可以很容易地找到解决方法,这没什么大不了的。我只是想了解为什么会发生这种情况。
  • 当然,我应该想到的。如果在这个迷你测试用例场景中分配给对象而不是动态,测试是否仍然挂起?可能是 NUnit 与分配给动态的泛型苦苦挣扎(出于推断对象测试是否通过的原因)
  • @Steve Lillis,只有在使用动态类型时才会挂起测试。
  • 我只能建议将其作为 NUnit 的 git 上的问题提出。绝对看起来像一个框架打嗝,而不是你做错了什么。 github.com/nunit/nunit/issues

标签: c# c#-4.0 dynamic structuremap structuremap3


【解决方案1】:

问题在于 StructureMap 的 LambdaInstance&lt;T&gt; 类继承。 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&lt;T, TPluginType&gt; 继承ExpressedInstance&lt;T, TReturned, TPluginType&gt;,但特化为T:LambdaInstance&lt;T, TPluginType&gt;。 所以泛型参数T 的特化是子类型定义-LambdaInstance&lt;T, TPluginType&gt;。 这会在运行时获取构造类型的情况下创建 循环 引用,这是运行时绑定器对不变多态行为的要求。

如果您需要问题的根源,请查看 Microsoft.CSharp.RuntimeBinder.SymbolTable 类的私有方法 LoadSymbolsFromType(Type originalType)GetConstructedType(Type type, AggregateSymbol agg) ( Microsoft.CSharp.dll 程序集)。 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[])

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-28
    相关资源
    最近更新 更多