为什么在C#中捕获并重新抛出异常?

时间:2009-05-19 07:56:55

标签: c# exception-handling try-catch

我正在查看序列化DTO上的文章 C# - Data Transfer Object

这篇文章包含这段代码:

public static string SerializeDTO(DTO dto) {
    try {
        XmlSerializer xmlSer = new XmlSerializer(dto.GetType());
        StringWriter sWriter = new StringWriter();
        xmlSer.Serialize(sWriter, dto);
        return sWriter.ToString();
    }
    catch(Exception ex) {
        throw ex;
    }
}

本文的其余部分看起来很合理(对于菜鸟),但是try-catch-throw会抛出一个WtfException ... 这不完全等同于根本不处理异常吗?

埃尔戈:

public static string SerializeDTO(DTO dto) {
    XmlSerializer xmlSer = new XmlSerializer(dto.GetType());
    StringWriter sWriter = new StringWriter();
    xmlSer.Serialize(sWriter, dto);
    return sWriter.ToString();
}

或者我错过了C#中错误处理的基本内容?它与Java几乎相同(减去已检查的异常),不是吗? ......也就是说,他们都改进了C ++。

Stack Overflow问题 The difference between re-throwing parameter-less catch and not doing anything? 似乎支持我的观点,即try-catch-throw是一个无操作。


修改

总结一下今后发现这个帖子的人......

不要

try {
    // Do stuff that might throw an exception
}
catch (Exception e) {
    throw e; // This destroys the strack trace information!
}

堆栈跟踪信息对于确定问题的根本原因至关重要!

DO

try {
    // Do stuff that might throw an exception
}
catch (SqlException e) {
    // Log it
    if (e.ErrorCode != NO_ROW_ERROR) { // filter out NoDataFound.
        // Do special cleanup, like maybe closing the "dirty" database connection.
        throw; // This preserves the stack trace
    }
}
catch (IOException e) {
    // Log it
    throw;
}
catch (Exception e) {
    // Log it
    throw new DAOException("Excrement occurred", e); // wrapped & chained exceptions (just like java).
}
finally {
    // Normal clean goes here (like closing open files).
}

在不太具体的异常之前捕获更具体的异常(就像Java一样)。


参考文献:

17 个答案:

答案 0 :(得分:391)

首先;文章中代码的表现方式是邪恶的。 throw ex将异常中的调用堆栈重置为此throw语句的位置;丢失有关实际创建例外的信息。

其次,如果你只是抓住并重新抛出这样的东西,我认为没有附加价值,上面的代码示例同样好(或者,考虑到throw ex位,甚至更好)没有尝试 - 捉。

但是,在某些情况下,您可能希望捕获并重新抛出异常。记录可能是其中之一:

try 
{
    // code that may throw exceptions    
}
catch(Exception ex) 
{
    // add error logging here
    throw;
}

答案 1 :(得分:108)

不要这样做,

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

您将丢失堆栈跟踪信息......

要么,

try { ... }
catch { throw; }

OR

try { ... }
catch (Exception ex)
{
    throw new Exception("My Custom Error Message", ex);
}

您可能想要重新抛出的一个原因是,如果您正在处理不同的例外,那么 e.g。

try
{
   ...
}
catch(SQLException sex)
{
   //Do Custom Logging 
   //Don't throw exception - swallow it here
}
catch(OtherException oex)
{
   //Do something else
   throw new WrappedException("Other Exception occured");
}
catch
{
   System.Diagnostics.Debug.WriteLine("Eeep! an error, not to worry, will be handled higher up the call stack");
   throw; //Chuck everything else back up the stack
}

答案 2 :(得分:52)

C#(在C#6之前)不支持CIL“过滤异常”,这是VB所做的,因此在C#1-5中重新抛出异常的一个原因是你没有足够的信息。 catch()确定是否要实际捕获异常。

例如,在VB中你可以做

Try
 ..
Catch Ex As MyException When Ex.ErrorCode = 123
 .. 
End Try

...它不会处理具有不同ErrorCode值的MyExceptions。在v6之前的C#中,如果ErrorCode不是123,则必须捕获并重新抛出MyException:

