Как восстановить внутреннее исключение исключения 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(); 
}

Ответ 1

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

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 для этого в будущем. Предложение ExceptionDispatchInfo.

Ответ 2

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

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