13

オブジェクトを動的に呼び出し、MethodInfoその内部からスローされた例外を、通常どおり呼び出されたかのように外側に渡したいと考えています。

2つのオプションがあるようです。それらの概要を以下に示します。

オプション 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の両方の利点を得る方法はありますか?

編集:

私が望んでいたオプション(その場で特別な新しいC#キーワード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 ブロックに直接いなければならない」という要件から切り離す方法です。

4

4 に答える 4

10

これが私が思いついた解決策です。それは仕事を成し遂げます。もっと簡単できれいなものがあるかもしれないので、私はまだ他の答えに興味があります。

  • 機能が必要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(); }
    }
}

アップデート

.NET 4.5 には新しいSystem.Runtime.ExceptionServices.ExceptionDispatchInfoクラスがあります。これは、例外をキャプチャするために使用できます。

var capturedException = ExceptionDispatchInfo.Capture(e);

その後、これを使用して例外のスローを再開します。

capturedException.Throw();
于 2013-03-28T01:27:48.713 に答える
1

いいえ、両方の利点を得る方法はないと思います。ただし、元のスタック トレースを取得するためにe.InnerException単に を使用できるため、スローe.InnerException.StackTraceしても元のスタック トレースを取得できます。したがって、要するに、オプション 1 を使用する必要があります。

于 2013-03-27T20:06:52.973 に答える
0

同様の問題があり、これを思いつきました:

/// <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");
}
于 2014-05-28T14:48:01.663 に答える
0

最適なオプションはオプション 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()();
}

注意点として、いくつかのシグネチャの 1 つに適合する式を動的に構築するコードを記述できますが、メソッドのシグネチャを知る必要があります。

この手法を常に使用できるとは限りませんが、使用する場合は最適なオプションです。すべての意図と目的において、他のデリゲートを呼び出すのと同じです。また、複数の呼び出しを行う場合は、リフレクションよりも高速です (この場合、コンパイルは 1 回だけで、コンパイルされたデリゲートのハンドルを保持します)。

于 2014-04-29T07:45:22.787 に答える