Каков правильный способ повторного вызова исключения в 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.

теперь вы можете просто не поймать execption вообще, и пусть он пройдет. Здесь, хотя пользователь получает исключение из 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; или вы хотите создать свое собственное исключение непосредственно соответствовать проблему с точки зрения метода. Есть случаи, подобные приведенным выше, где повторное метание, как это имеет больше смысла, и есть много других случаев. Е. Г. брать совсем другой пример, Если атом файл Reader реализована с FileStream и XmlTextReader получает ошибку файла или недопустимый XML, затем возможно, захочет выбросить точно такое же исключение, которое он получил от этих классов, но он должен смотреть на вызывающего абонента, что это AtomFileReader это бросание FileNotFoundException или XmlException, поэтому они могут быть кандидатами для аналогичного повторного броска.

Edit:

мы также можем комбинировать два:

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;
  }
}

вы всегда должны использовать " throw;", чтобы перестроить исключения в .NET,

обратитесь к этому,http://weblogs.asp.net/bhouse/archive/2004/11/30/272297.aspx

в основном MSIL (CIL) имеет две инструкции - "throw" и "rethrow" и C#"throw ex;" компилируется в MSIL "throw" и C#"throw"; "- в MSIL"rethrow"! В основном я вижу причину, по которой "throw ex" переопределяет трассировку стека.


первая лучше. Если вы попытаетесь отладить второй и посмотрите на стек вызовов, вы не увидите, откуда появилось исходное исключение. Есть трюки, чтобы сохранить стек вызовов неповрежденным (попробуйте поиск, на него ответили раньше), если вам действительно нужно переосмыслить.


Я обнаружил, что если исключение выбрасывается тем же методом, что и поймано, трассировка стека не сохраняется, чего бы это ни стоило.

void testExceptionHandling()
{
    try
    {
        throw new ArithmeticException("illegal expression");
    }
    catch (Exception ex)
    {
        throw;
    }
    finally
    {
        System.Diagnostics.Debug.WriteLine("finally called.");
    }
}

Это зависит. В отладочной сборке я хочу видеть исходную трассировку стека с минимальными усилиями. В этом случае "бросок" подходит. Однако в сборке выпуска (a) я хочу зарегистрировать ошибку с исходной трассировкой стека, и как только это будет сделано, (b) переделать обработку ошибок, чтобы иметь больше смысла для пользователя. Здесь" исключение броска " имеет смысл. Это правда, что переосмысление ошибки отбрасывает исходную трассировку стека, но не разработчик ничего не получает от просмотра стека трассировка информации, так что это нормально, чтобы переосмыслить ошибку.

        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
            }
        }

способ формулировки вопроса, питтинг "бросок:" против "бросок ex;" делает его немного отвлекающим. Реальный выбор-между " Throw;" и "throw Exception", где "Throw ex;" является маловероятным частным случаем " Throw Exception."