在 C# 中重新抛出异常的正确方法是什么?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/178456/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What is the proper way to re-throw an exception in C#?
提问by Patrick Desjardins
I have a question for you that stems from my partner doing things a different way than I do.
我有一个问题要问你,因为我的搭档做事的方式与我不同。
Is it better to do this :
这样做是否更好:
try
{
...
}
catch (Exception ex)
{
...
throw;
}
or this:
或这个:
try
{
...
}
catch (Exception ex)
{
...
throw ex;
}
Do they do the same thing? Is one better than the other?
他们做同样的事情吗?这个比那个好吗?
采纳答案by Torbj?rn Gyllebring
You should always use following syntax to rethrow an exception, else you'll stomp the stack trace:
您应该始终使用以下语法重新抛出异常,否则您将踩踏堆栈跟踪:
throw;
If you print the trace resulting from "throw ex", you'll see that it ends on that statement and not at the real source of the exception.
如果您打印由“throw ex”产生的跟踪,您将看到它以该语句结束,而不是在异常的真正来源处。
Basically, it should be deemed a criminal offense to use "throw ex".
基本上,使用“throw ex”应该被视为刑事犯罪。
回答by Quibblesome
The first preserves the original stack trace of the exception, the second one replaces it with the current location.
第一个保留异常的原始堆栈跟踪,第二个将其替换为当前位置。
Therefore the first is BY FAR the better.
因此,第一个是 BY FAR 更好。
回答by Mendelt
The first is better. If you try to debug the second and look at the call stack you won't see where the original exception came from. There are tricks to keep the call-stack intact (try search, it's been answered before) if you really need to rethrow.
第一个更好。如果您尝试调试第二个并查看调用堆栈,您将看不到原始异常的来源。如果您真的需要重新抛出,有一些技巧可以保持调用堆栈完整(尝试搜索,之前已经回答过)。
回答by RB.
My preferences is to use
我的偏好是使用
try
{
}
catch (Exception ex)
{
...
throw new Exception ("Put more context here", ex)
}
This preserves the original error, but allows you to put more context, such as an object ID, a connection string, stuff like that. Often my exception reporting tool will have 5 chained exceptions to report, each reporting more detail.
这保留了原始错误,但允许您放置更多上下文,例如对象 ID、连接字符串等。通常,我的异常报告工具会报告 5 个链式异常,每个异常报告更详细。
回答by RB.
If you throw an exception withouta variable (the second example) the StackTrace will include the original method that threw the exception.
如果您在没有变量的情况下抛出异常(第二个示例),则 StackTrace 将包含抛出异常的原始方法。
In the first example the StackTrace will be changed to reflect the current method.
在第一个示例中,将更改 StackTrace 以反映当前方法。
Example:
例子:
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
}
回答by Perry Tribolet
It depends. In a debug build, I want to see the original stack trace with as little effort as possible. In that case, "throw;" fits the bill. In a release build, however, (a) I want to log the error with the original stack trace included, and once that's done, (b) refashion the error handling to make more sense to the user. Here "Throw Exception" makes sense. It's true that rethrowing the error discards the original stack trace, but a non-developer gets nothing out of seeing stack trace information so it's okay to rethrow the error.
这取决于。在调试版本中,我希望尽可能少地查看原始堆栈跟踪。在这种情况下,“扔;” 符合要求。然而,在发布版本中,(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
}
}
The way the question is worded, pitting "Throw:" vs. "Throw ex;" makes it a bit of a red-herring. The real choice is between "Throw;" and "Throw Exception," where "Throw ex;" is an unlikely special case of "Throw Exception."
问题的措辞方式,将“扔:”与“扔前”区别开来;让它有点像红鲱鱼。真正的选择是在“扔”之间;和“抛出异常”,其中“抛出前;” 是“抛出异常”的一个不太可能的特例。
回答by Vinod T. Patil
You should always use "throw;" to rethrow the exceptions in .NET,
你应该总是使用“throw;” 在 .NET 中重新抛出异常,
Refer this, http://weblogs.asp.net/bhouse/archive/2004/11/30/272297.aspx
参考这个,http://weblogs.asp.net/bhouse/archive/2004/11/30/272297.aspx
Basically MSIL (CIL) has two instructions - "throw" and "rethrow" and C#'s "throw ex;" gets compiled into MSIL's "throw" and C#'s "throw;" - into MSIL "rethrow"! Basically I can see the reason why "throw ex" overrides the stack trace.
基本上 MSIL (CIL) 有两条指令——“throw”和“rethrow”以及 C# 的“throw ex;” 被编译成 MSIL 的“throw”和 C# 的“throw”;- 进入MSIL“重新抛出”!基本上我可以看到“throw ex”覆盖堆栈跟踪的原因。
回答by James
I found that if the exception is thrown in the same method that it is caught, the stack trace is not preserved, for what it's worth.
我发现如果在捕获异常的同一方法中抛出异常,则不会保留堆栈跟踪,这是值得的。
void testExceptionHandling()
{
try
{
throw new ArithmeticException("illegal expression");
}
catch (Exception ex)
{
throw;
}
finally
{
System.Diagnostics.Debug.WriteLine("finally called.");
}
}
回答by Jon Hanna
I know this is an old question, but I'm going to answer it because I have to disagree with all the answers here.
我知道这是一个老问题,但我要回答它,因为我必须不同意这里的所有答案。
Now, I'll agree that most of the time you either want to do a plain throw
, to preserve as much information as possible about what went wrong, or you want to throw a new exception that may contain that as an inner-exception, or not, depending on how likely you are to want to know about the inner events that caused it.
现在,我同意大多数时候你要么想要做一个普通的throw
,以保留尽可能多的关于出错的信息,要么你想要抛出一个可能包含作为内部异常的新异常,或者不是,取决于你想知道导致它的内在事件的可能性有多大。
There is an exception though. There are several cases where a method will call into another method and a condition that causes an exception in the inner call should be considered the same exception on the outer call.
不过也有例外。有几种情况,一个方法将调用另一个方法,并且在内部调用中导致异常的条件应被视为外部调用中的相同异常。
One example is a specialised collection implemented by using another collection. Let's say it's a DistinctList<T>
that wraps a List<T>
but refuses duplicate items.
一个示例是使用另一个集合实现的专用集合。假设它DistinctList<T>
是包装 aList<T>
但拒绝重复项的 a 。
If someone called ICollection<T>.CopyTo
on your collection class, it might just be a straight call to CopyTo
on the inner collection (if say, all the custom logic only applied to adding to the collection, or setting it up). Now, the conditions in which that call would throw are exactly the same conditions in which your collection should throw to match the documentation of ICollection<T>.CopyTo
.
如果有人调用ICollection<T>.CopyTo
您的集合类,它可能只是直接调用CopyTo
内部集合(如果说,所有自定义逻辑仅应用于添加到集合或设置它)。现在,该调用将抛出的条件与您的集合应抛出以匹配ICollection<T>.CopyTo
.
Now, you could just not catch the execption at all, and let it pass through. Here though the user gets an exception from List<T>
when they were calling something on a DistinctList<T>
. Not the end of the world, but you may want to hide those implementation details.
现在,您根本无法捕捉到 execption 并让它通过。尽管用户在List<T>
调用DistinctList<T>
. 不是世界末日,但您可能想要隐藏这些实现细节。
Or you could do your own checking:
或者你可以自己做检查:
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);
}
That's not the worse code because it's boilerplate and we can probably just copy it from some other implementation of CopyTo
where it wasn't a simple pass-through and we had to implement it ourselves. Still, it's needlessly repeating the exact samechecks that are going to be done in _innerList.CopyTo(array, arrayIndex)
, so the only thing it has added to our code is 6 lines where there could be a bug.
这不是最糟糕的代码,因为它是样板文件,我们可能只是从其他一些实现中复制它,CopyTo
而不是简单的传递,我们必须自己实现它。尽管如此,它还是不必要地重复了将在 中完成的完全相同的检查_innerList.CopyTo(array, arrayIndex)
,因此它添加到我们代码中的唯一内容是可能存在错误的 6 行。
We could check and wrap:
我们可以检查和包装:
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);
}
}
In terms of new code added that could potentially be buggy, this is even worse. And we don't gain a thing from the inner exceptions. If we pass a null array to this method and receive an ArgumentNullException
, we're not going to learn anything by examining the inner exception and learning that a call to _innerList.CopyTo
was passed a null array and threw an ArgumentNullException
.
就添加的可能有问题的新代码而言,情况更糟。我们不会从内部异常中获得任何东西。如果我们将一个空数组传递给这个方法并接收到一个ArgumentNullException
,我们将不会通过检查内部异常并了解调用_innerList.CopyTo
传递了一个空数组并抛出一个来学习任何东西ArgumentNullException
。
Here, we can do everything we want with:
在这里,我们可以做我们想做的一切:
public CopyTo(T[] array, int arrayIndex)
{
try
{
_innerList.CopyTo(array, arrayIndex);
}
catch(ArgumentException ae)
{
throw ae;
}
}
Every one of the exceptions that we expect to have to throw if the user calls it with incorrect arguments, will correctly be thrown by that re-throw. If there's a bug in the logic used here, it's in one of two lines - either we were wrong in deciding this was a case where this approach works, or we were wrong in having ArgumentException
as the exception type looked for. It's the only two bugs that the catch block can possibly have.
如果用户使用不正确的参数调用它,我们期望必须抛出的每一个异常,都将被重新抛出正确抛出。如果此处使用的逻辑中存在错误,则在两行之一中 - 要么我们错误地确定这是这种方法有效的情况,要么我们错误地ArgumentException
将异常类型视为所寻找的。这是 catch 块可能存在的仅有的两个错误。
Now. I still agree that most of the time you either want a plain throw;
or you want to construct your own exception to more directly match the problem from the perspective of the method in question. There are cases like the above where re-throwing like this makes more sense, and there are plenty of other cases. E.g. to take a very different example, if an ATOM file reader implemented with a FileStream
and an XmlTextReader
receives a file error or invalid XML, then it will perhaps want to throw exactly the same exception it received from those classes, but it should look to the caller that it is AtomFileReader
that is throwing a FileNotFoundException
or XmlException
, so they might be candidates for similarly re-throwing.
现在。我仍然同意,大多数时候您要么想要一个简单的,throw;
要么想要构建自己的异常,以从相关方法的角度更直接地匹配问题。有像上面这样重新抛出更有意义的情况,还有很多其他情况。例如,举一个非常不同的例子,如果使用 aFileStream
和 an实现的 ATOM 文件读取器XmlTextReader
接收到文件错误或无效的 XML,那么它可能想要抛出与从这些类接收到的完全相同的异常,但它应该查看调用者它AtomFileReader
被扔了FileNotFoundException
或者XmlException
,所以他们可能是候选人同样重新投掷。
Edit:
编辑:
We can also combine the two:
我们也可以将两者结合起来:
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;
}
}