首页 文章

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

提问于
浏览
367

有一些帖子询问这两者之间的区别是什么 .
(为什么我还要提这个......)

但我的问题是不同的,我称之为"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 具有相同的效果吗?

10 回答

  • 87

    让我们理解throw和throw ex之间的区别 . 我听说在很多.net采访中都会被问到这个常见问题 .

    为了概括这两个术语,throw和throw ex都用于了解异常发生的位置 . 抛出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 . 以及行号

    enter image description here

    其次..让我们通过throw ex了解 . 在M2方法catch块中用throw ex替换throw . 如下 .

    enter image description here

    throw ex代码的输出如下 .

    enter image description here

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

  • 36

    抛出ex时,抛出的异常变为“原始”异常 . 所以之前的所有堆栈跟踪都不会存在 .

    如果你做了抛出,那么异常只是在线上,你将获得完整的堆栈跟踪 .

  • 4

    看这里:http://blog-mstechnology.blogspot.de/2010/06/throw-vs-throw-ex.html

    Throw

    try 
    {
        // do some operation that can fail
    }
    catch (Exception ex)
    {
        // do some local cleanup
        throw;
    }
    

    它使用Exception保留堆栈信息

    这被称为“Rethrow”

    如果想抛出新的异常,

    throw new ApplicationException("operation failed!");
    

    Throw Ex

    try
    {
        // do some operation that can fail
    }
    catch (Exception ex)
    {
        // do some local cleanup
        throw ex;
    }
    

    它不会发送带有异常的堆栈信息

    这被称为“打破堆栈”

    如果想抛出新的异常,

    throw new ApplicationException("operation failed!",ex);
    
  • 1

    不,这将导致异常具有不同的堆栈跟踪 . 只在 catch 处理程序中使用 throw 而没有任何异常对象将使堆栈跟踪保持不变 .

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

  • 0

    其他答案完全正确,但我认为这个答案提供了一些额外的答案 .

    考虑这个例子:

    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
    

    特别是你可以看到导致问题的是 line 35 . 但是,这需要人们搜索 InnerException ,并且在简单情况下使用内部异常感觉有点间接 .

    this blog post中,它们通过在 Exception 对象上调用(通过反射) internal intance方法 InternalPreserveStackTrace() 来保留行号(try块的行) . 但是使用这样的反射并不好(.NET Framework可能会在某天没有警告的情况下改变它们的 internal 成员) .

  • -1

    是,有一点不同;

    • 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;
        }
    }
    
  • 4

    MSDN stands for

    一旦抛出异常,它携带的部分信息就是堆栈跟踪 . 堆栈跟踪是方法调用层次结构的列表,该方法调用层次结构以抛出异常的方法开始,并以捕获异常的方法结束 . 如果通过在throw语句中指定异常来重新抛出异常,则会在当前方法重新启动堆栈跟踪,并且丢失抛出异常的原始方法与当前方法之间的方法调用列表 . 要保留包含异常的原始堆栈跟踪信息,请使用throw语句而不指定异常 .

  • 2

    为了给您一个不同的视角,如果您向客户端提供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
        }
    }
    
  • 596

    (我之前发过,@ 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 语句的行号, notDivByZero() 调用的行号,这可能是有道理的,因为我现在考虑一下......

    在发布模式下输出

    例外1:

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

    例外2:

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

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

  • 4
    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行都被注释 - 输出 - 内部前

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

    • 如果所有第1行和第2行都被注释 - 输出 - 内部ex System.Exception:devide by 0 ----

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

    如果是throw ex,则会重置StackTrace;

相关问题