16

I have defined a generic class that derives from BindingList and has a nested non-generic class:

class Generic<T> : BindingList<Generic<T>.Inner>
{
    public class Inner
    {
        public object Foo { get; set; }
    }
}

A StackOverflowException occurs in mscorlib when attempting to access the Value property via a dynamic reference like so:

dynamic d = new Generic<string>.Inner();
var value = d.Foo; // StackOverflowException

var value = d.Bar    // StackOverflowException as well, not a 
                     // 'RuntimeBinderException' like you would expect when
                     // trying to access a non-existing member

This is the smallest reproduction i was able to make.

Deriving from BindingList is an important detail, if i change it to a List the program executes correctly.

Why does this happen?

Edit:

This is the top of the call stack:

[Managed to Native Transition]  
mscorlib.dll!System.RuntimeTypeHandle.Instantiate(System.Type[] inst)   
mscorlib.dll!System.RuntimeType.MakeGenericType(System.Type[] instantiation)    
Microsoft.CSharp.dll!Microsoft.CSharp.RuntimeBinder.Semantics.CType.CalculateAssociatedSystemTypeForAggregate(Microsoft.CSharp.RuntimeBinder.Semantics.AggregateType aggtype)   
Microsoft.CSharp.dll!Microsoft.CSharp.RuntimeBinder.Semantics.CType.CalculateAssociatedSystemType(Microsoft.CSharp.RuntimeBinder.Semantics.CType src)   
Microsoft.CSharp.dll!Microsoft.CSharp.RuntimeBinder.Semantics.CType.AssociatedSystemType.get()  
Microsoft.CSharp.dll!Microsoft.CSharp.RuntimeBinder.Semantics.TypeManager.GetAggregate(Microsoft.CSharp.RuntimeBinder.Semantics.AggregateSymbol agg, Microsoft.CSharp.RuntimeBinder.Semantics.AggregateType atsOuter, Microsoft.CSharp.RuntimeBinder.Semantics.TypeArray typeArgs)  
Microsoft.CSharp.dll!Microsoft.CSharp.RuntimeBinder.Semantics.TypeManager.GetAggregate(Microsoft.CSharp.RuntimeBinder.Semantics.AggregateSymbol agg, Microsoft.CSharp.RuntimeBinder.Semantics.TypeArray typeArgsAll)    
Microsoft.CSharp.dll!Microsoft.CSharp.RuntimeBinder.Semantics.TypeManager.GetAggregate(Microsoft.CSharp.RuntimeBinder.Semantics.AggregateSymbol agg, Microsoft.CSharp.RuntimeBinder.Semantics.TypeArray typeArgsAll)    
Microsoft.CSharp.dll!Microsoft.CSharp.RuntimeBinder.Semantics.TypeManager.SubstTypeCore(Microsoft.CSharp.RuntimeBinder.Semantics.CType type, Microsoft.CSharp.RuntimeBinder.Semantics.SubstContext pctx)    
Microsoft.CSharp.dll!Microsoft.CSharp.RuntimeBinder.Semantics.TypeManager.SubstTypeArray(Microsoft.CSharp.RuntimeBinder.Semantics.TypeArray taSrc, Microsoft.CSharp.RuntimeBinder.Semantics.SubstContext pctx)  
10
  • 1
    So um... Have you looked at the actual stack while debugging to see where it recurses infinitely? Commented Oct 26, 2013 at 21:54
  • 5
    Looks like a bug in the DLR... Commented Oct 26, 2013 at 22:03
  • 3
    The inner type is not required. Generic<T> : BindingList<Generic<T>> will reproduce it as well as well. Commented Oct 28, 2013 at 1:31
  • 2
    I think it is a bug in the DLR, and I think it occurs because BindingList<T> inherits from Collection<T> (whereas List<T> inherits from object). See this gist for my attempt at a minimal repro. As @MackieChan points out, the inner type is not required. Commented Oct 30, 2013 at 20:37
  • 2
    Is this the same bug stackoverflow.com/questions/22672775/net-c-framework-bug ? Commented May 19, 2014 at 13:06

2 Answers 2

2

I think the problem is in this place

Generic<T> :BindingList<Generic<T>.Inner>

Notice you use the declared class as a generic parameter in the parent class BindingList. So I believe reflection just ends up with an infinitive loop and you get StackOverflow.

When you use

var d = new Generic<string>.Inner();

compiler just replaces it with Generic.Inner so it is the same like

Generic<string>.Inner d = new Generic<string>.Inner();

But when you use

dynamic d = new Generic<string>.Inner();

You really use reflection. Again reflection starts digging deeper in your class structure and it goes like... your class => BindingList = > generic parameter of BindingList => your class(because it's a generic parameter of BindingList) = > BindingList = > and so on until you get StackOverflow.

You can change to Generic<T> : BindingList<string> to break this infinitive loop and it works!

Sign up to request clarification or add additional context in comments.

2 Comments

what do you mean? It doesn't break for BindingList<string>! As I said problem not in BindingList problem in BindingList<Generic<T>.Inner>. Because you create an infinitive loop by making reflection go from Generic to BindingList and again to Generic and again to BindingList. So you run out of memory and get this exception
Re-read the OP. Or just try it yourself! If you define it as class Generic<T> : BindingList<Generic<T>.Inner> it breaks, but if you define it as class Generic<T> : List<Generic<T>.Inner> it works. What is causing this difference?
1

Thank you very much for your correction! I investigated this I would say very interesting moment and found that I was right.

First of all, this is not a BUG! This is just how the Microsoft team solved this issue. Again all what I wrote above I believe is true!

So as I said you end up with an infinitive loop and get StackOverflow, but it seems to me that you get it very very fast. So no any long periods when you have no any access to your machine and just it looks like it's dead. I started digging deeper into the structure of BindingList and here the results.

I created

class Example<T> : Level1<Example<T>>
{
    public string Name = "111";
}

public class Level1<T>
{

}

and in the main

dynamic d = new Example<string>();
var value = d.Name;

and it works! Then I added another level

public class Level1<T> : Level2<T>
{

}

public class Level2<T>
{

}

and I got StackOverflow. I changed to

public class Level1<T> : Level2
{

}

public class Level2
{

}

and it works again!

So I think that the guys from Microsoft just said ... so this is the max level after no way through and throw the exception.

Now let's look at BindingList<T>

public class BindingList<T> : Collection<T>, 
    IBindingList, IList, ICollection, IEnumerable, ICancelAddNew, 
    IRaiseItemChangedEvents

Notice Collection<T>

And look at List<T>

public class List<T> : IList<T>, ICollection<T>, 
    IList, ICollection, IReadOnlyList<T>, IReadOnlyCollection<T>, IEnumerable<T>, 
    IEnumerable

Just interfaces....

Therefore it works with List but not with BindingList!My example proves that!I believe they did it intentionally to stop infinitive looping.

3 Comments

I came to the same conclusion: that an inherited generic type is the issue. (I posted this gist on the OP in case you missed it.) But saying "this is not a bug" is a bold claim. At the very least, it should throw something like a NotSupportedException instead.
I really don't know. Look I believe they just said what you are doing leads you to a StackOverflow exception so they thow it. They just prevented your machine from a failure and real stack overflow situation. Not Supported means not possible, but it's not true. It's possible , but it will kill your system. Again it's very relative and up to designers. But you have found a rear issue man!!!
It is a bug. The DLR is unable to resolve an object member that the C# compiler can. The fact that changing the nesting level triggers and then doesn’t trigger the bug is just a sign of how fragile the DLR’s type/member resolution implementation is.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.