且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

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

更新时间:2023-01-17 17:31:08

我创建了一个更短的更多的 SSCCE ,说明了这个问题:

  class Program 
{
static void Main()
{
dynamic obj = new Third< int>();
打印(obj); //导致堆栈溢出
}

static void Print(object obj){}
}

class First< T>其中T:First< T> {}

class Second< T> :第一< T>其中T:First< T> {}

class Third< T> :第二< Third>>查看调用堆栈,似乎在C#中的两对符号之间弹跳,运行时绑定器:
  Microsoft.CSharp.RuntimeBinder.SymbolTable.LoadSymbolsFromType(
System.Type originalType


Microsoft.CSharp.RuntimeBinder.SymbolTable.GetConstructedType(
System.Type类型,
Microsoft.CSharp.RuntimeBinder.Semantics.AggregateSymbol agg

  Microsoft.CSharp.RuntimeBinder.Semantics.TypeManager.SubstTypeCore(
Microsoft.CSharp.RuntimeBinder.Semantics.CType类型,
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上提交一个bug;如果编译器没有被这个捕获,那么运行时绑定程序可能不应该是。






此代码示例运行正确地:

  class Program 
{
static void Main()
{
dynamic obj = new Second< int>();
打印(obj);
}

static void Print(object obj){}
}

内部类首先< T>
其中T:First< T> {}

内部类Second< T> :第一< Second< T> {}

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


In a program I'm using the dynamic keyword to invoke the best matching method. However, I have found that the framework crashes with a ***Exception under some circumstances.

I have tried to simplify my code as much as possible while still being able to re-produce this problem.

class Program
{
    static void Main(string[] args)
    {
        var obj = new SetTree<int>();
        var dyn = (dynamic)obj;
        Program.Print(dyn); // throws ***Exception!!
        // 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");
    }
}

That program would normally print "set" if the newed up instance implements the ISortedSet<TKey> interface and print "object" for anything else. But, with the following declarations a ***Exception is thrown instead (as noted in a comment above).

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

Whether this is a bug or not it is very troubling that a ***Exception is thrown as we are unable to catch it and also pretty much unable to determine in advance whether an exception will be thrown (and thereby terminate the process!).

Can someone please explain what's going on? Is this a bug in the framework?

When debugging and switching to "Disassembly mode" I'm seeing this:

Register dump at that location:

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

That doesn't tell me much more than being an indicator that this indeed must be some kind of bug in the framework.

I've filed a bug report on Microsoft Connect but I'm interested in knowing what's going on here. Are my class declarations unsupported in some way?

Not knowing WHY this is happening causes me to worry about other places where we are using the dynamic keyword. Can I not trust that at all?

I created a shorter, more to-the-point SSCCE that illustrates the problem:

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

Looking at the call stack, it seems to be bouncing between two pairs of symbols in the C# runtime binder:

Microsoft.CSharp.RuntimeBinder.SymbolTable.LoadSymbolsFromType(
    System.Type originalType
)

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

and

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
)

If I had to hazard a guess, some of the generic type constraint nesting you've got going on has managed to confuse the binder into recursively walking the types involved in the constraints along with the constraints themselves.

Go ahead and file a bug on Connect; if the compiler doesn't get caught by this, the runtime binder probably shouldn't either.


This code example runs correctly:

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

This leads me to believe (without much knowledge of the internals of the runtime binder) that it's proactively checking for recursive constraints, but only one level deep. With an intermediary class in between, the binder ends up not detecting the recursion and tries to walk it instead. (But that's all just an educated guess. I'd add it to your Connect bug as additional information and see if it helps.)