Как перебросить внутреннее исключение TargetInvocationException без потери трассировки стека

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

Я использовал этот метод для повторного выброса, но он очищает трассировку стека:

 try
 {
      return myDelegate.DynamicInvoke(args);
 }
 catch(TargetInvocationException ex)
 {
     Func<TargetInvocationException, Exception> getInner = null;
     getInner =
        delegate(TargetInvocationException e)
        {
        if (e.InnerException is TargetInvocationException)
            return getInner((TargetInvocationException) e.InnerException);

         return e.InnerException;
        };

     Exception inner = getInner(ex);
     inner.PreserveStackTrace();
     throw inner;
 }

PreserveStackTrace Метод - это метод расширения, который я исправил благодаря другому посту (я не знаю, что он на самом деле делает). Тем не менее, это также не сохраняет след:

public static void PreserveStackTrace(this Exception e)
{
    var ctx = new StreamingContext(StreamingContextStates.CrossAppDomain);
    var mgr = new ObjectManager(null, ctx);
    var si = new SerializationInfo(e.GetType(), new FormatterConverter());

    e.GetObjectData(si, ctx);
    mgr.RegisterObject(e, 1, si);
    mgr.DoFixups(); 
}

3 ответа

Решение

Если вы просто хотите повторно выдать внутреннее исключение, сохранив его трассировку стека, вы можете сделать это с помощью метода, подобного этому:

public static void Rethrow(this Exception ex)
{
  typeof(Exception).GetMethod("PrepForRemoting",
      BindingFlags.NonPublic | BindingFlags.Instance)
      .Invoke(ex, new object[0]);
  throw ex;
}

Эта техника используется Rx (и выставляется ими как метод расширения Exception.PrepareForRethrow) и также используется Async CTP своей системой автоматического развертывания (без публичного доступа к API).

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

Обновление: официальный API был добавлен в.NET 4.5: ExceptionDispatchInfo,

Необходимо помнить, почему.NET заключает исключение в TargetInvocationException, а не просто пропускает исходное исключение. Для этого есть действительно веская причина, неясно, откуда взялась настоящая причина исключения. Было ли это потому, что вызов DynamicInvoke() не выполняется? Не исключено, что компилятор ничего не сможет сделать, чтобы убедиться, что переданы правильные аргументы. Или вызванный целевой метод выбрасывает все сам?

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

IIRC невозможно полностью сохранить исключение, однако трассировка стека может быть сохранена с некоторым отражением. Вот сообщение в блоге, описывающее, как это сделать: http://iridescence.no/post/Preserving-Stack-Traces-When-Re-Throwing-Inner-Exceptions.aspx

Другие вопросы по тегам