我正在查看关于可序列化 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!
}
堆栈跟踪信息对于确定问题的根本原因至关重要!
做
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 一样)。
参考:
MSDN - 异常处理
MSDN - try-catch(C# 参考)
第一的;文章中的代码这样做的方式是邪恶的。 throw ex
会将异常中的调用堆栈重置到该 throw 语句所在的位置;丢失有关异常实际创建位置的信息。
其次,如果您只是像这样捕获并重新抛出,我认为没有任何附加值,上面的代码示例在没有 try-catch 的情况下也一样好(或者,给定 throw ex
位,甚至更好)。
但是,在某些情况下,您可能想要捕获并重新抛出异常。日志记录可能是其中之一:
try
{
// code that may throw exceptions
}
catch(Exception ex)
{
// add error logging here
throw;
}
不要这样做,
try
{
...
}
catch(Exception ex)
{
throw ex;
}
您将丢失堆栈跟踪信息...
要么做,
try { ... }
catch { throw; }
或者
try { ... }
catch (Exception ex)
{
throw new Exception("My Custom Error Message", ex);
}
您可能想要重新抛出的原因之一是如果您正在处理不同的异常,例如
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
}
WrappedException
忘记了包装。作者是否打算将原始异常作为 InnerException 放入该构造函数中?
IOException
),较长的首字母缩写词应为 PascalCased (SqlException
)。与 Java 不同(参见 SQLException
)。这可能就是为什么 SQLException
困扰你的原因,@MichaelMyers。
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
}
我有如下代码的主要原因:
try
{
//Some code
}
catch (Exception e)
{
throw;
}
这样我就可以在 catch 中有一个断点,它有一个实例化的异常对象。我在开发/调试时经常这样做。当然,编译器会对所有未使用的 e 发出警告,理想情况下应该在发布构建之前将它们删除。
不过,它们在调试过程中很好。
try {
和 } catch () {...}
周围的 #if DEBUG
有点混乱,坦率地说,让我感到恶心......一般来说,预处理器不是我的朋友。
重新抛出异常的一个正当理由可能是您想向异常添加信息,或者可能将原始异常包装在您自己的制作中:
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);
}
}
这不完全等同于根本不处理异常吗?
不完全一样,也不一样。它重置异常的堆栈跟踪。尽管我同意这可能是一个错误,因此是错误代码的示例。
你不想抛出 ex - 因为这会丢失调用堆栈。请参阅 Exception Handling (MSDN)。
是的,try...catch 没有做任何有用的事情(除了丢失调用堆栈 - 所以实际上更糟 - 除非出于某种原因您不想公开此信息)。
当您为库或 dll 编写函数时,这可能很有用。
这种重新抛出结构可用于有目的地重置调用堆栈,以便从函数本身获取异常,而不是查看函数内部单个函数抛出的异常。
我认为这只是为了使抛出的异常更干净,并且不会进入库的“根”。
人们没有提到的一点是,虽然 .NET 语言并没有真正做出适当的区分,但是当异常发生时是否应该采取行动以及是否会解决它的问题实际上是不同的问题。在许多情况下,人们应该根据没有希望解决的异常采取措施,并且在某些情况下,“解决”异常所需要的只是将堆栈展开到某个点——不需要进一步的操作.
由于人们应该只“捕获”可以“处理”的东西这一普遍智慧,许多在发生异常时应该采取行动的代码却没有。例如,很多代码会获取锁,将受保护的对象“临时”置于违反其不变量的状态,然后将其置于合法状态,然后在其他人看到该对象之前释放锁。如果在对象处于危险无效状态时发生异常,通常的做法是在对象仍处于该状态时释放锁。更好的模式是在对象处于“危险”条件时发生异常,明确使锁无效,因此任何未来获取它的尝试都将立即失败。一致使用这种模式将大大提高所谓的“Pokemon”异常处理的安全性,恕我直言,这主要是因为代码允许异常在没有先采取适当措施的情况下渗透出来。
在大多数 .NET 语言中,代码根据异常采取行动的唯一方法是catch
它(即使它知道它不会解决异常),执行有问题的行动,然后重新throw
)。如果代码不关心抛出什么异常,另一种可能的方法是使用带有 try/finally
块的 ok
标志;在块之前将 ok
标志设置为 false
,在块退出之前设置为 true
,在块内的任何 return
之前设置。然后,在 finally
中,假设如果未设置 ok
,则一定发生了异常。这种方法在语义上比 catch
/throw
更好,但丑陋且难以维护。
虽然许多其他答案提供了很好的例子来说明为什么您可能想要重新抛出异常,但似乎没有人提到“最终”的场景。
例如,您有一个设置光标的方法(例如设置为等待光标),该方法有几个退出点(例如 if () return;),并且您希望确保光标在方法结束。
为此,您可以将所有代码包装在 try/catch/finally 中。在finally中将光标设置回右光标。为了不掩埋任何有效的异常,请在 catch 中重新抛出它。
try
{
Cursor.Current = Cursors.WaitCursor;
// Test something
if (testResult) return;
// Do something else
}
catch
{
throw;
}
finally
{
Cursor.Current = Cursors.Default;
}
catch
在历史上是 try...finally
的必需部分,还是在本示例中发挥了功能性作用? - 我只是仔细检查了一下,我可以在没有 catch 块的情况下使用 try {} finally {}
。
catch-throw 的一个可能原因是禁用堆栈更深处的任何异常过滤器以防止向下过滤 (random old link)。但是,当然,如果这是意图,那里会有评论说这样的话。
这取决于您在 catch 块中所做的事情,以及您是否要将错误传递给调用代码。
您可能会说 Catch io.FileNotFoundExeption ex
然后使用替代文件路径或类似的路径,但仍然会引发错误。
此外,使用 Throw
而不是 Throw Ex
可以让您保留完整的堆栈跟踪。 Throw ex 从 throw 语句重新启动堆栈跟踪(我希望这是有道理的)。
实际上,在您发布的代码示例中,捕获异常是没有意义的,因为在捕获时没有做任何事情,它只是重新抛出,实际上它弊大于利,因为调用堆栈丢失了.
但是,如果发生异常,您将捕获异常以执行一些逻辑(例如关闭文件锁的 sql 连接,或者只是一些日志记录),然后将其扔回调用代码进行处理。这在业务层中比前端代码更常见,因为您可能希望实现业务层的编码器来处理异常。
尽管在您发布的示例中捕获异常没有意义,但要重新迭代。不要那样做!
对不起,但许多“改进设计”的例子仍然闻起来很糟糕,或者可能极具误导性。尝试 { } catch { log; throw } 完全没有意义。异常日志记录应该在应用程序的中心位置完成。无论如何,异常都会在堆栈跟踪中冒泡,为什么不将它们记录在靠近系统边界的某个地方呢?
当您将上下文(即一个给定示例中的 DTO)序列化到日志消息中时,应谨慎使用。它可以很容易地包含敏感信息,可能不想到达所有可以访问日志文件的人的手中。而且,如果您不向异常添加任何新信息,我真的看不出异常包装的意义。好的旧 Java 对此有一定的意义,它要求调用者知道在调用代码时应该期待什么样的异常。由于您在 .NET 中没有此功能,因此在我见过的至少 80% 的情况下,包装没有任何好处。
除了其他人所说的之外,请参阅 my answer 到一个相关问题,该问题表明捕获和重新抛出不是无操作(它在 VB 中,但某些代码可以从 VB 调用 C#)。
大多数答案都在谈论场景捕获日志重新抛出。
不要在代码中编写它,而是考虑使用 AOP,特别是 Postsharp.Diagnostic.Toolkit 与 OnExceptionOptions IncludeParameterValue 和 IncludeThisArgument
当您没有特定代码来处理当前异常时,或者当您有处理特定错误情况的逻辑但想跳过所有其他情况时,通过 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 运行时不必在重新抛出异常对象之前重新构建它。
ex
对象,则无需实例化它。throw ex
不会重新启动堆栈跟踪。