try 
{
   ...
}
catch(MyException ex)
{
    if (ex.ErrorCode != 123) throw;
    ...
}

Since C# 6.0 you can filter就像VB一样:

try 
{
  // Do stuff
} 
catch (Exception e) when (e.ErrorCode == 123456) // filter
{
  // Handle, other exceptions will be left alone and bubble up
}

答案 3 :(得分:13)

我使用代码的主要原因是:

try
{
    //Some code
}
catch (Exception e)
{
    throw;
}

是这样我可以在catch中有一个断点,它有一个实例化的异常对象。我在开发/调试时做了很多。当然,编译器会在所有未使用的e上发出警告,理想情况下,它们应该在发布版本之前删除。

他们在调试时很不错。

答案 4 :(得分:11)

重新抛出异常的一个正当理由可能是您希望向异常添加信息,或者可能将原始异常包装在您自己的异常中:

public static string SerializeDTO(DTO dto) {
  try {
      XmlSerializer xmlSer = new XmlSerializer(dto.GetType());
      StringWriter sWriter = new StringWriter();
      xmlSer.Serialize(sWriter, dto);
      return sWriter.ToString();
  }
  catch(Exception ex) {
    string message = 
      String.Format("Something went wrong serializing DTO {0}", DTO);
    throw new MyLibraryException(message, ex);
  }
}

答案 5 :(得分:10)

  

这不完全等同于没有   完全处理异常?

不完全是,它不一样。它重置了异常的堆栈跟踪。 虽然我同意这可能是一个错误,因而是一个糟糕代码的例子。

答案 6 :(得分:8)

你不想抛出前 - 因为这将失去调用堆栈。请参阅 Exception Handling (MSDN)。

是的,try ... catch没有任何用处(除了丢失调用堆栈 - 所以它实际上更糟糕 - 除非出于某种原因你不想公开这些信息)。

答案 7 :(得分:5)

人们没有提到的一点是,虽然.NET语言并没有真正做出正确的区分,但是当发生异常时是否应该采取行动的问题,以及是否会解决它,实际上是截然不同的问题。在许多情况下,人们应该根据无法解决的异常采取行动,并且在某些情况下,“解决”异常所需的一切都是将堆栈展开到某一点 - 不需要进一步的操作

由于人们普遍认为只能“抓住”人们可以“处理”的东西,因此很多代码应该在异常发生时采取行动。例如,许多代码将获取锁,将受保护的对象“暂时”置于违反其不变量的状态,然后将其置于合法状态,然后在其他任何人都可以看到该对象之前释放锁定。如果在对象处于危险无效状态时发生异常,通常的做法是在对象仍处于该状态时释放锁定。一个更好的模式是在对象处于“危险”状态时发生异常,明确地使锁无效,因此将来任何获取它的尝试都将立即失败。一致使用这种模式将极大地提高所谓的“口袋妖怪”异常处理的安全性,恕我直言的主要原因是代码允许异常渗透,而不首先采取适当的行动。

在大多数.NET语言中,代码基于异常采取操作的唯一方法是catch它(即使它知道它不会解决异常),执行有问题的操作然后再 - throw)。如果代码不关心抛出什么异常,则另一种可能的方法是使用带有ok块的try/finally标志;在块之前将ok标志设置为false,在块退出之前设置为true,并且在块内的任何return之前设置。finally。然后,在ok内,假设如果未设置catch,则必须发生异常。这种方法在语义上优于throw / {{1}},但是它很丑陋并且维护得不够应该。

答案 8 :(得分:3)

抛出抛出的一个可能原因是禁止堆叠中更深层次的任何异常过滤器(random old link)。但是,当然,如果这是意图,那么就会有评论。

答案 9 :(得分:3)

当您的编程功能为库或DLL时,这非常有用。

这个rethrow结构可以用来有目的地重置调用堆栈,这样就不会看到函数内部的单个函数抛出的异常,而是从函数本身获得异常。

我认为这只是用来使被抛出的异常变得更加清晰,并且不会进入" root"图书馆。

答案 10 :(得分:3)

虽然许多其他答案提供了一个很好的例子,说明为什么你可能想要重新抛出一个例外,但似乎没有人提到过最后一个'场景。

