通过动态访问泛型类型的成员时发生StackOverflowException:.NET/C#framework错误?

通过动态访问泛型类型的成员时发生StackOverflowException:.NET/C#framework错误?,c#,.net,dynamic,C#,.net,Dynamic,在一个程序中,我使用dynamic关键字来调用最佳匹配方法。但是,我发现在某些情况下,框架会因StackOverflowException而崩溃 我试图尽可能地简化代码,同时仍然能够重新产生这个问题 class Program { static void Main(string[] args) { var obj = new SetTree<int>(); var dyn = (dynamic)obj; Program.

在一个程序中,我使用
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");
    }
}
无论这是否是一个bug,抛出一个
StackOverflowException
都是非常麻烦的,因为我们无法捕获它,也几乎无法提前确定是否会抛出异常(从而终止进程!)

有人能解释一下发生了什么事吗?这是框架中的一个bug吗

当调试并切换到“反汇编模式”时,我看到:

在该位置注册转储:

这只不过是一个指标,表明这确实是框架中的某种缺陷

我知道,但我想知道这里发生了什么。我的类声明在某种程度上不受支持吗


不知道为什么会发生这种情况会让我担心我们正在使用
动态
关键字的其他地方。我完全不能相信这一点吗?

我创建了一个更简短、更切题的例子来说明问题:

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

如果我不得不冒险猜测的话,您正在进行的一些泛型类型约束嵌套已经成功地将绑定器混淆为递归地遍历约束中涉及的类型以及约束本身

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


此代码示例正确运行:

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>> { }
类程序
{
静态void Main()
{
动态对象=新的第二个();
打印(obj);
}
静态无效打印(对象obj){}
}
内部类优先
其中T:First{}
内部类第二:第一{}

这让我相信(不太了解运行时绑定器的内部结构),它正在主动检查递归约束,但只有一个层次的深度。中间有一个中间类,绑定器最终没有检测到递归,而是尝试遍历它。(但这只是一个有根据的猜测。我会将其作为附加信息添加到您的Connect bug中,看看是否有帮助。)

问题在于您是从自身派生类型的:

abstract class SetTreeNode<TKey> : KeyTreeNode<SetTreeNode<TKey>, TKey> { }

抽象类SetTreeNode:KeyTreeNode{}
类型
SetTreeNote
变为
KeyTreeNode
,该类型变为
KeyTreeNode
,并一直持续到堆栈溢出

我不知道你试图通过使用这个复杂的模型来实现什么,但这是你的问题

我设法将其简化为以下失败的示例:

interface ISortedSet<TKey> { }

sealed class SetTree<TKey> : BalancedTree<SetTreeNode<TKey>>, ISortedSet<TKey> { }

abstract class BalancedTree<TNode> { }

abstract class SetTreeNode<TKey> : KeyTreeNode<SetTreeNode<TKey>, TKey> { }

abstract class KeyTreeNode<TNode, TKey> : TreeNode<TNode> { }

abstract class TreeNode<TNode> { }
接口ISortedSet{}
密封类集合树:BalancedTree,ISortedSet{}
抽象类平衡树{}
抽象类SetTreeNode:KeyTreeNode{}
抽象类KeyTreeNode:TreeNode{}
抽象类TreeNode{}
然后我通过这样做修复了它:

interface ISortedSet<TKey> { }

sealed class SetTree<TKey> : BalancedTree<SetTreeNode<TKey>>, ISortedSet<TKey> { }

abstract class BalancedTree<TNode> { }

abstract class SetTreeNode<TKey> : KeyTreeNode<TKey, TKey> { }

abstract class KeyTreeNode<TNode, TKey> : TreeNode<TNode> { }

abstract class TreeNode<TNode> { }
接口ISortedSet{}
密封类集合树:BalancedTree,ISortedSet{}
抽象类平衡树{}
抽象类SetTreeNode:KeyTreeNode{}
抽象类KeyTreeNode:TreeNode{}
抽象类TreeNode{}
两者之间唯一的区别是我用
KeyTreeNode
替换了
KeyTreeNode

有人能解释一下发生了什么事吗?这是框架中的一个bug吗

问题在于泛型类型如何被解析为其特定的具体用途

好的,让我们从一些明显的东西开始,以了解编译器出了什么问题。正如您所知,对于类似于
List
的东西,编译器(无论是动态编译器,还是自C#2引入泛型以来的任何静态编译器)必须采用
List
类型和
int
类型,并结合这两种类型的信息来生成
List
类型

现在,考虑一下:

public class Base<T, U>
{

}

public class Derived<T> : Base<T, int>
{

}

Derived<long> l = new Derived<long>();
公共类基
{
}
派生的公共类:基
{
}
派生l=新派生();
在这里,您可以看到,在对类型
派生的
长的
进行相同的工作时,编译器必须填充三个插槽:

  • 派生的
    上定义的
    T
    ,用
    long
    填充
  • Base
    上定义的
    T
    ,其中填充了在
    Derived
    上定义的
    T
    ,其中填充了
    long
  • Base
    上定义的
    U
    ,其中填充
    int
  • 当您考虑嵌套类、长继承链、从其他泛型类型派生的泛型类型以及添加其他泛型参数等时,可以看到有好几种不同的排列要覆盖。如果你从<代码>派生开始,并且必须回答“类的基本类型是什么”的问题(显然编译器需要考虑很多),那么所有这些都必须解决。 <>动态编译程序是基于前RSLYN静态编译器,它是基于以前编译的编译器,实际上是用C++编写的,而C语言中还有很多动态编译器,而C语言中有C++的味道。可以认为,在端点(可以执行的代码)上比在起点上更相似;静态编译器必须解析的一组文本,以了解所涉及的类型和操作,而动态编译器则从对象和标志表示的现有类型和操作开始

    他们都需要知道的一件事是,如果一个类型被多次提及,那么它就是同一个类型(这几乎是类型含义的最基本定义,在
    abstract class SetTreeNode<TKey> : KeyTreeNode<SetTreeNode<TKey>, TKey> { }
    
    interface ISortedSet<TKey> { }
    
    sealed class SetTree<TKey> : BalancedTree<SetTreeNode<TKey>>, ISortedSet<TKey> { }
    
    abstract class BalancedTree<TNode> { }
    
    abstract class SetTreeNode<TKey> : KeyTreeNode<SetTreeNode<TKey>, TKey> { }
    
    abstract class KeyTreeNode<TNode, TKey> : TreeNode<TNode> { }
    
    abstract class TreeNode<TNode> { }
    
    interface ISortedSet<TKey> { }
    
    sealed class SetTree<TKey> : BalancedTree<SetTreeNode<TKey>>, ISortedSet<TKey> { }
    
    abstract class BalancedTree<TNode> { }
    
    abstract class SetTreeNode<TKey> : KeyTreeNode<TKey, TKey> { }
    
    abstract class KeyTreeNode<TNode, TKey> : TreeNode<TNode> { }
    
    abstract class TreeNode<TNode> { }
    
    public class Base<T, U>
    {
    
    }
    
    public class Derived<T> : Base<T, int>
    {
    
    }
    
    Derived<long> l = new Derived<long>();