Разница между перебрасыванием параметра без улова и бездействием?

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

//in assembly A
public class TypeA {
   // Constructor omitted
   public void MethodA
   {
     try {
       //do something
     }
     catch {
        throw;
     }
   }
}
//in assembly B
public class TypeB {
   public void MethodB
   {
     try {
       TypeA a = new TypeA();
       a.MethodA();
     }
     catch (Exception e)
       //Handle exception
     }
   }
}

В этом случае try-catch в MethodA просто повышает исключение, но на самом деле не обрабатывает его. Есть ли какое-либо преимущество в использовании try-catch вообще в MethodA? Другими словами, есть ли разница между этим типом блока try-catch и его отсутствием вообще?

11 ответов

Решение

В вашем примере это не дает никаких преимуществ. Но есть случаи, когда желательно просто всплыть конкретное исключение.

    public void Foo()
    {
        try
        {
            // Some service adapter code

            // A call to the service
        }
        catch (ServiceBoundaryException)
        {
            throw;
        }
        catch (Exception ex)
        {
            throw new AdapterBoundaryException("some message", ex);
        }
    }

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

Да, есть разница. Когда вы перехватываете исключение, .NET предполагает, что вы собираетесь каким-то образом его обработать, стек разматывается до функции, выполняющей перехват.

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

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

С кодом, который вы написали для MethodA, нет никакой разницы. Все, что он будет делать, это съесть процессорные циклы. Однако при написании кода таким способом может быть преимущество, если есть ресурс, который вы должны освободить. Например

Resource r = GetSomeResource();
try {
  // Do Something
} catch { 
  FreeSomeResource();
  throw;
}
FreeSomeResource();

Однако в этом нет никакого смысла. Было бы намного лучше просто использовать блок finally.

Взятый как есть, первый вариант может показаться плохой (или это может быть "бесполезной") идеей. Тем не менее, это редко делается таким образом. Исключения повторно генерируются изнутри блока Catch обычно при двух условиях:

а. Вы хотите проверить сгенерированное для данных исключение и условно скопировать его в стек.

try 
{
  //do something
}
catch (Exception ex)
{
  //Check ex for certain conditions.
  if (ex.Message = "Something bad")
    throw ex;
  else
    //Handle the exception here itself.
}

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

try 
{
  //do something
}
catch (StackruException ex)
{
    //Bubble up the exception to calling code 
    //by wrapping it up in a custom exception.
    throw new MyEuphemisticException(ex, "Something not-so-good just happened!");
}

Просто отбрасывать смысла нет - это так же, как если бы ты ничего не делал.

Однако это становится полезным, когда вы действительно что-то делаете - чаще всего регистрируется исключение. Вы также можете изменить состояние своего класса, что угодно.

Поскольку классы находятся в 2 разных сборках, вы можете захотеть просто перехватить исключение для его регистрации, а затем выбросить его обратно вызывающей стороне, чтобы он мог обрабатывать его так, как считает нужным. Бросок вместо броска ex сохранит контекстную информацию о том, откуда возникло исключение. Это может оказаться полезным, когда ваша сборка представляет собой API/framework, в котором вы никогда не должны проглатывать исключения, если это не имеет смысла, но полезно, тем не менее, при устранении неполадок, если она регистрируется, например, в EventLog.

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

public class XmlException: Exception{
   ....
} 

public class XmlParser{
   public void Parse()
   {
      try{
          ....
      }
      catch(IOException ex)
      {
         throw new XmlException("IO Error while Parsing", ex );
      }
   }
}

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

Никогда не используйте вариант A. Как говорит Антон, он поглощает трассировку стека. Пример JaredPar также съедает трассировку стека. Лучшее решение было бы:

SomeType* pValue = GetValue();
try {
  // Do Something
} finally {
  delete pValue;
}

Если у вас есть что-то в C#, которое нужно выпустить, например, FileStream, у вас есть два следующих варианта:

FileStream stream;
try
{
  stream = new FileStream("C:\\afile.txt");
  // do something with the stream
}
finally
{
  // Will always close the stream, even if there are an exception
  stream.Close(); 
}

Или более чисто:

using (FileStream stream = new FileStream("c:\\afile.txt"))
{
  // do something with the stream
}

Оператор using удалит (и закроет) поток по завершении или при закрытии исключения.

Сборка A - try catch - block для меня не имеет никакого смысла. Я считаю, что если вы не собираетесь обрабатывать исключение, то почему вы ловите эти исключения... В любом случае оно будет выброшено на следующий уровень.

Но если вы создаете API среднего уровня или что-то в этом роде и обрабатываете исключение (и, следовательно, уничтожаете исключение) на этом уровне, не имеет смысла, тогда вы можете создать свой собственный уровень ApplicationException. Но, конечно, отбрасывать одно и то же исключение не имеет смысла.

Когда вы ловите и бросаете, это позволяет вам установить точку останова на throw линия.

Вы можете использовать блок try{} catch(ex){} в методе A, только если вы можете перехватить конкретное исключение, которое может быть обработано в MethodA() (например, для ведения журнала).

Другой вариант - связать исключение с помощью свойства InnerException и передать его вызывающей стороне. Эта идея не уничтожит трассировку стека.

Другие вопросы по тегам