Как отловить оригинальное (внутреннее) исключение в C #? - PullRequest
25 голосов
/ 09 января 2012

Я вызываю функцию, которая выдает пользовательское исключение:

GetLockOwnerInfo(...)

Эта функция, в свою очередь, вызывает функцию, которая выдает исключение:

GetLockOwnerInfo(...)
   ExecuteReader(...)

Эта функция, в свою очередь,вызывает функцию, которая выдает исключение:

GetLockOwnerInfo(...)
   ExecuteReader(...)
      ExecuteReader(...)

И так далее:

GetLockOwnerInfo(...)
   ExecuteReader(...)
      ExecuteReader(...)
         ExecuteReaderClient(...)
             Fill(...)

Одна из этих функций выдает SqlException, хотя этот код не имеет представления о том, что SqlException is.

Оберните более высокие уровни, которые SqlException, в другую BusinessRuleException, чтобы включить некоторые специальные свойства и дополнительные детали, в то же время добавив «оригинальное» исключение как InnerException:

catch (DbException ex)
{
    BusinessRuleExcpetion e = new BusinessRuleException(ex)
    ...
    throw e;
}

Более высокие уровни оборачивают это BusinessRuleException в другое LockerException, чтобы включить некоторые специальные свойства и дополнительные детали, включая «оригинальное» исключение как InnerException:

catch (BusinessRuleException ex)
{
    LockerException e = new LockerException(ex)
    ...
    throw e;
}

ПроблемаТеперь я хочу поймать origianl SqlException, чтобы проверить конкретный код ошибки.

Но нет способа "поймать внутреннее исключение":

try
{
   DoSomething();
}
catch (SqlException e)
{
   if (e.Number = 247) 
   {
      return "Someone";
   }
   else
      throw;
}

Я думало ловле SqlException прямо когда этобросить и скопировать различные значения в повторно брошенное исключение - но этот код не зависит от Sql.У него SqlException, но он не зависит от SqlException.

Я думал о перехвате всех исключений:

try
{
   DoSomething(...);
}
catch (Exception e)
{
   SqlException ex = HuntAroundForAnSqlException(e);
   if (ex != null)
   {
      if (e.Number = 247) 
      {
          return "Someone";
      }
      else
         throw;
   }
   else
      throw;
}

Но это ужасный код.

Учитывая, что .NET не позволяет вам изменять Message Exception чтобы включить дополнительную информацию, каков механизм обнаружения исходных исключений?

Ответы [ 5 ]

15 голосов
/ 09 января 2012

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

Вы можете проверить один.

Я предлагаю вам поймать ваше высокоуровневое исключение (я думаю, это было LockerException) и проверить свойство InnerException этого исключения.Проверьте тип, и если это не SqlException, проверьте InnerException этого исключения.Обходите каждый, пока не найдете тип SqlException, а затем получите необходимые данные.

Тем не менее, я согласен с dasblinkenlight, что вы должны рассмотреть - если это возможно - серьезный рефакторинг вашей структуры исключений.

14 голосов
/ 28 августа 2015

Вам потребуется c # 6 / visual studio 2015, чтобы сделать это с помощью предиката:

catch (ArgumentException e) when (e.ParamName == “…”)
{
}

Официальная документация C / Try / Catch

6 голосов
/ 09 января 2012

Проверка кода ошибки обернутого исключения не является хорошей практикой, потому что это вредит инкапсуляции довольно сильно.Представьте себе в какой-то момент переписывающую логику для чтения из источника, отличного от SQL, скажем, из веб-службы.При таком же условии он выдаст что-то отличное от SQLException, и ваш внешний код не сможет его обнаружить.

Вы должны добавить код в блок, перехватывающий SQLException, чтобы проверить правильность e.Number = 247тогда и там, и бросить BusinessRuleException с некоторым свойством, которое отличает его от BusinessRuleException, брошенного в ответ на не- SQLException и SQLException с e.Number != 247 каким-либо значимым образом.Например, если магическое число 247 означает, что вы столкнулись с дубликатом (чистое предположение с моей стороны в данный момент), вы можете сделать что-то вроде этого:

