Головоломка с использованием размотанных стеков при динамическом вызове - PullRequest
1 голос
/ 15 марта 2012

Это новая попытка представить версию вопроса, заданного менее успешно сегодня утром.

Рассмотрим следующую программу, которую мы запустим один раз в 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.

Но когда это исключениепроисходит при непосредственном запуске исполняемого файла, из CLR появляется небольшое всплывающее окно, указывающее, что программа вызвала необработанное исключение.Нажмите отладку и выберите отладчик VS.В этом случае стек разматывается до точки самого последнего .DynamicInvoke, и при подключении с помощью отладчика контекст стека, существовавший во время исключения, был частично потерян.

Он существует в ограниченной форме в пределах части «внутреннего исключения» события исключения.Вы нажимаете, чтобы развернуть связанную информацию и найти номер строки, где возникла проблема.Но очевидно, что локальные переменные и другой контекст исчезнут.

Если кто-то попытается сделать то же самое, но без .DynamicInvoke (например, вызвать Foo (1, null) в строке 1 Main), все еще дваждыщелкнув по файлу .exe, мы ДЕЙСТВИТЕЛЬНО получаем правильный номер строки, когда присоединяется отладчик.Точно так же, если приложение запускается нажатием на .exe, но отладчик подключается до того, как генерируется исключение.

Кто-нибудь знает, как приложение, использующее динамическое отражение / вызов, может избежать этой проблемы?В моем предполагаемом случае использования в системе, имя которой я не буду здесь упоминать, я не могу предсказать сигнатуру типа объекта, который будет использоваться в .DynamicInvoke, или даже число аргументов, которые будут использоваться, следовательно,статическая типизация или даже генерики не являются выходом из этого.

Мой вопрос таков: кто-нибудь знает, почему мы получаем такое различное поведение при запуске непосредственно из отладчика по сравнению с подключением к программе после создания исключения?

Ответы [ 2 ]

2 голосов
/ 17 марта 2012

Согласно комментариям, видите ли вы 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 голос
/ 17 марта 2012

На самом деле ответил @hvd:

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

решает мою проблему в одной строке кода. Спасибо!

...