“ throw”和“ throw ex”之间有区别吗?


437

有些帖子问这两者之间已经有什么区别。
(为什么我什至不得不提这个...)

但是我的问题有所不同,在另一种类似于神的错误处理方法中,我称呼为“ 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.
    }
}

如果try & catch在中使用Main,那么我将使用throw;该错误。但是在上面简化的代码中,所有异常都会通过HandleException

是否throw ex;与调用同样的效果throw里面调用时HandleException


3
两者之间有区别,这与是否或在异常中显示堆栈跟踪有关,但是我不记得现在是哪个,所以我不会列出答案。
Joel Coehoorn

@Joel:谢谢。我猜想使用HandleError异常是一个坏主意。我只是想重构一些错误处理代码。
dance2die

1
第三种方法是包装新异常并重新抛出timwise.blogspot.co.uk/2014/05/…–
蒂姆·阿贝尔

Answers:


679

是,有一点不同;

  • throw ex重置堆栈跟踪(因此您的错误似乎源自HandleException
  • throw 不会-原罪犯将得到保留。

    static void Main(string[] args)
    {
        try
        {
            Method2();
        }
        catch (Exception ex)
        {
            Console.Write(ex.StackTrace.ToString());
            Console.ReadKey();
        }
    }
    
    private static void Method2()
    {
        try
        {
            Method1();
        }
        catch (Exception ex)
        {
            //throw ex resets the stack trace Coming from Method 1 and propogates it to the caller(Main)
            throw ex;
        }
    }
    
    private static void Method1()
    {
        try
        {
            throw new Exception("Inside Method1");
        }
        catch (Exception)
        {
            throw;
        }
    }

28
:要在马克的回答扩大一点,你可以在这里找到更多的细节geekswithblogs.net/sdorman/archive/2007/08/20/...
斯科特多尔曼

3
@Shaul; 不,不是。我在对您的帖子的评论中提供了详细信息。
Marc Gravell

1
@Marc Gravell-对不起,您说的对。对不起,对我而言,现在撤消太迟了... :(
Shaul Behr

3
@Marc:看来,只有在抛出初始异常的方法中没有抛出异常时,抛出异常才会保留原始犯罪者(请参阅此问题:stackoverflow.com/questions/5152265/…
Brann

3
@ScottDorman博客迁移后,您的链接似乎未正确转发。看起来现在住在这里编辑:嘿,等等,那是你的博客!修复您自己的链接!; ^ D
ruffin

96

(我之前发布过,@ 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()现在考虑一下,这可能很有意义...

在释放模式下输出

例外1:

at ConsoleAppBasics.Program.ThrowException1()
at ConsoleAppBasics.Program.Main(String[] args)

例外2:

at ConsoleAppBasics.Program.ThrowException2()
at ConsoleAppBasics.Program.Main(String[] args)

它是否仅在调试模式下维护原始stackTrace?


1
这是因为编译器的优化过程内联了诸如之类的短方法DevideByZero,因此堆栈跟踪是相同的。也许您应该自己将其发布为问题
Menahem

42

其他答案是完全正确的,但我认为此答案提供了一些额外的细节。

考虑以下示例:

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,在简单情况下使用内部异常感觉有些间接。

此博客文章中,他们通过调用(通过反射)对象上的internalintance方法InternalPreserveStackTrace()来保留行号(try块的行)Exception。但是使用这样的反射是不好的(.NET Framework可能internal有一天在没有警告的情况下更改其成员)。


6

让我们了解throw和throw ex之间的区别。我听说在许多.net采访中都询问了这个常见问题。

只是为了概述这两个术语,throw和throw ex都用于了解异常发生在何处。Throw ex重写异常的堆栈跟踪信息,而不管实际在哪里被抛出。

让我们看一个例子。

首先让我们了解一下。

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。连同行号

在此处输入图片说明

其次..让我们通过抛出ex来理解。只需在M2方法catch块中用throw ex替换throw。如下。

在此处输入图片说明

throw ex代码的输出如下。

在此处输入图片说明

您可以看到输出的差异。throw ex仅忽略所有先前的层次结构,并使用在其中写入throw ex的行/方法重置堆栈跟踪。


5

当您这样做throw ex时,抛出的异常将成为“原始”异常。因此所有以前的堆栈跟踪都不会存在。

如果您这样做throw,则该异常仅会沿线结束,您将获得完整的堆栈跟踪。


4

不,这将导致异常具有不同的堆栈跟踪。只有throwcatch处理程序中使用没有任何异常的对象,堆栈跟踪才会保持不变。

无论是否应重新抛出异常,您都可能希望从HandleException返回一个布尔值。


4

MSDN代表

引发异常后,堆栈携带的部分信息就是堆栈跟踪。堆栈跟踪是方法调用层次结构的列表,该列表以引发异常的方法开始并以捕获异常的方法结束。如果通过在throw语句中指定异常来重新引发异常,则堆栈跟踪将从当前方法重新启动,并且引发该异常的原始方法与当前方法之间的方法调用列表将丢失。要使原始堆栈跟踪信息与该异常保持一致,请使用throw语句而不指定该异常。


2

在这里查看: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);

0

为了给您一个不同的看法,如果您要向客户端提供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
    }
}

-1
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. 如果所有第1、2和3行都已注释-输出-内部ex

  2. 如果注释了所有第2行和第3行-输出-内部ex System.DevideByZeroException:{“试图除以零。”} ---------

  3. 如果对第1行和第2行都进行了注释-输出-内部ex System.Exception:由0代替----

  4. 如果所有第1行和第3行都被注释了-输出-内部ex System.DevideByZeroException:{“试图除以零。”} ---------

如果抛出throw,StackTrace将被重置;

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.