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)  
error_404
  • 346
  • 2
  • 9
  • 12
  • 1
    So um... Have you looked at the actual stack while debugging to see where it recurses infinitely? – millimoose Oct 26 '13 at 21:54
  • 5
    Looks like a bug in the DLR... – Thomas Levesque Oct 26 '13 at 22:03
  • 1
    If you change `dynamic d` to `var d` it runs fine, looks like some internal reflection magic causing trouble. – CodeCaster Oct 26 '13 at 22:04
  • @millimoose I've added the callstack as reported by visual studio. Though it seems odd that the top few methods are completely different from the hundreds before them (which is more or less an endless list of `SubstTypeCore` followed by `SubstTypeArray`) – error_404 Oct 26 '13 at 22:04
  • 1
    I'm not sure it's a bug. Do you get the error when you run the code or during build? Can the compiler really infer what type value should be? – o_weisman Oct 27 '13 at 16:15
  • @o_weisman The exception occurs when executing the program. – error_404 Oct 27 '13 at 18:06
  • 3
    The inner type is not required. `Generic : BindingList>` will reproduce it as well as well. – zastrowm Oct 28 '13 at 01:31
  • 2
    I think it is a bug in the DLR, and I think it occurs because `BindingList` inherits from `Collection` (whereas `List` inherits from object). See [this gist](https://gist.github.com/default-kramer/7239781) for my attempt at a minimal repro. As @MackieChan points out, the inner type is not required. – default.kramer Oct 30 '13 at 20:37
  • 2
    Is this the same bug http://stackoverflow.com/questions/22672775/net-c-framework-bug ? – Marcin Wisnicki May 19 '14 at 13:06
  • 1
    @MarcinWisnicki it is, the same fix that fixes that bug fixes this one too. +1 for this one for pointing out that the stack overflows when it should throw RBE as well as when it should work. I'm copying that for a test case. – Jon Hanna Nov 11 '17 at 19:59

2 Answers2

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!

Mike Zboray
  • 39,828
  • 3
  • 90
  • 122
Anton
  • 731
  • 4
  • 5
  • 1
    what do you mean? It doesn't break for BindingList! As I said problem not in BindingList problem in BindingList.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 – Anton Oct 29 '13 at 00:08
  • 1
    Re-read the OP. Or just try it yourself! If you define it as `class Generic : BindingList.Inner>` it breaks, but if you define it as `class Generic : List.Inner>` it works. What is causing this difference? – default.kramer Oct 29 '13 at 14:23
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.

Anton
  • 731
  • 4
  • 5
  • 2
    I came to the same conclusion: that an inherited generic type is the issue. (I posted [this gist](https://gist.github.com/default-kramer/7239781) 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. – default.kramer Oct 31 '13 at 23:42
  • 1
    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!!! – Anton Nov 01 '13 at 00:00
  • [It is a bug](http://stackoverflow.com/a/22673972/429091). 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. – binki Apr 08 '15 at 14:54