在动态调用中涉及解开堆栈的难题

时间:2012-03-15 18:38:40

标签: c# .net dynamic clr dynamic-invoke

这是一个新的尝试,提出今天早上问题不成功的问题。

考虑以下程序,我们将在Visual Studio 2010中运行一次,直接双击可执行文件再次运行

namespace ConsoleApplication3
{
    delegate void myFoo(int i, string s);

    class Program
    {
        static void Main(string[] args)
        {
            Foo(1, "hello");
            Delegate Food = (myFoo)Foo;
            Food.DynamicInvoke(new object[] { 2, null });
        }

        static void Foo(int i, string s)
        {
            Console.WriteLine("If the next line triggers an exception, the stack will be unwound up to the .Invoke");
            Console.WriteLine("i=" + i + ", s.Length = " + s.Length);
        }
    }
}

当运行VS时Foo中的异常触发时,调试器正确显示堆栈并显示Foo中第二个WriteLine上出现问题。

但是当直接运行可执行文件时发生异常时,会从CLR获取一个弹出窗口,指示程序抛出了未处理的异常。单击debug并选择VS调试器。在这种情况下,堆栈展开到最近的.DynamicInvoke点,当您使用调试器附加时,异常时存在的堆栈上下文已部分丢失。

它以有限​​的形式存在于异常事件的“内部异常”部分中。单击以展开相关信息,并找到出现问题的行号。但显然局部变量和其他背景将会消失。

如果一个尝试同样的事情,但没有.DynamicInvoke(例如,在Main的第1行调用Foo(1,null)),仍然通过双击.exe文件,我们得到正确的行号调试器附加。类似地,如果通过单击.exe启动应用程序,但在抛出异常之前附加调试器。

有谁知道使用动态反射/调用的应用程序如何避免此问题?在我的预期用例中,在一个我不会在这里提到的名称的系统中,我无法预测将在.DynamicInvoke中使用的对象的类型签名,甚至不会预测将使用的参数的数量,因此静态类型甚至泛型都不是一种方法。

我的问题是这样的:有没有人知道为什么我们在直接从调试器运行时遇到这种不同的行为而不是在抛出异常后附加到程序?

2 个答案:

答案 0 :(得分:2)

根据评论,您是否认为NullReferenceException未处理取决于是否已处理。以下是调用Foo的一些方法,前三个将异常保留为未处理,后两个将通过包装它来处理NullReferenceException,并抛出新的异常。

using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace ConsoleApplication3
{
    delegate void myFoo(int i, string s);

    internal class Program
    {
        private static void Main(string[] args)
        {
            Foo(1, "hello");

            // From a delegate
            try
            {
                Delegate Food = (myFoo)Foo;
                ((dynamic)Food).Invoke(2, null);
            }
            catch (NullReferenceException ex)
            { Console.WriteLine("Caught NullReferenceException at " + ex.StackTrace); }

            MethodInfo Foom = typeof(Program).GetMethod("Foo", BindingFlags.Static | BindingFlags.NonPublic);

            // From a MethodInfo, obtaining a delegate from it
            try
            {
                Delegate Food = Delegate.CreateDelegate(typeof(Action<,>).MakeGenericType(Foom.GetParameters().Select(p => p.ParameterType).ToArray()), Foom);
                ((dynamic)Food).Invoke(2, null);
            }
            catch (NullReferenceException ex)
            { Console.WriteLine("Caught NullReferenceException at " + ex.StackTrace); }

            // From a MethodInfo, creating a plain Action
            try
            {
                Expression.Lambda<Action>(
                    Expression.Call(
                        Foom,
                        Expression.Constant(2),
                        Expression.Constant(null, typeof(string)))).Compile()();
            }
            catch (NullReferenceException ex)
            { Console.WriteLine("Caught NullReferenceException at " + ex.StackTrace); }

            // MethodBase.Invoke, exception gets wrapped
            try
            {
                Foom.Invoke(null, new object[] { 2, null });
            }
            catch (NullReferenceException)
            { Console.WriteLine("Won't catch NullReferenceException"); }
            catch (TargetInvocationException)
            { Console.WriteLine("Bad!"); }

            // DynamicInvoke, exception gets wrapped
            try
            {
                Delegate Food = (myFoo)Foo;
                Food.DynamicInvoke(2, null);
            }
            catch (NullReferenceException)
            { Console.WriteLine("Won't catch NullReferenceException"); }
            catch (TargetInvocationException)
            { Console.WriteLine("Bad!"); }
        }

        private static void Foo(int i, string s)
        {
            Console.WriteLine("i=" + i + ", s.Length = " + s.Length);
        }
    }
}

答案 1 :(得分:1)

实际上由@hvd回答:

((dynamic)Food).Invoke(2, null);

在一行代码中解决了我的问题。谢谢!