【发布时间】:2010-09-15 18:15:33
【问题描述】:
这样做更好吗:
try
{
...
}
catch (Exception ex)
{
...
throw;
}
或者这个:
try
{
...
}
catch (Exception ex)
{
...
throw ex;
}
他们做同样的事情吗?一个比另一个好?
【问题讨论】:
这样做更好吗:
try
{
...
}
catch (Exception ex)
{
...
throw;
}
或者这个:
try
{
...
}
catch (Exception ex)
{
...
throw ex;
}
他们做同样的事情吗?一个比另一个好?
【问题讨论】:
您应该始终使用以下语法重新引发异常。否则你会踩到堆栈跟踪:
throw;
如果您打印由 throw ex 产生的跟踪,您会看到它以该语句结束,而不是异常的真正来源。
基本上,使用throw ex应该被视为刑事犯罪。
如果需要重新抛出来自其他地方(AggregateException、TargetInvocationException)或可能来自另一个线程的异常,您也不应该直接重新抛出它。相反,ExceptionDispatchInfo 保留了所有必要的信息。
try
{
methodInfo.Invoke(...);
}
catch (System.Reflection.TargetInvocationException e)
{
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(e.InnerException).Throw();
throw; // just to inform the compiler that the flow never leaves the block
}
【讨论】:
我的偏好是使用
try
{
}
catch (Exception ex)
{
...
throw new Exception ("Add more context here", ex)
}
这保留了原始错误,但它允许您添加更多上下文,例如对象 ID、连接字符串和类似的东西。我的异常报告工具通常会报告五个链接的异常,每个都报告更多详细信息。
【讨论】:
throw; 取决于具体情况。
throw new Exception( string.Format("while deserializing the element {0}", element.Name), ex );,那么如果我在 XML 解析中遇到崩溃,顶级异常处理程序会打印所有 InnerExceptions,并且我会得到它当前所有嵌套 XML 节点名称的完整跟踪内部,仅通过查看调用堆栈就不会很明显,该调用堆栈只是对递归函数进行了很多调用。
如果您使用out变量引发异常(第二个示例),堆栈跟踪将包括引发异常的原始方法。
在第一个示例中,堆栈跟踪将被更改以反映当前方法。
例子:
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 stack trace
throw; // This will show ReadAllLines in the stack trace
}
【讨论】:
第一个保留异常的原始堆栈跟踪,第二个将其替换为当前位置。
因此,第一个到目前为止更好。
【讨论】:
我同意大多数情况下,您要么想做一个简单的throw,以尽可能多地保留有关问题所在的信息,要么您想抛出一个新的异常,其中可能包含该异常作为内部- 异常与否,取决于您想了解导致它的内部事件的可能性。
但有一个例外。在几种情况下,一个方法会调用另一个方法,而导致内部调用异常的条件应被视为外部调用的相同异常。
一个例子是使用另一个集合实现的专用集合。假设它是一个 DistinctList<T>,它包装了一个 List<T>,但拒绝重复项。
如果有人在您的集合类上调用ICollection<T>.CopyTo,它可能只是在内部集合上直接调用CopyTo(如果说,所有自定义逻辑仅适用于添加到集合或设置它) .现在,该调用将引发的条件与您的集合应引发的条件完全相同,以匹配ICollection<T>.CopyTo 的文档。
现在,您根本无法捕获异常,而让它通过。但在这里,当用户在DistinctList<T> 上调用某些东西时,他们会从List<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;,要么您想要构建自己的异常以从相关方法的角度更直接地匹配问题。在上述情况下,这样的重新抛出更有意义,还有很多其他情况。例如,举一个非常不同的例子,如果使用FileStream 和XmlTextReader 实现的ATOM 文件阅读器接收到文件错误或无效的XML,那么它可能希望抛出与从这些类接收到的完全相同的异常,但它应该让调用者知道是 AtomFileReader 正在抛出 FileNotFoundException 或 XmlException,因此它们可能是类似重新抛出的候选者。
我们也可以将两者结合起来:
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;
}
}
【讨论】:
您应该始终使用“throw;”在 .NET 中重新抛出异常,
参考博文Throw vs. Throw ex。
基本上,MSIL (CIL) 有两条指令——“throw”和“rethrow”以及 C# 的“throw ex;”。被编译成 MSIL 的 "throw" 和 C# 的 "throw;" - 进入MSIL“重投”!基本上我可以看到“throw ex”覆盖堆栈跟踪的原因。
【讨论】:
第一个更好。如果您尝试调试第二个并查看调用堆栈,您将看不到原始异常的来源。如果您确实需要重新抛出,有一些技巧可以保持调用堆栈完整(尝试搜索,之前已回答过)。
【讨论】:
我发现如果在捕获异常的同一方法中抛出异常,则不会保留堆栈跟踪,因为它的价值。
void testExceptionHandling()
{
try
{
throw new ArithmeticException("illegal expression");
}
catch (Exception ex)
{
throw;
}
finally
{
System.Diagnostics.Debug.WriteLine("finally called.");
}
}
【讨论】:
这取决于。在调试版本中,我希望尽可能少地查看原始堆栈跟踪。在这种情况下,“投掷”;符合要求。
然而,在发布版本中,(a) 我想记录包含原始堆栈跟踪的错误,一旦完成,(b) 重新设计错误处理以对用户更有意义。这里“抛出异常”是有道理的。确实,重新抛出错误会丢弃原始堆栈跟踪,但非开发人员从看到堆栈跟踪信息中一无所获,因此重新抛出错误是可以的。
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:”与“Throw ex;”相提并论。让它有点像红鲱鱼。真正的选择是在“投掷”之间。和“Throw Exception”,其中“Throw ex;”是“抛出异常”的一个不太可能的特例。
【讨论】:
ex.ToString() 记录到应用程序日志中,等等。而且您无需为是否正在运行调试版本而烦恼。