catch (SQLException e) {
    var toThrow = new BusinessRuleException(e);
    if (e.Number == 247) {
        toThrow.DuplicateDetected = true;
    }
    throw toThrow;
}

Когда вы поймаете BusinessRuleException позже вы можете проверить его свойство DuplicateDetected и действовать соответствующим образом.

РЕДАКТИРОВАТЬ 1 (в ответ на комментарий о том, что код чтения БД не может проверить SQLException)

Вы также можете изменить BusinessRuleException для проверки на SQLException в его конструкторе, например:

public BusinessRuleException(Exception inner)
:   base(inner) {
    SetDuplicateDetectedFlag(inner);
}

public BusinessRuleException(string message, Exception inner)
:   base(message, inner) {
    SetDuplicateDetectedFlag(inner);
}

private void SetDuplicateDetectedFlag(Exception inner) {
    var innerSql = inner as SqlException;
    DuplicateDetected = innerSql != null && innerSql.Number == 247;
}

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

3 голосов
/ 09 января 2012

Наличие внешнего уровня приложения, заботящегося о деталях упакованного исключения, является запахом кода;чем глубже упаковка, тем сильнее запах.Класс, который у вас теперь есть SqlException в dbException, предположительно предназначен для представления SqlClient в качестве универсального интерфейса базы данных.Таким образом, этот класс должен включать средства различения различных исключительных условий.Например, он может определить исключение dbTimeoutWaitingForLockException и принять решение выбросить его, когда он перехватывает исключение SqlException, и на основании кода ошибки определяет, что истекло время ожидания блокировки.В vb.net может быть чище иметь тип dbException, который предоставляет перечисление ErrorCause, поэтому можно сказать Catch Ex as dbException When ex.Cause = dbErrorCauses.LockTimeout, но, к сожалению, фильтры исключений не могут быть использованы в C #.

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

1 голос
/ 10 января 2012

Хороший вопрос и хорошие ответы!

Я просто хочу дополнить уже приведенные ответы некоторыми соображениями:

С одной стороны, я согласен с dasblinkenlight и другими пользователями.Если вы перехватываете одно исключение, чтобы перебросить исключение другого типа с исходным исключением, установленным как внутреннее исключение, то вы должны делать это только по той причине, что поддерживает контракт метода .(Доступ к SQL-серверу - это деталь реализации, о которой вызывающая сторона не / не должна / не может знать, поэтому не может ожидать, что будет выдано SqlException (или DbException)).

Применение этого метода, однако, имеет некоторые последствия, о которых следует знать:

  • Вы скрываете основную причину ошибки. В своем примере вы сообщаете вызывающей сторонечто бизнес-правило было недопустимым (?), нарушенным (?) и т. д., когда на самом деле возникла проблема с доступом к БД (что было бы сразу понятно, если бы DbException было разрешено дополнительно увеличивать стек вызовов).
  • Вы скрываете место, где изначально произошла ошибка. Свойство StackTrace перехваченного исключения будет указывать на блок перехвата вдали от места, где изначально возникла ошибка.Это может сделать отладку заведомо сложной, если вы не позаботитесь о том, чтобы записывать в стек трассировки всех внутренних исключений.(Это особенно актуально после того, как программное обеспечение было развернуто в рабочей среде, и у вас нет средств для подключения отладчика ...)

Учитывая, что .NET не позволяет вам изменять СообщениеИсключение для включения дополнительной информации, каков предполагаемый механизм для перехвата оригинальных исключений?

Это правда, что .NET не позволяет вам изменять Сообщение об исключении.Однако он предоставляет другой механизм для предоставления дополнительной информации об Исключении через словарь Exception.Data.Таким образом, если все, что вы хотите сделать, это добавить дополнительные данные в исключение, то нет никаких причин переносить исходное исключение и выдавать новое.Вместо этого просто сделайте:

public void DoStuff(String filename)
{
    try {
       // Some file I/O here...
    }
    catch (IOException ex) {

      // Add filename to the IOException
      ex.Data.Add("Filename", filename);

      // Send the exception along its way
      throw;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...