在C#中重新抛出exception的正确方法是什么?
我有一个问题来源于我的合作伙伴,与我做的事情不一样。
这样做更好吗:
try { ... } catch (Exception ex) { ... throw; }
或这个:
try { ... } catch (Exception ex) { ... throw ex; }
他们做同样的事情吗? 这个比那个好吗?
你应该总是使用下面的语法重新抛出一个exception,否则你会踩踏堆栈跟踪:
throw;
如果您打印由“throw ex”产生的追踪,您将会看到它以该语句结束,而不是真正的exception来源。
基本上用“扔掉”应该被认为是犯罪行为。
我的偏好是使用
try { } catch (Exception ex) { ... throw new Exception ("Put more context here", ex) }
这保留了原始错误,但允许您放置更多的上下文,如对象ID,连接string,类似的东西。 通常我的例外报告工具将有5个链式例外报告,每个报告更多的细节。
如果抛出exception(第二个示例),则StackTrace将包含抛出exception的原始方法。
在第一个例子中,StackTrace将被改变以反映当前的方法。
例:
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 StackTrace throw; // This will show ReadAllLines in the StackTrace }
第一个保存exception的原始堆栈跟踪,第二个用当前位置replace它。
所以第一个是BY FAR越好越好。
我知道这是一个古老的问题,但我要回答这个问题,因为我不同意这里的所有答案。
现在,我会同意大多数时候你要么做一个简单的throw
,尽可能地保留关于错误的信息,要么抛出一个新的exception作为内部exception,或者不是,取决于你想知道导致它的内在事件的可能性。
虽然有一个例外。 有几种情况下,一个方法将调用另一个方法,并且在内部调用中导致exception的条件应该被视为外部调用中的相同exception。
一个例子是使用另一个集合实现的专用集合。 假设这是一个DistinctList<T>
,它包装一个List<T>
但拒绝重复的项目。
如果有人在你的集合类上调用了ICollection<T>.CopyTo
,它可能就是直接调用内部集合上的CopyTo
(如果说所有的自定义逻辑只适用于添加到集合或设置它)。 现在,这个调用将会抛出的条件与你的集合抛出的匹配ICollection<T>.CopyTo
文档的条件完全一样。
现在,你根本就不能抓住这个执行,而是让它通过。 虽然用户在调用DistinctList<T>
时会从List<T>
获取exception。 不是世界末日,但你可能想要隐藏这些实现细节。
或者你可以做你自己的检查:
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); } }
就添加的新代码而言,这可能是越野车,这更糟糕。 而我们从内在的例外中并没有获得任何东西。 如果我们向这个方法传递一个null数组并接收到一个ArgumentNullException
,那么我们不会通过检查内部exception来学习任何东西,并且知道对_innerList.CopyTo
的调用传递了一个null数组并抛出了一个ArgumentNullException
exception。
在这里,我们可以做任何我们想要的:
public CopyTo(T[] array, int arrayIndex) { try { _innerList.CopyTo(array, arrayIndex); } catch(ArgumentException ae) { throw ae; } }
如果用户用不正确的参数调用它,我们希望抛出的每一个exception都会被重新抛出。 如果在这里使用的逻辑有一个错误,那么它有两行之一 – 要么是我们在决定这种情况下是错误的,要么是这种方法起作用,或者我们错误地将ArgumentException
作为exceptiontypes寻找。 这是catch块可能只有的两个bug。
现在。 我仍然同意,大部分时间你要么想要一个简单的throw;
或者你想构build自己的exception,从所讨论的方法的angular度更直接地匹配问题。 像上面这样的情况下,像这样的重新投掷更有意义,还有很多其他的情况。 举个例子,如果用FileStream
和XmlTextReader
实现的ATOM文件读取器接收到文件错误或无效的XML,那么它可能会想要抛出与从这些类中接收到的完全相同的exception,但它应该看起来像调用者是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; } }
你应该总是使用“扔”; 在.NET中重新抛出exception,
请参阅http://weblogs.asp.net/bhouse/archive/2004/11/30/272297.aspx
基本上MSIL(CIL)有两个指令 – “抛出”和“重新抛出”和C#的“抛出前”; 被编译成MSIL的“throw”和C#的“throw”。 – 进入MSIL“重新抛出”! 基本上我可以看到“抛出”覆盖栈跟踪的原因。
第一个更好。 如果您尝试debugging第二个,并查看调用堆栈,您将看不到原始exception来自哪里。 如果你真的需要重新抛出,有一些技巧可以保持调用堆栈(尝试search,之前已经回答)。
我发现,如果exception被捕获的方法被抛出,堆栈跟踪不会被保留,因为它是值得的。
void testExceptionHandling() { try { throw new ArithmeticException("illegal expression"); } catch (Exception ex) { throw; } finally { System.Diagnostics.Debug.WriteLine("finally called."); } }
这取决于。 在debugging版本中,我希望尽可能less地看到原始堆栈跟踪。 在这种情况下,“抛出” 符合法案。 然而,在发布版本中,(a)我想用包含的原始堆栈跟踪logging错误,并且一旦完成,(b)重新deviseerror handling以使用户更有意义。 这里“抛出exception”是有道理的。 确实,重新抛出错误会丢弃原始堆栈跟踪,但是非开发者无法看到堆栈跟踪信息,因此重新抛出错误是可以的。
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 } }
问题出现的方式是“抛出:”与“抛出”; 使它成为一个红鲱鱼。 真正的select是在“投掷”之间。 和“抛出exception”,其中“抛出”; 是“抛出exception”的一个不太可能的特例。