C# · 12月 31, 2021

当通过动态访问通用类型的成员时,StackOverflowException:.NET / C#框架错误?

在一个程序中,我使用dynamic关键字来调用最佳匹配方法.但是,在某些情况下,我发现框架崩溃了StackOverflowException.

我尽可能地简化了我的代码,同时仍然能够重新生成这个问题.

class Program{ static void Main(string[] args) { var obj = new SetTree<int>(); var dyn = (dynamic)obj; Program.Print(dyn); // throws StackOverflowException!! // Note: this works just fine for ‘everything else’ but my SetTree<T> } static void Print(object obj) { Console.WriteLine(“object”); } static void Print<TKey>(ISortedSet<TKey> obj) { Console.WriteLine(“set”); }}

如果新增的实例实现了ISTEDSet< TKey>,则该程序通常将打印“set”界面和打印“对象”的其他任何东西.但是,使用以下声明,将抛出一个StackOverflowException(如上述注释中所述).

interface ISortedSet<TKey> { }sealed class SetTree<TKey> : BalancedTree<SetTreeNode<TKey>>,ISortedSet<TKey> {}abstract class BalancedTree<TNode> where TNode : TreeNode<TNode> { }abstract class SetTreeNode<TKey> : KeyTreeNode<SetTreeNode<TKey>,TKey> { }abstract class KeyTreeNode<TNode,TKey> : TreeNode<TNode> where TNode : KeyTreeNode<TNode,TKey> { }abstract class TreeNode<TNode> where TNode : TreeNode<TNode> { }

这是否是一个错误,因为我们无法捕捉到StackOverflowException是非常麻烦的,并且几乎无法提前确定是否抛出异常(从而终止进程!).

有人可以解释发生了什么吗?这是框架中的错误吗?

当调试并切换到“拆卸模式”时,我看到:

在该位置注册转储:

EAX=02B811B4 EBX=0641EA5C ECX=02C3B0EC EDX=02C3A504 ESI=02C2564CEDI=0641E9AC EIP=011027B9 ESP=0641E91C EBP=0641E9B8 EFL=00000202

这不仅仅是一个指标,它确实必须是框架中的某种错误.

我有filed a bug report on Microsoft Connect,但我有兴趣知道这里发生了什么.我的类声明在某种程度上不受支持吗?

不知道为什么发生这种情况导致我担心使用动态关键字的其他地方.我可以不信任吗?

解决方法 我创建了一个较短的,更加明确的 SSCCE,说明了这个问题: class Program{ static void Main() { dynamic obj = new Third<int>(); Print(obj); // causes stack overflow } static void Print(object obj) { }}class First<T> where T : First<T> { }class Second<T> : First<T> where T : First<T> { }class Third<T> : Second<Third<T>> { }

看看调用堆栈,似乎在C#运行时绑定器中的两对符号之间弹起来:

Microsoft.CSharp.RuntimeBinder.SymbolTable.LoadSymbolsFromType( System.Type originalType)Microsoft.CSharp.RuntimeBinder.SymbolTable.GetConstructedType( System.Type type,Microsoft.CSharp.RuntimeBinder.Semantics.AggregateSymbol agg)

Microsoft.CSharp.RuntimeBinder.Semantics.TypeManager.SubstTypeCore( Microsoft.CSharp.RuntimeBinder.Semantics.CType type,Microsoft.CSharp.RuntimeBinder.Semantics.SubstContext pctx)Microsoft.CSharp.RuntimeBinder.Semantics.TypeManager.SubstTypeArray( Microsoft.CSharp.RuntimeBinder.Semantics.TypeArray taSrc,Microsoft.CSharp.RuntimeBinder.Semantics.SubstContext pctx)

如果我不得不冒犯一些猜测,一些通常的类型约束嵌套你已经进行了一切设法混淆了绑定器,循序渐进地介绍了约束条件以及约束本身.

继续在Connect上提交错误;如果编译器没有被这个捕获,那么运行时绑定可能也不应该.

此代码示例正确运行:

class Program{ static void Main() { dynamic obj = new Second<int>(); Print(obj); } static void Print(object obj) { }}internal class First<T> where T : First<T> { }internal class Second<T> : First<Second<T>> { }

这导致我相信(没有关于运行时绑定的内部知识),它主动检查递归约束,但只有一个级别.在中间类之间,绑定器最终没有检测到递归,并试图走走它. (但这只是一个有教养的猜测,我会将其添加到您的Connect bug作为附加信息,看看它是否有帮助.)