这方面的一个例子是你有一个设置光标的方法(例如等待光标),该方法有几个退出点(例如if()return;)并且你想确保光标是在方法结束时重置。

要执行此操作,您可以将所有代码包装在try / catch / finally中。在最后将光标设置回右光标。这样你就不会埋没任何有效的例外情况,在捕获中重新抛出它。

try
{
    Cursor.Current = Cursors.WaitCursor;
    // Test something
    if (testResult) return;
    // Do something else
}
catch
{
    throw;
}
finally
{
     Cursor.Current = Cursors.Default;
}

答案 11 :(得分:3)

这取决于你在catch块中做了什么,以及你是否想要将错误传递给调用代码。

您可能会说Catch io.FileNotFoundExeption ex然后使用替代文件路径或其他类似的路径,但仍会抛出错误。

同时执行Throw而不是Throw Ex可以保留完整的堆栈跟踪。抛出ex从throw语句重新启动堆栈跟踪(我希望这是有道理的。)

答案 12 :(得分:2)

在您发布的代码示例中,实际上没有必要捕获异常,因为没有对catch进行任何操作只是重新抛出,实际上它的弊大于利堆栈丢失了。

然而,如果发生异常,你会在执行某些逻辑(例如关闭文件锁的sql连接,或者只是一些日志记录)时捕获异常,将其抛回到调用代码来处理。这在业务层中比在前端代码中更常见,因为您可能希望编码器实现您的业务层来处理异常。

重新迭代虽然在您发布的示例中捕获异常没有任何意义。不要那样做!

答案 13 :(得分:1)

除了其他人所说的之外,请参阅my answer一个相关问题,该问题表明捕获和重新抛出不是无操作(它在VB中,但是一些代码可能是从VB调用的C# )。

答案 14 :(得分:1)

大多数答案都在讨论方案catch-log-rethrow。

不要在代码中编写它,而是考虑使用AOP,特别是Postsharp.Diagnostic.Toolkit和OnExceptionOptions IncludeParameterValue和 IncludeThisArgument

答案 15 :(得分:1)

很抱歉,但许多“改进设计”的例子仍然闻到可怕的气味,或者极具误导性。尝试{} catch {log;扔掉是完全没有意义的。异常日志记录应在应用程序内部的中心位置完成。异常会使堆栈跟踪冒泡,为什么不将它们记录到某个地方并靠近系统的边界?

当您将上下文(即一个给定示例中的DTO)序列化到日志消息中时,应该谨慎使用。它可以轻松地包含可能不希望能够访问所有可以访问日志文件的人员的敏感信息。如果你没有向异常添加任何新信息,我真的没有看到异常包装的重点。好的旧Java有一点意义,它要求调用者知道在调用代码之后应该期望什么样的异常。由于你没有在.NET中使用它,因此至少80%的情况下包装不会有任何好处。

答案 16 :(得分:0)

当您没有特定的代码来处理当前异常时,或者当您有逻辑来处理特定的错误情况而又想跳过所有其他错误时,通过throw抛出异常非常有用。

示例:

string numberText = "";
try
{
    Console.Write("Enter an integer: ");
    numberText = Console.ReadLine();
    var result = int.Parse(numberText);

    Console.WriteLine("You entered {0}", result);
}
catch (FormatException)
{
    if (numberText.ToLowerInvariant() == "nothing")
    {
        Console.WriteLine("Please, please don't be lazy and enter a valid number next time.");
    }
    else
    {
        throw;
    }
}    
finally
{
    Console.WriteLine("Freed some resources.");
}
Console.ReadKey();

但是,还有另一种方法,在catch块中使用条件子句

string numberText = "";
try
{
    Console.Write("Enter an integer: ");
    numberText = Console.ReadLine();
    var result = int.Parse(numberText);

    Console.WriteLine("You entered {0}", result);
}
catch (FormatException) when (numberText.ToLowerInvariant() == "nothing")
{
    Console.WriteLine("Please, please don't be lazy and enter a valid number next time.");
}    
finally
{
    Console.WriteLine("Freed some resources.");
}
Console.ReadKey();
  

此机制比重新引发异常更为有效,因为   .NET运行时不需要重建异常对象   重新扔之前。