有一些帖子询问这两者之间的区别是什么。 (为什么我还要提这个……)
但是我的问题在某种程度上是不同的,我在另一种类似错误的上帝般的处理方法中称其为“throw ex”。
public class Program {
public static void Main(string[] args) {
try {
// something
} catch (Exception ex) {
HandleException(ex);
}
}
private static void HandleException(Exception ex) {
if (ex is ThreadAbortException) {
// ignore then,
return;
}
if (ex is ArgumentOutOfRangeException) {
// Log then,
throw ex;
}
if (ex is InvalidOperationException) {
// Show message then,
throw ex;
}
// and so on.
}
}
如果在 Main
中使用了 try & catch
,那么我将使用 throw;
重新抛出错误。但在上面的简化代码中,所有异常都经过 HandleException
在 HandleException
内部调用时,throw ex;
是否与调用 throw
具有相同的效果?
是,有一点不同。
throw ex 重置堆栈跟踪(因此您的错误似乎源自 HandleException)
throw 不会 - 原始违规者将被保留。 static void Main(string[] args) { try { Method2(); } 捕捉(异常前){ Console.Write(ex.StackTrace.ToString()); Console.ReadKey(); } } 私有静态无效 Method2() { 尝试 { Method1(); } catch (Exception ex) { //throw ex 重置来自方法 1 的堆栈跟踪并将其传播给调用者(Main) throw ex; } } private static void Method1() { try { throw new Exception("Inside Method1"); } 捕捉(异常){ 抛出; } }
(我之前发布过,@Marc Gravell 纠正了我)
这是差异的演示:
static void Main(string[] args) {
try {
ThrowException1(); // line 19
} catch (Exception x) {
Console.WriteLine("Exception 1:");
Console.WriteLine(x.StackTrace);
}
try {
ThrowException2(); // line 25
} catch (Exception x) {
Console.WriteLine("Exception 2:");
Console.WriteLine(x.StackTrace);
}
}
private static void ThrowException1() {
try {
DivByZero(); // line 34
} catch {
throw; // line 36
}
}
private static void ThrowException2() {
try {
DivByZero(); // line 41
} catch (Exception ex) {
throw ex; // line 43
}
}
private static void DivByZero() {
int x = 0;
int y = 1 / x; // line 49
}
这是输出:
Exception 1:
at UnitTester.Program.DivByZero() in <snip>\Dev\UnitTester\Program.cs:line 49
at UnitTester.Program.ThrowException1() in <snip>\Dev\UnitTester\Program.cs:line 36
at UnitTester.Program.TestExceptions() in <snip>\Dev\UnitTester\Program.cs:line 19
Exception 2:
at UnitTester.Program.ThrowException2() in <snip>\Dev\UnitTester\Program.cs:line 43
at UnitTester.Program.TestExceptions() in <snip>\Dev\UnitTester\Program.cs:line 25
您可以看到,在异常 1 中,堆栈跟踪会返回到 DivByZero()
方法,而在异常 2 中则不会。
但请注意,ThrowException1()
和 ThrowException2()
中显示的行号是 throw
语句的行号,不是调用 DivByZero()
的行号,这可能现在想起来有点道理……
释放模式下的输出
例外一:
at ConsoleAppBasics.Program.ThrowException1()
at ConsoleAppBasics.Program.Main(String[] args)
例外 2:
at ConsoleAppBasics.Program.ThrowException2()
at ConsoleAppBasics.Program.Main(String[] args)
它是否仅在调试模式下维护原始 stackTrace?
DevideByZero
等短方法,所以堆栈跟踪是一样的。也许你应该把它作为一个问题单独发布
Throw 保留堆栈跟踪。所以假设 Source1 throws Error1 ,它被 Source2 捕获,Source2 说 throw then Source1 Error + Source2 Error 将在堆栈跟踪中可用。
Throw ex 不会保留堆栈跟踪。所以 Source1 的所有错误都会被清除,只有 Source2 错误会发送给客户端。
有时只是看的东西不太清楚,建议观看此视频演示以获得更清晰的信息,Throw vs Throw ex in C#。
https://i.stack.imgur.com/6DMcn.png
其他答案是完全正确的,但我认为这个答案提供了一些额外的细节。
考虑这个例子:
using System;
static class Program {
static void Main() {
try {
ThrowTest();
} catch (Exception e) {
Console.WriteLine("Your stack trace:");
Console.WriteLine(e.StackTrace);
Console.WriteLine();
if (e.InnerException == null) {
Console.WriteLine("No inner exception.");
} else {
Console.WriteLine("Stack trace of your inner exception:");
Console.WriteLine(e.InnerException.StackTrace);
}
}
}
static void ThrowTest() {
decimal a = 1m;
decimal b = 0m;
try {
Mult(a, b); // line 34
Div(a, b); // line 35
Mult(b, a); // line 36
Div(b, a); // line 37
} catch (ArithmeticException arithExc) {
Console.WriteLine("Handling a {0}.", arithExc.GetType().Name);
// uncomment EITHER
//throw arithExc;
// OR
//throw;
// OR
//throw new Exception("We handled and wrapped your exception", arithExc);
}
}
static void Mult(decimal x, decimal y) {
decimal.Multiply(x, y);
}
static void Div(decimal x, decimal y) {
decimal.Divide(x, y);
}
}
如果取消注释 throw arithExc;
行,则输出为:
Handling a DivideByZeroException.
Your stack trace:
at Program.ThrowTest() in c:\somepath\Program.cs:line 44
at Program.Main() in c:\somepath\Program.cs:line 9
No inner exception.
当然,您丢失了有关异常发生位置的信息。如果改为使用 throw;
行,您将得到以下结果:
Handling a DivideByZeroException.
Your stack trace:
at System.Decimal.FCallDivide(Decimal& d1, Decimal& d2)
at System.Decimal.Divide(Decimal d1, Decimal d2)
at Program.Div(Decimal x, Decimal y) in c:\somepath\Program.cs:line 58
at Program.ThrowTest() in c:\somepath\Program.cs:line 46
at Program.Main() in c:\somepath\Program.cs:line 9
No inner exception.
这好多了,因为现在您看到是 Program.Div
方法给您带来了问题。但是仍然很难看出这个问题是来自 try
块中的第 35 行还是第 37 行。
如果您使用第三种选择,包装在一个外部异常中,您不会丢失任何信息:
Handling a DivideByZeroException.
Your stack trace:
at Program.ThrowTest() in c:\somepath\Program.cs:line 48
at Program.Main() in c:\somepath\Program.cs:line 9
Stack trace of your inner exception:
at System.Decimal.FCallDivide(Decimal& d1, Decimal& d2)
at System.Decimal.Divide(Decimal d1, Decimal d2)
at Program.Div(Decimal x, Decimal y) in c:\somepath\Program.cs:line 58
at Program.ThrowTest() in c:\somepath\Program.cs:line 35
特别是,您可以看到导致问题的是 第 35 行。但是,这需要人们搜索 InnerException
,在简单的情况下使用内部异常感觉有些间接。
在 this blog post 中,它们通过调用(通过反射)Exception
对象上的 internal
实例方法 InternalPreserveStackTrace()
来保留行号(try 块的行)。但是这样使用反射并不好(.NET Framework 可能有一天会在没有警告的情况下更改其 internal
成员)。
让我们了解 throw 和 throw ex 之间的区别。我听说在许多.net 采访中,这个常见的问题都被问到了。
只是为了概述这两个术语, throw 和 throw ex 都用于了解异常发生的位置。 Throw ex 重写异常的堆栈跟踪,而与实际抛出的位置无关。
让我们通过一个例子来理解。
让我们先了解一下 Throw。
static void Main(string[] args) {
try {
M1();
} catch (Exception ex) {
Console.WriteLine(" -----------------Stack Trace Hierarchy -----------------");
Console.WriteLine(ex.StackTrace.ToString());
Console.WriteLine(" ---------------- Method Name / Target Site -------------- ");
Console.WriteLine(ex.TargetSite.ToString());
}
Console.ReadKey();
}
static void M1() {
try {
M2();
} catch (Exception ex) {
throw;
};
}
static void M2() {
throw new DivideByZeroException();
}
上面的输出如下。
显示实际抛出异常的完整层次结构和方法名称。它是 M2 -> M2。连同行号
https://i.stack.imgur.com/wUu6g.png
其次..让我们通过 throw ex 来理解。只需在 M2 方法 catch 块中将 throw 替换为 throw ex。如下。
https://i.stack.imgur.com/GlLqc.png
throw ex 代码的输出如下..
https://i.stack.imgur.com/V575m.png
您可以看到输出中的差异.. throw ex 只是忽略所有先前的层次结构并使用写入 throw ex 的行/方法重置堆栈跟踪。
当您执行 throw ex
时,抛出的异常将成为“原始”异常。所以所有以前的堆栈跟踪都不会存在。
如果您执行 throw
,则异常会继续,您将获得完整的堆栈跟踪。
不,这将导致异常具有不同的堆栈跟踪。仅在 catch
处理程序中使用没有任何异常对象的 throw
将使堆栈跟踪保持不变。
您可能希望从 HandleException 返回一个布尔值,是否应重新抛出异常。
Microsoft Docs 代表:
一旦抛出异常,它携带的部分信息就是堆栈跟踪。堆栈跟踪是方法调用层次结构的列表,它以引发异常的方法开始,以捕获异常的方法结束。如果通过在 throw 语句中指定异常而重新引发异常,则堆栈跟踪将在当前方法处重新启动,并且在引发异常的原始方法与当前方法之间的方法调用列表将丢失。要将原始堆栈跟踪信息与异常一起保留,请使用 throw 语句而不指定异常。
来源:https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2200
看这里:http://blog-mstechnology.blogspot.de/2010/06/throw-vs-throw-ex.html
扔:
try
{
// do some operation that can fail
}
catch (Exception ex)
{
// do some local cleanup
throw;
}
它使用异常保留堆栈信息
这被称为“重新抛出”
如果想抛出新的异常,
throw new ApplicationException("operation failed!");
扔前:
try
{
// do some operation that can fail
}
catch (Exception ex)
{
// do some local cleanup
throw ex;
}
它不会发送异常堆栈信息
这被称为“打破堆栈”
如果想抛出新的异常,
throw new ApplicationException("operation failed!",ex);
最好使用 throw
而不是 throw ex
。
throw ex 重置原始堆栈跟踪,并且找不到之前的堆栈跟踪。
如果我们使用 throw,我们将获得完整的堆栈跟踪。
为了让您对此有不同的看法,如果您向客户端提供 API 并且想要为内部库提供详细的堆栈跟踪信息,则使用 throw 特别有用。通过在此处使用 throw,我将在 File.Delete 的 System.IO.File 库的这种情况下获得堆栈跟踪。如果我使用 throw ex,那么该信息将不会传递给我的处理程序。
static void Main(string[] args) {
Method1();
}
static void Method1() {
try {
Method2();
} catch (Exception ex) {
Console.WriteLine("Exception in Method1");
}
}
static void Method2() {
try {
Method3();
} catch (Exception ex) {
Console.WriteLine("Exception in Method2");
Console.WriteLine(ex.TargetSite);
Console.WriteLine(ex.StackTrace);
Console.WriteLine(ex.GetType().ToString());
}
}
static void Method3() {
Method4();
}
static void Method4() {
try {
System.IO.File.Delete("");
} catch (Exception ex) {
// Displays entire stack trace into the .NET
// or custom library to Method2() where exception handled
// If you want to be able to get the most verbose stack trace
// into the internals of the library you're calling
throw;
// throw ex;
// Display the stack trace from Method4() to Method2() where exception handled
}
}
为了扩展 Lucero 的答案,您可以在不丢失原始堆栈跟踪的情况下完成原始代码的意图。
public class Program {
public static void Main(string[] args) {
try {
// something
} catch (Exception ex) {
if (!HandleException(ex)) throw;
}
}
/// <returns>
/// true if the exception has been handled;
/// false if exception should be passed along
/// </returns>
private static bool HandleException(Exception ex) {
if (ex is ThreadAbortException) {
// ignore then,
return true;
}
if (ex is ArgumentOutOfRangeException) {
// Log then,
return false;
}
if (ex is InvalidOperationException) {
// Show message then,
return false;
}
// and so on.
}
}
int a = 0;
try {
int x = 4;
int y ;
try {
y = x / a;
} catch (Exception e) {
Console.WriteLine("inner ex");
//throw; // Line 1
//throw e; // Line 2
//throw new Exception("devide by 0"); // Line 3
}
} catch (Exception ex) {
Console.WriteLine(ex);
throw ex;
}
如果所有第 1 ,2 和 3 行都被注释 - 输出 - 内部 ex 如果所有第 2 和 3 行都被注释 - 输出 - 内部 ex System.DevideByZeroException: {“尝试除以零。”}-------- - 如果所有第 1 行和第 2 行都被注释 - 输出 - 内部 ex System.Exception:除以 0 ---- 如果所有第 1 和 3 行都被注释 - 输出 - 内部 ex System.DevideByZeroException:{“尝试除以零。 "}---------
并且 StackTrace 将在 throw ex 的情况下被重置;
不定期副业成功案例分享
throw;
和throw ex;
抛出相同的对象,但它的堆栈跟踪以不同的方式修改,正如您在答案中所解释的那样。