ChatGPT解决这个技术问题 Extra ChatGPT

“扔”和“扔前”有区别吗?

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

但是我的问题在某种程度上是不同的,我在另一种类似错误的上帝般的处理方法中称其为“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 具有相同的效果?

有区别,它与堆栈跟踪是否或如何出现在异常中有关,但我不记得现在哪个是哪个,所以我不会列出这个答案。
@乔尔:谢谢。我想使用 HandleError 异常是个坏主意。我只是想重构一些错误处理代码。
第三种方法是包装一个新的异常并重新抛出 timwise.blogspot.co.uk/2014/05/…
观看此 youtube 视频 throw vs throw ex youtube.com/watch?v=WGwOd6yMja4 当您看到事物时,它会变得更加清晰。

i
iliketocode

是,有一点不同。

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 - 我很抱歉,你是对的。很抱歉投反对票;我撤消为时已晚... :(
@Marc:似乎只有在 throw 不在引发初始异常的方法中时,throw 才会保留原始违规者(请参阅此问题:stackoverflow.com/questions/5152265/…
不,你是对的。在这种情况下 throw;throw ex; 抛出相同的对象,但它的堆栈跟踪以不同的方式修改,正如您在答案中所解释的那样。
@ScottDorman 看起来您的链接在博客迁移后没有被正确转发。看起来像 it now lives here编辑:嘿,等等,这是你的博客!修复您自己的链接! ;^D
N
Nerdroid

(我之前发布过,@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等短方法,所以堆栈跟踪是一样的。也许你应该把它作为一个问题单独发布
S
Shivprasad Koirala

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


那里的图表非常好:)
很好的解释。
N
Nerdroid

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

考虑这个例子:

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 成员)。


N
Nerdroid

让我们了解 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 的行/方法重置堆栈跟踪。


l
liviriniu

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

如果您执行 throw,则异常会继续,您将获得完整的堆栈跟踪。


L
Lucero

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

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


P
Pang

Microsoft Docs 代表:

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

来源:https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2200


N
Nerdroid

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

M
Maksud

最好使用 throw 而不是 throw ex

throw ex 重置原始堆栈跟踪,并且找不到之前的堆栈跟踪。

如果我们使用 throw,我们将获得完整的堆栈跟踪。


N
Nerdroid

为了让您对此有不同的看法,如果您向客户端提供 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
    }
}

T
Tim Sparkles

为了扩展 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.
    }
}

N
Nerdroid
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 的情况下被重置;