1

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

考虑以下程序,我们将在 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);
        }
    }
}

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

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

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

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

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

我的问题是:有谁知道为什么我们在直接从调试器运行时与在抛出异常后附加到程序时会得到如此不同的行为?

4

2 回答 2

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);
        }
    }
}
于 2012-03-17T14:18:07.187 回答
1

实际上由@hvd回答:

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

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

于 2012-03-17T13:25:18.490 回答