Иногда возникает ситуация, когда необходимо заново выбросить исключение ex, которое уже было выброшено, перехвачено и записано как внутренне (InnerException) в другом исключении. Проблема заключается в том, что если использовать вызов throw ex, то исходный стек вызова будет заменен на текущий. То есть потеряется важная для отладки информации. Но этого можно избежать. |
Поддерживаемые платформы:
- .NET 4.5 и выше
- .NET Core 1.x и выше
ExceptionDispatchInfo
Методы и использование
Начиная с .NET 4.5 для решения описанной проблемы существует класс ExceptionDispatchInfo. Он позволяет повторно выбросить исключение, сохраненное в переменной, без потери исходного стека вызовов.
Публичный конструктор у данного класса отсутствует. Поэтому для создания его экземпляра необходимо передать исходное исключение в статический метод
public static ExceptionDispatchInfo ExceptionDispatchInfo.Capture (Exception source);
Полученный экземпляр может повторно выбрасывать исключение при помощи метода
public void Throw ();
При необходимости, исходное исключение можно прочитать в свойстве:
public Exception SourceException { get; }
В .NET Core 2.x и выше существует еще один статический метод, который сразу выполнить повторный выброс исключения:
public static ExceptionDispatchInfo.Throw(Exception source);
По факту он просто вызывает описанные выше методы: ExceptionDispatchInfo.Capture(source).Throw();
Принцип работы
ExceptionDispatchInfo при создании экземпляра сохраняет переданное исключение и его состояние, включая данные стека вызовов.
При вызове метода Throw() сначала в текущем потоке восстанавливается состояние, после чего следует вызов throw для исходного исключения.
Пример создаваемого стека вызовов
Посмотрим на стека вызовов, полученного при помощи ExceptionDispatchInfo:
at ExceptionRethrow.Test.Save(String title) in Program.cs:line 29
at ExceptionRethrow.Test.ProcessRequests() in Program.cs:line 35
--- End of stack trace from previous location where exception was thrown ---
at ExceptionRethrow.Test.Dispatcher(Boolean useDispatchInfo) in Program.cs:line 49
at ExceptionRethrow.Program.Main(String[] args) in Program.cs:line 13
Текст
--- End of stack trace from previous location where exception was thrown ---
используется как разделитель исходной (вверху) и текущей (внизу) цепочек вызовов. Пример кода, при помощи которого был получено данное сообщение, приведен ниже.
Пример кода
В коде для наглядности выделены стоки, на которые ссылается текст рассмотренного примера стека вызовов.
namespace ExceptionRethrow
{
using System;
using System.Runtime.ExceptionServices;
internal class Program
{
static void Main(string[] args)
{
var test = new Test();
try {
test.Dispatch(useDispatchInfo: true);
}
catch (Exception ex) {
Console.WriteLine(ex.GetType().FullName);
Console.WriteLine(ex.StackTrace);
}
Console.ReadKey(intercept: true);
}
}
public class Test
{
public void Save(string title)
{
if (title == null)
throw new ArgumentNullException(nameof(title));
}
public void ProcessRequests()
{
try {
this.Save(null);
}
catch (Exception ex) {
throw new Exception("Wrapping source exception", ex);
}
}
public void Dispatch(bool useDispatchInfo)
{
try {
this.ProcessRequests();
}
catch (Exception ex) when (ex.InnerException != null) {
if (useDispatchInfo)
ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
throw ex.InnerException;
}
}
}
}