Каков правильный способ повторного выброса исключения в С#?

У меня есть вопрос для вас, который связан с тем, что мой партнер делает что-то по-другому, чем я.

Лучше ли это сделать:

try
{
    ...
}
catch (Exception ex)
{
    ...
    throw;
}

или это:

try
{
    ...
}
catch (Exception ex)
{
    ...
    throw ex;
}

Они делают то же самое? Лучше другого?

Ответ 1

Вы должны всегда использовать следующий синтаксис для повторного исключения исключения, иначе вы будете топать трассировкой стека:

throw;

Если вы печатаете трассировку, полученную в результате "throw ex", вы увидите, что она заканчивается этим оператором, а не реальным источником исключения.

В принципе, это должно считаться уголовным преступлением, чтобы использовать "throw ex".

Ответ 2

Мои предпочтения - использовать

try 
{
}
catch (Exception ex)
{
     ...
     throw new Exception ("Put more context here", ex)
}

Это сохраняет исходную ошибку, но позволяет помещать больше контекста, например идентификатора объекта, строки подключения и тому подобного. Часто мой отчет об исключительной отчетности будет иметь 5 связанных исключений для отчета, каждый из которых сообщает более подробно.

Ответ 3

Если вы выбрали исключение с переменной out (второй пример), 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
    }

Ответ 4

Первый сохраняет исходную трассировку стека исключения, второй заменяет ее текущим местоположением.

Следовательно, первое значение BY FAR лучше.

Ответ 5

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

Теперь я соглашусь, что большую часть времени вы либо хотите сделать простой 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, поэтому они могут быть кандидатами для повторного металирования.

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

Ответ 6

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

Обратите внимание, http://weblogs.asp.net/bhouse/archive/2004/11/30/272297.aspx

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

Ответ 7

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

Ответ 8

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

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

Ответ 9

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

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

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