Сохранение исключений из динамически вызываемых методов

Я хочу динамически вызывать объект MethodInfo и иметь любые исключения, которые могут быть выбрасываться изнутри, проходить наружу, как если бы они вызывались нормально.

У меня есть два варианта. Они описаны ниже.

Вариант 1 поддерживает тип исключения, созданного MyStaticFunction, но StackTrace разрушен из-за throw.

Вариант 2 поддерживает StackTrace исключения, но тип исключения всегда TargetInvocationException. Я могу вытащить InnerException и его тип, но это означает, что я не могу написать это, например:

try { DoDynamicCall(); }
catch (MySpecialException e) { /* special handling */ }

Вариант 1:

void DoDynamicCall()
{
    MethodInfo method = /*referencing MyClass method void MyStaticFunction(int x)*/;
    try
    {
        method.Invoke(null, new object[] { 5 });
    }
    catch (TargetInvocationException e)
    {
        throw e.InnerException;
    }
}

Вариант 2:

void DoDynamicCall()
{
    MethodInfo method = /*referencing MyClass method void MyStaticFunction(int x)*/;
    method.Invoke(null, new object[] { 5 });
}

Что мне действительно нужно для звонящих DoDynamicCall для получения исключений, как если бы они вызывали это:

void DoDynamicCall()
{
    MyClass.MyStaticFunction(5);
}

Есть ли способ получить преимущества как Вариант 1, так и Вариант 2?

Изменить:

Опция Я бы хотел, чтобы у меня был (на месте появилось специальное новое ключевое слово С# rethrow):

void DoDynamicCall()
{
    MethodInfo method = /*referencing MyClass method void MyStaticFunction(int x)*/;
    try
    {
        method.Invoke(null, new object[] { 5 });
    }
    catch (TargetInvocationException e)
    {
        //Magic "rethrow" keyword passes this exception
        //onward unchanged, rather than "throw" which
        //modifies the StackTrace, among other things
        rethrow e.InnerException;
    }
}

Это также устранит необходимость в этом чудовище, потому что вместо этого вы можете использовать rethrow e;:

try { ... }
catch (Exception e)
{
    if (...)
        throw;
}

В общем, это будет способ отделить throw; от требования "Я должен быть непосредственно в блоке catch".

Ответ 1

Вот решение, которое я придумал. Он выполняет свою работу. Меня все еще интересуют другие ответы, поскольку может быть что-то проще или чище.

  • Если вам нужна функциональность throw;, но исключение, которое вы хотите передать, не является исключением текущего блока catch, используйте throw Functional.Rethrow(e);
  • Замените try...catch... на Functional.TryCatch
  • Замените try...catch...finally... на Functional.TryCatchFinally

Здесь код:

//Need a dummy type that is throwable and can hold an Exception
public sealed class RethrowException : Exception
{
    public RethrowException(Exception inner) : base(null, inner) { }
}

public static Functional
{    
    public static Exception Rethrow(Exception e)
    {
        return new RethrowException(e);
    }

    public static void TryCatch(Action _try, Action<Exception> _catch)
    {
        try { _try(); }
        catch (RethrowException e) { _catch(e.InnerException); }
        catch (Exception e) { _catch(e); }
    }

    public static T TryCatch<T>(Func<T> _try, Func<Exception, T> _catch)
    {
        try { return _try(); }
        catch (RethrowException e) { return _catch(e.InnerException); }
        catch (Exception e) { return _catch(e); }
    }

    public static void TryCatchFinally(
        Action _try, Action<Exception> _catch, Action _finally)
    {
        try { _try(); }
        catch (RethrowException e) { _catch(e.InnerException); }
        catch (Exception e) { _catch(e); }
        finally { _finally(); }
    }

    public static T TryCatchFinally<T>(
        Func<T> _try, Func<Exception, T> _catch, Action _finally)
    {
        try { return _try(); }
        catch (RethrowException e) { return _catch(e.InnerException); }
        catch (Exception e) { return _catch(e); }
        finally { _finally(); }
    }
}

Update

В .NET 4.5 есть новый класс System.Runtime.ExceptionServices.ExceptionDispatchInfo. Это можно использовать для захвата исключения:

var capturedException = ExceptionDispatchInfo.Capture(e);

И затем это используется для возобновления исключения исключения:

capturedException.Throw();

Ответ 2

Нет, я не верю, что есть способ получить преимущества обоих. Однако бросание e.InnerException по-прежнему позволит вам получить исходную стеклу, потому что вы можете просто использовать e.InnerException.StackTrace для получения исходной трассировки стека. Итак, короче говоря, вы должны использовать опцию 1.

Ответ 3

Самый лучший вариант Вариант 3: вообще не использовать отражение, но вместо этого используйте Expression<T>.Compile().

Вместо этого:

static void CallMethodWithReflection(MethodInfo method)
{
    try
    {
        method.Invoke(null, new object[0]);
    }
    catch (TargetInvocationException exp)
    {
        throw exp.InnerException;
    }
}

Постарайтесь сделать это:

private static void CallMethodWithExpressionCompile(MethodInfo method)
{
    Expression.Lambda<Action>(Expression.Call(method)).Compile()();
}

Предостережение заключается в том, что вам нужно знать подпись метода, хотя вы можете написать код, который динамически создает выражение для соответствия одной из нескольких подписей.

Вы не всегда можете использовать эту технику, но когда вы это делаете, это лучший вариант. Для всех целей и целей это похоже на вызов любого другого делегата. Это также быстрее, чем отражение, если вы выполняете несколько вызовов (в этом случае компилируйте только один раз и сохраняйте дескриптор компилируемого делегата).

Ответ 4

У меня была аналогичная проблема и придумал следующее:

/// <summary>
/// Attempts to throw the inner exception of the TargetInvocationException
/// </summary>
/// <param name="ex"></param>
[DebuggerHidden]
private static void ThrowInnerException(TargetInvocationException ex)
{
    if (ex.InnerException == null) { throw new NullReferenceException("TargetInvocationException did not contain an InnerException", ex); }

    Exception exception = null;
    try
    {
        //Assume typed Exception has "new (String message, Exception innerException)" signature
        exception = (Exception) Activator.CreateInstance(ex.InnerException.GetType(), ex.InnerException.Message, ex.InnerException);
    }
    catch
    {
        //Constructor doesn't have the right constructor, eat the error and throw the inner exception below
    }

    if (exception == null ||
        exception.InnerException == null ||
        ex.InnerException.Message != exception.Message)
    {
        // Wasn't able to correctly create the new Exception.  Fall back to just throwing the inner exception
        throw ex.InnerException;
    }
    throw exception;
}

Пример его использования ниже:

try
{
    return typeof(MyType).GetMethod(methodName, BindingFlags.Public | BindingFlags.Static)
                                    .MakeGenericMethod(new[] { myType) })
                                    .Invoke(null, parameters);
}
catch (TargetInvocationException ex)
{
    ThrowInnerException(ex);
    throw new Exception("Throw InnerException didn't throw exception");
}