1

Recently I came across an interesting problem trying to implement double dispatch via dynamic type.

A little background: in one of my projects I use StructureMap container and dynamic type as a clean way to dispatch method calls at runtime. After updating StructureMap container to the newer version(3), some of my unit-tests started to hang eternally.

To reproduce the problem I created 2 maximally simplified unit-tests: The first test hangs forever on the line marked with (*), the second test passes as expected. The only difference between them is that method of the first one returns StructureMap's object of type LambdaInstance.

The hanging test:

    [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              
        }
    }

The passing test:

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

How it could be possible? Or I am just tired and need to go sleep?

Anyway, it's conceptual and educational question, rather than willing to find fix for the particular problem in the particular library.

UPDATE1: Verified that problem exists for 4.0 & 4.5 target Frameworks, verified in VS2010(SP1), VS2013.

UPDATE2: Simple console application is hanging on the same line as well(so, it's not the problem with 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            
    }
}

Also I created standalone example on GitHub.

alekseevi15
  • 1,732
  • 2
  • 16
  • 20
  • Why are you assigning to dynamic? Just assign to object since you're passing it to a method that takes object anyway. As for the issue, I've sometimes found testing frameworks having trouble with the types to be the true cause of a hang. – Steve Lillis Nov 19 '14 at 08:17
  • @Steve Lillis, thanks for advice, but, like I said in description, it is very simplified version of real working code - just to investigate the problem. I could easily find workaround, it's not a big deal. I just want to understand why this actually happens. – alekseevi15 Nov 19 '14 at 08:22
  • Of course, I should have thought. Does the test still hang if you assign to object instead of dynamic in this mini test case scenario? Could be NUnit struggling with generic assigned to dynamic (for reasons to deduce if the object test passes) – Steve Lillis Nov 19 '14 at 08:27
  • @Steve Lillis, No test only hangs when dynamic type is in use. – alekseevi15 Nov 19 '14 at 08:37
  • Only thing I can suggest then is to raise it as an issue on NUnit's git. Definitely looks like a framework hiccup rather than anything you've done wrong. https://github.com/nunit/nunit/issues – Steve Lillis Nov 19 '14 at 08:43
  • @Steve Lillis, thanks, but it seems that it's not NUnit who is guilty for that. I've just added example of simple console application which is hanging analogically. – alekseevi15 Nov 19 '14 at 13:05
  • Hmm. Unfortunately this is beyond my scope of expertise. If you find out what causes it, I'd love to know! – Steve Lillis Nov 19 '14 at 13:22
  • What is `LambdaInstance`? Maybe I'm just not searching correctly, but I can't find any reference to that type on MSDN, or even on the web. Can you provide a genuinely self-contained code example to reproduce the issue? I.e. where all of the types used are either declared in the example or present in the .NET assemblies? – Peter Duniho Nov 19 '14 at 18:01
  • @Peter Duniho, I've just updated answer with link to the standalone example. – alekseevi15 Nov 20 '14 at 09:21
  • @ialekseev: sorry...the only example that really matters is the entirely self-contained one, and it belongs here with this question, not as a link to another web site. – Peter Duniho Nov 20 '14 at 17:21
  • @PeterDuniho, but you could find the whole example above(console application). That's it. LambdaInstance is a class from StructureMap library. That's why I created standalone example on GitHub. However I could attach link to StructureMap's LambdaInstance as well: [link](https://github.com/structuremap/structuremap/blob/master/src/StructureMap/Pipeline/LambdaInstance.cs) – alekseevi15 Nov 20 '14 at 17:29
  • The point is to come up with a _concise_ example that reproduces the problem. I doubt that the _entire_ declaration of `LambdaInstance` is responsible for whatever behavior is going on here. Your job as the questioner is to take the time to create the _smallest_ code example possible which still reproduces the problem, and then include _in the question itself_. See http://stackoverflow.com/help/mcve – Peter Duniho Nov 20 '14 at 18:12
  • @PeterDuniho, you will be laughing but _it is_ the smallest code example. And, yes, LambdaInstance is the possible culprit of the problem. – alekseevi15 Nov 20 '14 at 18:25

1 Answers1

1

The issue is located at StructureMap's LambdaInstance<T> class inheritance. Usage of C# dynamic involves creation of polymorphic callsites, which ones does use runtime binders.

Consider simplified inheritance tree for LambdaInstance class:

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>
{

}

As shown above LambdaInstance<T, TPluginType> inherites ExpressedInstance<T, TReturned, TPluginType>, but with specialization of T:LambdaInstance<T, TPluginType>. So specialization of generic parameter T is child type definition - LambdaInstance<T, TPluginType>. This creates circular reference in a case of getting constructed type at runtime as required by runtime binders for invariant polymorphic behaviour.

If you need the source of issue take a look at private methods LoadSymbolsFromType(Type originalType) and GetConstructedType(Type type, AggregateSymbol agg) of Microsoft.CSharp.RuntimeBinder.SymbolTable class (Microsoft.CSharp.dll assembly). The methods LoadSymbolsFromType and GetConstructedType recusively calls each other, while instantiating new types.

To check this without even having framework sources, try to dissect generics specialization by providing predefined type, i.e. System.Int32 for example.

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>
{

}

Run application. The System.StackOverflowException will be thrown. Using debugger and disassembly mode - the issue source would be System.RuntimeTypeHandle.Instantiate(System.Type[]).

szKarlen
  • 66
  • 4