Как правильно перекинуть исключение в C#?
У меня к вам вопрос, который связан с тем, что мой партнер делает что-то не так, как я.
Это лучше сделать это:
try
{
...
}
catch (Exception ex)
{
...
throw;
}
или это:
try
{
...
}
catch (Exception ex)
{
...
throw ex;
}
Они делают то же самое? Один лучше другого?
9 ответов
Вы должны всегда использовать следующий синтаксис, чтобы перебросить исключение, иначе вы топчете трассировку стека:
throw;
Если вы напечатаете трассировку, полученную в результате "throw ex", вы увидите, что она заканчивается на этом операторе, а не на реальном источнике исключения.
По сути, использование "throw ex" должно считаться уголовным преступлением.
Мои предпочтения - использовать
try
{
}
catch (Exception ex)
{
...
throw new Exception ("Put more context here", ex)
}
Это сохраняет исходную ошибку, но позволяет поместить больше контекста, такого как идентификатор объекта, строка подключения и тому подобное. Часто в моем инструменте создания отчетов об исключениях будет 5 связанных исключений, каждое из которых более детально.
Если вы генерируете исключениебез переменной (второй пример), StackTrace будет включать в себя оригинальный метод, вызвавший исключение.
В первом примере StackTrace будет изменен, чтобы отразить текущий метод.
Пример:
static string ReadAFile(string fileName) {
string result = string.Empty;
try {
result = File.ReadAllLines(fileName);
} catch(Exception ex) {
throw ex; // This will show ReadAFile in the StackTrace
throw; // This will show ReadAllLines in the StackTrace
}
Первый сохраняет исходную трассировку стека исключения, второй заменяет его текущим местоположением.
Поэтому первое - НАДЕЖНО, тем лучше.
Я знаю, что это старый вопрос, но я собираюсь ответить на него, потому что я должен не согласиться со всеми ответами здесь.
Теперь я согласен, что большую часть времени вы хотите сделать простой throw
чтобы сохранить как можно больше информации о том, что пошло не так, или вы хотите создать новое исключение, которое может содержать это как внутреннее исключение, или нет, в зависимости от того, насколько вероятно, что вы захотите узнать о внутренних событиях, которые вызвали Это.
Хотя есть исключение. Существует несколько случаев, когда метод вызывает другой метод, и условие, вызывающее исключение во внутреннем вызове, должно рассматриваться как то же исключение во внешнем вызове.
Одним из примеров является специализированная коллекция, реализованная с использованием другой коллекции. Допустим, это DistinctList<T>
это оборачивает List<T>
но отказывается от дубликатов.
Если кто-то звонил ICollection<T>.CopyTo
на вашем классе коллекции, это может быть просто прямой вызов CopyTo
во внутренней коллекции (если, скажем, вся пользовательская логика применяется только для добавления в коллекцию или ее настройки). Теперь условия, при которых вызов будет сгенерирован, точно такие же условия, при которых ваша коллекция должна сгенерировать, чтобы соответствовать документации ICollection<T>.CopyTo
,
Теперь вы можете просто не уловить исполнение и позволить ему пройти. Здесь, хотя пользователь получает исключение из List<T>
когда они звонили что-то на DistinctList<T>
, Не конец света, но вы можете скрыть эти детали реализации.
Или вы можете сделать свою собственную проверку:
public CopyTo(T[] array, int arrayIndex)
{
if(array == null)
throw new ArgumentNullException("array");
if(arrayIndex < 0)
throw new ArgumentOutOfRangeException("arrayIndex", "Array Index must be zero or greater.");
if(Count > array.Length + arrayIndex)
throw new ArgumentException("Not enough room in array to copy elements starting at index given.");
_innerList.CopyTo(array, arrayIndex);
}
Это не худший код, потому что это шаблон, и мы, вероятно, можем просто скопировать его из какой-то другой реализации CopyTo
где это не было простым проходом, и мы должны были реализовать это сами. Тем не менее, это излишне повторять те же самые проверки, которые будут сделаны в _innerList.CopyTo(array, arrayIndex)
Таким образом, единственное, что он добавил в наш код - это 6 строк, в которых может быть ошибка.
Мы могли бы проверить и обернуть:
public CopyTo(T[] array, int arrayIndex)
{
try
{
_innerList.CopyTo(array, arrayIndex);
}
catch(ArgumentNullException ane)
{
throw new ArgumentNullException("array", ane);
}
catch(ArgumentOutOfRangeException aore)
{
throw new ArgumentOutOfRangeException("Array Index must be zero or greater.", aore);
}
catch(ArgumentException ae)
{
throw new ArgumentException("Not enough room in array to copy elements starting at index given.", ae);
}
}
С точки зрения добавления нового кода, который потенциально может содержать ошибки, это еще хуже. И мы ничего не получаем от внутренних исключений. Если мы передадим нулевой массив этому методу и получим ArgumentNullException
мы не собираемся ничего изучать, исследуя внутреннее исключение и узнавая, что вызов _innerList.CopyTo
был передан нулевой массив и бросил ArgumentNullException
,
Здесь мы можем делать все, что хотим:
public CopyTo(T[] array, int arrayIndex)
{
try
{
_innerList.CopyTo(array, arrayIndex);
}
catch(ArgumentException ae)
{
throw ae;
}
}
Каждое исключение, которое мы ожидаем выдать, если пользователь вызывает его с неверными аргументами, будет корректно сгенерировано этим перебросом. Если в используемой здесь логике есть ошибка, она находится в одной из двух строк - либо мы ошиблись, решив, что это подход, или мы ошиблись в ArgumentException
как искал тип исключения. Это только две ошибки, которые может иметь блок catch.
Сейчас. Я все еще согласен, что большую часть времени вы либо хотите простой throw;
или вы хотите создать собственное исключение, чтобы более точно сопоставить проблему с точки зрения рассматриваемого метода. Есть случаи, подобные вышеупомянутым, где повторное бросание, как это, имеет больше смысла, и есть много других случаев. Например, если взять совсем другой пример, если программа чтения файлов ATOM реализована с FileStream
и XmlTextReader
получает файл об ошибке или недопустимый XML, тогда он, возможно, захочет выбросить точно такое же исключение, которое он получил от этих классов, но он должен посмотреть вызывающей стороне, что это AtomFileReader
это бросает FileNotFoundException
или же XmlException
Таким образом, они могут быть кандидатами на аналогичный повторный бросок.
Редактировать:
Мы также можем объединить два:
public CopyTo(T[] array, int arrayIndex)
{
try
{
_innerList.CopyTo(array, arrayIndex);
}
catch(ArgumentException ae)
{
throw ae;
}
catch(Exception ex)
{
//we weren't expecting this, there must be a bug in our code that put
//us into an invalid state, and subsequently let this exception happen.
LogException(ex);
throw;
}
}
Вы всегда должны использовать "бросить"; отбросить исключения в.NET,
Обратитесь к этому, http://weblogs.asp.net/bhouse/archive/2004/11/30/272297.aspx
В основном MSIL (CIL) имеет две инструкции - throw и rethrow и C# throw ex; компилируется в MSI "throw" и C# "throw;" - в MSIL "свергни"! В основном я вижу причину, по которой "throw ex" переопределяет трассировку стека.
Первое лучше. Если вы попытаетесь отладить второй и посмотреть на стек вызовов, вы не увидите, откуда появилось исходное исключение. Существуют приемы, позволяющие сохранить целостность стека вызовов (попробуйте выполнить поиск, на который уже был дан ответ), если вам действительно нужно перебросить.
Я обнаружил, что если исключение выдается в том же методе, что и оно, оно перехватывается, трассировка стека не сохраняется, чего бы это ни стоило.
void testExceptionHandling()
{
try
{
throw new ArithmeticException("illegal expression");
}
catch (Exception ex)
{
throw;
}
finally
{
System.Diagnostics.Debug.WriteLine("finally called.");
}
}
Это зависит. В отладочной сборке я хочу увидеть исходную трассировку стека с минимальными усилиями, насколько это возможно. В этом случае, "бросить"; отвечает всем требованиям. Однако в сборке релиза (а) я хочу записать ошибку с включенной исходной трассировкой стека, и, как только это будет сделано, (б) изменить способ обработки ошибок, чтобы сделать его более понятным для пользователя. Здесь "Бросок исключения" имеет смысл. Это правда, что перебрасывание ошибки отбрасывает исходную трассировку стека, но не разработчик ничего не получает от просмотра информации трассировки стека, поэтому можно перебросить ошибку.
void TrySuspectMethod()
{
try
{
SuspectMethod();
}
#if DEBUG
catch
{
//Don't log error, let developer see
//original stack trace easily
throw;
#else
catch (Exception ex)
{
//Log error for developers and then
//throw a error with a user-oriented message
throw new Exception(String.Format
("Dear user, sorry but: {0}", ex.Message));
#endif
}
}
То, как вопрос сформулирован, противопоставляя слова "Брось" и "Брось экс"; делает это немного красной селедки. Настоящий выбор между "Броском"; и "Брось исключение", где "Брось бывшего"; вряд ли частный случай "Исключения Броска".